[med-svn] [python-mne] 01/08: New upstream version 0.15.2+dfsg

Yaroslav Halchenko debian at onerussian.com
Tue Dec 5 02:52:25 UTC 2017


This is an automated email from the git hooks/post-receive script.

yoh pushed a commit to branch master
in repository python-mne.

commit 89393dcdb926a0f936afa7955b6db3dbeeb0d991
Author: Yaroslav Halchenko <debian at onerussian.com>
Date:   Mon Dec 4 12:53:42 2017 -0500

    New upstream version 0.15.2+dfsg
---
 .gitignore                                         |    4 +-
 .mailmap                                           |   26 +-
 .travis.yml                                        |  208 +-
 MANIFEST.in                                        |   60 +-
 Makefile                                           |   47 +-
 README.rst                                         |   22 +-
 appveyor.yml                                       |   13 +-
 circle.yml                                         |  110 +-
 doc/Makefile                                       |   17 +-
 doc/_static/copybutton.js                          |   66 +
 doc/_static/favicon.ico                            |  Bin 6687 -> 7738 bytes
 doc/_static/flag-icon.css                          |   23 +
 doc/_static/font-awesome.css                       | 2337 +++++++++++++++++++
 doc/_static/fontawesome-webfont.eot                |  Bin 0 -> 165742 bytes
 doc/_static/fontawesome-webfont.ttf                |  Bin 0 -> 165548 bytes
 doc/_static/fontawesome-webfont.woff               |  Bin 0 -> 98024 bytes
 doc/_static/fontawesome-webfont.woff2              |  Bin 0 -> 77160 bytes
 doc/_static/fr.svg                                 |    7 +
 doc/_static/institutions.png                       |  Bin 25284 -> 29945 bytes
 doc/_static/mne_logo.png                           |  Bin 39669 -> 38287 bytes
 doc/_static/mne_logo_small.png                     |  Bin 868 -> 980 bytes
 doc/_static/style.css                              |  223 +-
 doc/_static/us.svg                                 |   18 +
 doc/_templates/layout.html                         |   39 +-
 doc/_templates/navbar.html                         |   20 +
 doc/advanced_setup.rst                             |  163 ++
 doc/carousel.inc                                   |   87 +
 doc/cite.rst                                       |   17 -
 doc/cited.rst                                      |  303 ++-
 doc/conf.py                                        |  139 +-
 doc/{contributing.rst => configure_git.rst}        |  399 +---
 doc/contributing.rst                               |  959 +-------
 doc/customizing_git.rst                            |    4 +
 doc/documentation.rst                              |  710 ++++++
 doc/faq.rst                                        |   79 +-
 doc/getting_started.rst                            |  178 +-
 doc/git_links.inc                                  |    9 +-
 doc/index.rst                                      |  257 +--
 doc/install_mne_c.rst                              |   70 +-
 doc/install_mne_python.rst                         |  298 +--
 doc/known_projects.inc                             |    6 +
 doc/manual/appendix/c_release_notes.rst            |    2 +-
 doc/manual/channel_interpolation.rst               |    6 +-
 doc/manual/cookbook.rst                            |   51 +-
 doc/manual/datasets_index.rst                      |  134 +-
 doc/manual/decoding.rst                            |  101 +-
 doc/manual/index.rst                               |   93 +-
 doc/manual/io.rst                                  |   72 +-
 doc/manual/matlab.rst                              |    6 +-
 doc/manual/memory.rst                              |   10 +-
 doc/manual/migrating.rst                           |   58 +-
 doc/manual/pitfalls.rst                            |   30 -
 doc/manual/preprocessing/bads.rst                  |    2 +
 doc/manual/preprocessing/filter.rst                |    2 +
 doc/manual/preprocessing/overview.rst              |    8 +-
 doc/manual/preprocessing/ssp.rst                   |   30 +-
 doc/manual/sample_dataset.rst                      |    4 +-
 doc/manual/statistics.rst                          |   10 +-
 doc/manual/visualization.rst                       |    2 +
 doc/martinos.rst                                   |   10 +-
 doc/mne_cpp.rst                                    |   35 +-
 doc/python_reference.rst                           |  544 ++---
 doc/references.rst                                 |    2 +-
 doc/sphinxext/cited_mne.py                         |   23 +-
 doc/sphinxext/gen_commands.py                      |    2 +
 doc/tutorials.rst                                  |  192 --
 ...{seven_stories_about_mne.rst => philosophy.rst} |   84 +-
 doc/tutorials/report.rst                           |   30 +-
 doc/whats_new.rst                                  | 2351 +++++++++++++-------
 environment.yml                                    |   44 +
 environment2.yml                                   |   38 +
 .../connectivity/plot_cwt_sensor_connectivity.py   |   16 +-
 .../plot_mixed_source_space_connectivity.py        |  179 ++
 .../plot_mne_inverse_coherence_epochs.py           |   77 +-
 .../plot_mne_inverse_connectivity_spectrum.py      |   10 +-
 .../plot_mne_inverse_label_connectivity.py         |   53 +-
 .../connectivity/plot_mne_inverse_psi_visual.py    |   17 +-
 examples/datasets/plot_brainstorm_data.py          |    8 +-
 examples/datasets/plot_hf_sef_data.py              |   37 +
 examples/datasets/plot_megsim_data_single_trial.py |    2 +-
 examples/datasets/plot_spm_faces_dataset.py        |   18 +-
 examples/decoding/README.txt                       |    6 +-
 examples/decoding/decoding_rsa.py                  |  185 ++
 examples/decoding/plot_decoding_csp_eeg.py         |   77 +-
 examples/decoding/plot_decoding_csp_space.py       |   26 +-
 examples/decoding/plot_decoding_csp_timefreq.py    |  163 ++
 .../plot_decoding_spatio_temporal_source.py        |  141 +-
 examples/decoding/plot_decoding_spoc_CMC.py        |   84 +
 ...plot_decoding_time_generalization_conditions.py |   78 +-
 .../plot_decoding_unsupervised_spatial_filter.py   |    4 +-
 examples/decoding/plot_decoding_xdawn_eeg.py       |   33 +-
 examples/decoding/plot_ems_filtering.py            |   36 +-
 examples/decoding/plot_linear_model_patterns.py    |   72 +-
 examples/decoding/plot_receptive_field.py          |  265 +++
 examples/forward/plot_decimate_head_surface.py     |   16 +-
 examples/forward/plot_forward_sensitivity_maps.py  |    5 +-
 .../forward/plot_left_cerebellum_volume_source.py  |    3 +-
 examples/forward/plot_read_bem_surfaces.py         |   39 -
 examples/forward/plot_source_space_morphing.py     |    1 -
 .../plot_compute_mne_inverse_epochs_in_label.py    |    1 -
 .../plot_compute_mne_inverse_raw_in_label.py       |    2 +-
 examples/inverse/plot_covariance_whitening_dspm.py |   13 +-
 examples/inverse/plot_custom_inverse_solver.py     |  180 ++
 examples/inverse/plot_dics_beamformer.py           |   19 +-
 examples/inverse/plot_dics_source_power.py         |   21 +-
 examples/inverse/plot_gamma_map_inverse.py         |   63 +-
 examples/inverse/plot_label_from_stc.py            |    1 -
 examples/inverse/plot_label_source_activations.py  |    9 +-
 examples/inverse/plot_lcmv_beamformer.py           |   46 +-
 examples/inverse/plot_lcmv_beamformer_volume.py    |   33 +-
 examples/inverse/plot_mixed_norm_inverse.py        |   85 +-
 .../inverse/plot_mixed_source_space_inverse.py     |  148 ++
 examples/inverse/plot_mne_point_spread_function.py |    3 +-
 examples/inverse/plot_rap_music.py                 |   18 +-
 examples/inverse/plot_read_source_space.py         |   20 +-
 examples/inverse/plot_tf_dics.py                   |   16 +-
 examples/inverse/plot_tf_lcmv.py                   |   18 +-
 .../plot_time_frequency_mixed_norm_inverse.py      |   82 +-
 examples/inverse/plot_vector_mne_solution.py       |   41 +
 examples/io/README.txt                             |    2 +-
 examples/io/plot_elekta_epochs.py                  |   22 +-
 examples/io/plot_objects_from_arrays.py            |   50 +-
 examples/io/plot_read_events.py                    |   77 +
 examples/io/read_events.py                         |   31 -
 examples/preprocessing/plot_head_positions.py      |   31 +
 .../preprocessing/plot_movement_compensation.py    |    7 +-
 examples/preprocessing/plot_rereference_eeg.py     |   44 +-
 examples/preprocessing/plot_resample.py            |    4 +-
 examples/preprocessing/plot_run_ica.py             |    6 +-
 examples/preprocessing/plot_virtual_evoked.py      |    4 +-
 examples/preprocessing/plot_xdawn_denoising.py     |   13 +-
 examples/realtime/ftclient_rt_average.py           |    2 +-
 examples/realtime/plot_compute_rt_decoder.py       |   45 +-
 examples/realtime/rt_feedback_server.py            |    2 +-
 examples/simulation/plot_simulate_evoked_data.py   |   10 +-
 examples/stats/plot_linear_regression_raw.py       |    9 +-
 examples/stats/plot_sensor_permutation_test.py     |   10 +-
 examples/stats/plot_sensor_regression.py           |   15 +-
 .../plot_compute_raw_data_spectrum.py              |   54 +-
 .../plot_source_label_time_frequency.py            |   10 +-
 .../plot_source_space_time_frequency.py            |    2 +-
 examples/time_frequency/plot_temporal_whitening.py |    6 +-
 .../time_frequency/plot_time_frequency_erds.py     |  118 +
 .../plot_time_frequency_global_field_power.py      |  128 ++
 .../plot_time_frequency_simulated.py               |  146 +-
 examples/visualization/make_report.py              |   33 +-
 examples/visualization/plot_3d_to_2d.py            |  121 +
 .../visualization/plot_channel_epochs_image.py     |   12 +-
 examples/visualization/plot_clickable_image.py     |   66 -
 examples/visualization/plot_eeg_on_scalp.py        |   25 +
 examples/visualization/plot_evoked_topomap.py      |    1 -
 examples/visualization/plot_evoked_whitening.py    |   10 +-
 examples/visualization/plot_meg_sensors.py         |   25 +-
 examples/visualization/plot_parcellation.py        |   47 +
 examples/visualization/plot_roi_erpimage_by_rt.py  |   72 +
 examples/visualization/plot_sensor_noise_level.py  |   30 +
 .../plot_ssp_projs_sensitivity_map.py              |    4 +-
 .../visualization/plot_topo_compare_conditions.py  |   19 +-
 examples/visualization/plot_topo_customized.py     |    3 +-
 examples/visualization/plot_xhemi.py               |   47 +
 logo/generate_mne_logos.py                         |   22 +-
 mne/__init__.py                                    |   18 +-
 mne/annotations.py                                 |  123 +-
 mne/baseline.py                                    |   51 +-
 mne/beamformer/__init__.py                         |    6 +-
 mne/beamformer/_dics.py                            |  229 +-
 mne/beamformer/_lcmv.py                            | 1029 ++++++---
 mne/beamformer/_rap_music.py                       |   78 +-
 mne/beamformer/tests/test_dics.py                  |  101 +-
 mne/beamformer/tests/test_lcmv.py                  |  264 ++-
 mne/beamformer/tests/test_rap_music.py             |  101 +-
 mne/bem.py                                         |  379 ++--
 mne/channels/__init__.py                           |   13 +-
 mne/channels/channels.py                           |  511 ++++-
 .../data/montages/10-5-System_Mastoids_EGI129.csd  |  467 ----
 mne/channels/data/montages/mgh60.elc               |  132 ++
 mne/channels/data/montages/mgh70.elc               |  152 ++
 mne/channels/data/neighbors/__init__.py            |    2 +-
 mne/channels/interpolation.py                      |   27 +-
 mne/channels/layout.py                             |  132 +-
 mne/channels/montage.py                            |  681 +++---
 mne/channels/tests/test_channels.py                |   99 +-
 mne/channels/tests/test_interpolation.py           |   25 +-
 mne/channels/tests/test_layout.py                  |    1 +
 mne/channels/tests/test_montage.py                 |  560 +++--
 mne/chpi.py                                        |  721 ++++--
 mne/commands/__init__.py                           |    2 +
 mne/commands/mne_browse_raw.py                     |    3 +-
 mne/commands/mne_bti2fiff.py                       |    3 +-
 mne/commands/mne_clean_eog_ecg.py                  |    9 +-
 mne/commands/mne_compare_fiff.py                   |    3 +-
 mne/commands/mne_compute_proj_ecg.py               |    3 +-
 mne/commands/mne_compute_proj_eog.py               |    3 +-
 mne/commands/mne_coreg.py                          |   42 +-
 mne/commands/mne_flash_bem.py                      |    4 +-
 mne/commands/mne_freeview_bem_surfaces.py          |    8 +-
 mne/commands/mne_kit2fiff.py                       |    7 +
 mne/commands/mne_make_scalp_surfaces.py            |   67 +-
 mne/commands/mne_maxfilter.py                      |    3 +-
 mne/commands/mne_report.py                         |   11 +-
 mne/commands/mne_show_fiff.py                      |   19 +-
 mne/commands/mne_show_info.py                      |    1 +
 mne/commands/mne_surf2bem.py                       |   10 +-
 mne/commands/mne_watershed_bem.py                  |    7 +-
 mne/commands/tests/test_commands.py                |   48 +-
 mne/commands/utils.py                              |   49 +-
 mne/connectivity/__init__.py                       |    3 +-
 mne/connectivity/effective.py                      |   17 +-
 mne/connectivity/spectral.py                       |  642 +++---
 mne/connectivity/tests/test_effective.py           |    2 +-
 mne/connectivity/tests/test_spectral.py            |  103 +-
 mne/connectivity/utils.py                          |    3 +-
 mne/coreg.py                                       |  197 +-
 mne/cov.py                                         |  518 +++--
 mne/cuda.py                                        |   52 +-
 mne/data/__init__.py                               |    1 +
 mne/data/coil_def.dat                              |   78 +
 mne/data/fsaverage/fsaverage-fiducials.fif         |  Bin 0 -> 260 bytes
 mne/data/fsaverage/fsaverage-head.fif              |  Bin 0 -> 97892 bytes
 mne/data/fsaverage/fsaverage-inner_skull-bem.fif   |  Bin 0 -> 491944 bytes
 mne/data/fsaverage/fsaverage-trans.fif             |  Bin 0 -> 212 bytes
 mne/data/image/custom_layout.lout                  |   47 +-
 mne/datasets/__init__.py                           |   10 +-
 mne/datasets/_fake/__init__.py                     |    3 +-
 mne/datasets/_fake/_fake.py                        |    4 +-
 mne/datasets/brainstorm/__init__.py                |    3 +-
 mne/datasets/brainstorm/bst_auditory.py            |   21 +-
 mne/datasets/brainstorm/bst_phantom_ctf.py         |   21 +-
 mne/datasets/brainstorm/bst_phantom_elekta.py      |   22 +-
 mne/datasets/brainstorm/bst_raw.py                 |   22 +-
 mne/datasets/brainstorm/bst_resting.py             |   21 +-
 mne/datasets/eegbci/__init__.py                    |    3 +-
 mne/datasets/eegbci/eegbci.py                      |   96 +-
 mne/datasets/fieldtrip_cmc/__init__.py             |    3 +
 mne/datasets/fieldtrip_cmc/fieldtrip_cmc.py        |   32 +
 mne/datasets/hf_sef/__init__.py                    |    3 +
 mne/datasets/hf_sef/hf_sef.py                      |   99 +
 mne/datasets/megsim/__init__.py                    |    3 +-
 mne/datasets/megsim/megsim.py                      |   55 +-
 mne/datasets/megsim/urls.py                        |    6 +-
 mne/datasets/misc/__init__.py                      |    3 +-
 mne/datasets/misc/_misc.py                         |    2 +-
 mne/datasets/mtrf/__init__.py                      |    3 +
 mne/datasets/mtrf/mtrf.py                          |   30 +
 mne/datasets/multimodal/__init__.py                |    3 +-
 mne/datasets/multimodal/multimodal.py              |    4 +-
 mne/datasets/sample/__init__.py                    |    3 +-
 mne/datasets/sample/sample.py                      |    4 +-
 mne/datasets/somato/__init__.py                    |    3 +-
 mne/datasets/somato/somato.py                      |    4 +-
 mne/datasets/spm_face/__init__.py                  |    3 +-
 mne/datasets/spm_face/spm_data.py                  |    4 +-
 mne/datasets/testing/__init__.py                   |    3 +-
 mne/datasets/testing/_testing.py                   |    4 +-
 mne/datasets/tests/test_datasets.py                |   12 +-
 mne/datasets/utils.py                              |  595 +++--
 mne/datasets/visual_92_categories/__init__.py      |    5 +
 .../visual_92_categories/visual_92_categories.py   |   78 +
 mne/decoding/__init__.py                           |   11 +-
 mne/decoding/base.py                               |  914 +++-----
 mne/decoding/csp.py                                |  362 ++-
 mne/decoding/ems.py                                |   60 +-
 mne/decoding/mixin.py                              |    5 +-
 mne/decoding/receptive_field.py                    |  486 ++++
 mne/decoding/search_light.py                       |  377 ++--
 mne/decoding/tests/test_base.py                    |  230 ++
 mne/decoding/tests/test_csp.py                     |  149 +-
 mne/decoding/tests/test_ems.py                     |   19 +-
 mne/decoding/tests/test_receptive_field.py         |  528 +++++
 mne/decoding/tests/test_search_light.py            |  145 +-
 mne/decoding/tests/test_time_frequency.py          |   10 +-
 mne/decoding/tests/test_time_gen.py                |   88 +-
 mne/decoding/tests/test_transformer.py             |  134 +-
 mne/decoding/time_delaying_ridge.py                |  343 +++
 mne/decoding/time_frequency.py                     |   48 +-
 mne/decoding/time_gen.py                           |  100 +-
 mne/decoding/transformer.py                        |  509 ++---
 mne/defaults.py                                    |   22 +-
 mne/dipole.py                                      |  595 +++--
 mne/epochs.py                                      |  631 +++---
 mne/event.py                                       |  201 +-
 mne/evoked.py                                      |  300 ++-
 mne/externals/FieldTrip.py                         |    2 +-
 mne/externals/__init__.py                          |    7 +-
 mne/externals/funcsigs.py                          |  815 +++++++
 mne/externals/tempita/__init__.py                  | 1305 ++++++++++-
 mne/externals/tempita/_looper.py                   |    4 +-
 mne/externals/tempita/_tempita.py                  | 1182 ----------
 mne/externals/tempita/compat3.py                   |   20 +-
 mne/filter.py                                      | 1467 ++++++------
 mne/fixes.py                                       |  794 ++++++-
 mne/forward/__init__.py                            |    5 +-
 mne/forward/_compute_forward.py                    |   60 +-
 mne/forward/_field_interpolation.py                |   52 +-
 mne/forward/_lead_dots.py                          |   56 +-
 mne/forward/_make_forward.py                       |  242 +-
 mne/forward/forward.py                             |  493 ++--
 mne/forward/tests/test_field_interpolation.py      |   53 +-
 mne/forward/tests/test_forward.py                  |  138 +-
 mne/forward/tests/test_make_forward.py             |   66 +-
 mne/gui/__init__.py                                |  102 +-
 mne/gui/_backend.py                                |    9 +-
 mne/gui/_coreg_gui.py                              |  885 +++++---
 mne/gui/_fiducials_gui.py                          |  116 +-
 mne/gui/_file_traits.py                            |  365 +--
 mne/gui/_help.py                                   |    2 +-
 mne/gui/_kit2fiff_gui.py                           |  195 +-
 mne/gui/_marker_gui.py                             |   81 +-
 mne/gui/_viewer.py                                 |  231 +-
 mne/gui/tests/test_coreg_gui.py                    |  146 +-
 mne/gui/tests/test_fiducials_gui.py                |    9 +-
 mne/gui/tests/test_file_traits.py                  |   42 +-
 mne/gui/tests/test_kit2fiff_gui.py                 |   59 +-
 mne/gui/tests/test_marker_gui.py                   |   13 +-
 mne/inverse_sparse/__init__.py                     |    5 +-
 mne/inverse_sparse/_gamma_map.py                   |   94 +-
 mne/inverse_sparse/mxne_debiasing.py               |    7 +-
 mne/inverse_sparse/mxne_inverse.py                 |  313 ++-
 mne/inverse_sparse/mxne_optim.py                   |  649 +++---
 mne/inverse_sparse/tests/test_gamma_map.py         |   94 +-
 mne/inverse_sparse/tests/test_mxne_inverse.py      |  118 +-
 mne/inverse_sparse/tests/test_mxne_optim.py        |   19 +-
 mne/io/__init__.py                                 |   11 +-
 mne/io/array/__init__.py                           |    2 +-
 mne/io/array/array.py                              |   33 +-
 mne/io/array/tests/test_array.py                   |   63 +-
 mne/io/artemis123/__init__.py                      |    7 +
 mne/io/artemis123/artemis123.py                    |  440 ++++
 .../artemis123/resources/Artemis123_ChannelMap.csv |  146 ++
 mne/io/artemis123/resources/Artemis123_mneLoc.csv  |  144 ++
 mne/{data => io/artemis123/tests}/__init__.py      |    0
 mne/io/artemis123/tests/test_artemis123.py         |  100 +
 mne/io/artemis123/utils.py                         |  138 ++
 mne/io/base.py                                     |  771 ++++---
 mne/io/brainvision/__init__.py                     |    2 +-
 mne/io/brainvision/brainvision.py                  |  254 ++-
 mne/io/brainvision/tests/test_brainvision.py       |  335 ++-
 mne/io/bti/__init__.py                             |    2 +-
 mne/io/bti/bti.py                                  |  226 +-
 mne/io/bti/read.py                                 |   44 +-
 mne/io/bti/tests/test_bti.py                       |   18 +-
 mne/io/cnt/__init__.py                             |    2 +
 mne/io/cnt/cnt.py                                  |   93 +-
 mne/io/compensator.py                              |   11 +-
 mne/io/constants.py                                |   52 +-
 mne/io/ctf/__init__.py                             |    2 +-
 mne/io/ctf/constants.py                            |    2 +-
 mne/io/ctf/ctf.py                                  |   38 +-
 mne/io/ctf/eeg.py                                  |    8 +-
 mne/io/ctf/hc.py                                   |    7 +-
 mne/io/ctf/info.py                                 |   58 +-
 mne/io/ctf/res4.py                                 |   25 +-
 mne/io/ctf/tests/test_ctf.py                       |   32 +-
 mne/io/ctf/trans.py                                |   20 +-
 mne/io/ctf_comp.py                                 |   11 +-
 mne/io/diff.py                                     |    5 +-
 mne/io/edf/__init__.py                             |    4 +-
 mne/io/edf/edf.py                                  | 1067 +++++++--
 mne/io/edf/tests/test_edf.py                       |   64 +-
 mne/io/edf/tests/test_gdf.py                       |   92 +
 mne/io/eeglab/__init__.py                          |    4 +-
 mne/io/eeglab/eeglab.py                            |  171 +-
 mne/io/eeglab/tests/test_eeglab.py                 |  115 +-
 mne/io/egi/__init__.py                             |    4 +-
 mne/io/egi/egi.py                                  |   98 +-
 mne/io/egi/egimff.py                               |  576 +++++
 mne/io/egi/events.py                               |  165 ++
 mne/io/egi/general.py                              |  162 ++
 mne/io/egi/tests/test_egi.py                       |   87 +-
 mne/io/fiff/__init__.py                            |    2 +
 mne/io/fiff/raw.py                                 |  168 +-
 mne/io/fiff/tests/test_raw_fiff.py                 |  350 ++-
 mne/io/kit/__init__.py                             |    2 +-
 mne/io/kit/constants.py                            |  267 ++-
 mne/io/kit/coreg.py                                |    8 +-
 mne/io/kit/kit.py                                  |  566 ++---
 mne/io/kit/tests/test_kit.py                       |   95 +-
 mne/io/matrix.py                                   |    9 +-
 mne/io/meas_info.py                                |  652 ++++--
 mne/io/nicolet/__init__.py                         |    2 +-
 mne/io/nicolet/nicolet.py                          |   22 +-
 mne/io/open.py                                     |   46 +-
 mne/io/pick.py                                     |  119 +-
 mne/io/proc_history.py                             |  139 +-
 mne/io/proj.py                                     |  220 +-
 mne/io/reference.py                                |  350 ++-
 mne/io/tag.py                                      |   63 +-
 mne/io/tests/test_apply_function.py                |   25 +-
 mne/io/tests/test_compensator.py                   |   11 +-
 mne/io/tests/test_meas_info.py                     |   55 +-
 mne/io/tests/test_pick.py                          |   23 +-
 mne/io/tests/test_proc_history.py                  |    9 +-
 mne/io/tests/test_raw.py                           |   14 +-
 mne/io/tests/test_reference.py                     |  192 +-
 mne/io/tree.py                                     |    8 +-
 mne/io/utils.py                                    |   15 +-
 mne/io/write.py                                    |   96 +-
 mne/label.py                                       |  190 +-
 mne/minimum_norm/__init__.py                       |    2 +-
 mne/minimum_norm/inverse.py                        |  454 ++--
 mne/minimum_norm/psf_ctf.py                        |   34 +-
 mne/minimum_norm/tests/test_inverse.py             |  319 ++-
 mne/minimum_norm/tests/test_psf_ctf.py             |   11 +-
 mne/minimum_norm/tests/test_time_frequency.py      |   61 +-
 mne/minimum_norm/time_frequency.py                 |  133 +-
 mne/misc.py                                        |    8 +-
 mne/parallel.py                                    |   32 +-
 mne/preprocessing/__init__.py                      |    3 +-
 mne/preprocessing/_fine_cal.py                     |    6 +-
 mne/preprocessing/bads.py                          |    2 +-
 mne/preprocessing/ctps_.py                         |   10 +-
 mne/preprocessing/ecg.py                           |   55 +-
 mne/preprocessing/eog.py                           |   80 +-
 mne/preprocessing/ica.py                           |  479 ++--
 mne/preprocessing/infomax_.py                      |   29 +-
 mne/preprocessing/maxfilter.py                     |   36 +-
 mne/preprocessing/maxwell.py                       |  551 ++---
 mne/preprocessing/peak_finder.py                   |    6 +-
 mne/preprocessing/ssp.py                           |   93 +-
 mne/preprocessing/stim.py                          |   42 +-
 mne/preprocessing/tests/test_ecg.py                |    9 +-
 mne/preprocessing/tests/test_eeglab_infomax.py     |   21 +-
 mne/preprocessing/tests/test_eog.py                |   11 +-
 mne/preprocessing/tests/test_ica.py                |  226 +-
 mne/preprocessing/tests/test_maxwell.py            |  196 +-
 mne/preprocessing/tests/test_ssp.py                |   28 +-
 mne/preprocessing/tests/test_stim.py               |    9 +-
 mne/preprocessing/tests/test_xdawn.py              |   28 +-
 mne/preprocessing/xdawn.py                         |  120 +-
 mne/proj.py                                        |   29 +-
 mne/realtime/__init__.py                           |    2 +-
 mne/realtime/client.py                             |   49 +-
 mne/realtime/epochs.py                             |   75 +-
 mne/realtime/fieldtrip_client.py                   |   52 +-
 mne/realtime/mockclient.py                         |   21 +-
 mne/realtime/stim_server_client.py                 |   58 +-
 mne/realtime/tests/test_mockclient.py              |   69 +-
 mne/report.py                                      |  429 ++--
 mne/selection.py                                   |   20 +-
 mne/simulation/__init__.py                         |    3 +-
 mne/simulation/evoked.py                           |   93 +-
 mne/simulation/metrics.py                          |    6 +-
 mne/simulation/raw.py                              |  288 ++-
 mne/simulation/source.py                           |    6 +-
 mne/simulation/tests/test_evoked.py                |   58 +-
 mne/simulation/tests/test_metrics.py               |    2 +-
 mne/simulation/tests/test_raw.py                   |  186 +-
 mne/simulation/tests/test_source.py                |   14 +-
 mne/source_estimate.py                             | 1527 ++++++++-----
 mne/source_space.py                                |  709 +++---
 mne/stats/__init__.py                              |    7 +-
 mne/stats/cluster_level.py                         |  448 ++--
 mne/stats/multi_comp.py                            |    7 +-
 mne/stats/parametric.py                            |   30 +-
 mne/stats/permutations.py                          |   49 +-
 mne/stats/regression.py                            |   71 +-
 mne/stats/tests/test_cluster_level.py              |  198 +-
 mne/stats/tests/test_permutations.py               |   25 +-
 mne/stats/tests/test_regression.py                 |   35 +-
 mne/surface.py                                     |  620 +++---
 mne/tests/common.py                                |   11 +-
 mne/tests/test_annotations.py                      |  162 +-
 mne/tests/test_bem.py                              |    8 +-
 mne/tests/test_chpi.py                             |  289 ++-
 mne/tests/test_coreg.py                            |   51 +-
 mne/tests/test_cov.py                              |  208 +-
 mne/tests/test_dipole.py                           |  123 +-
 mne/tests/test_docstring_parameters.py             |  201 +-
 mne/tests/test_epochs.py                           |  624 +++---
 mne/tests/test_event.py                            |   35 +-
 mne/tests/test_evoked.py                           |   34 +-
 mne/tests/test_filter.py                           |  263 ++-
 mne/tests/test_fixes.py                            |   19 +-
 mne/tests/test_import_nesting.py                   |    4 +-
 mne/tests/test_label.py                            |   73 +-
 mne/tests/test_line_endings.py                     |    6 +-
 mne/tests/test_proj.py                             |   81 +-
 mne/tests/test_report.py                           |   42 +-
 mne/tests/test_selection.py                        |    6 +-
 mne/tests/test_source_estimate.py                  |  469 ++--
 mne/tests/test_source_space.py                     |  180 +-
 mne/tests/test_surface.py                          |   41 +-
 mne/tests/test_transforms.py                       |  191 +-
 mne/tests/test_utils.py                            |  148 +-
 mne/time_frequency/__init__.py                     |   18 +-
 mne/time_frequency/_stockwell.py                   |   84 +-
 mne/time_frequency/ar.py                           |  156 +-
 mne/time_frequency/csd.py                          |   84 +-
 mne/time_frequency/multitaper.py                   |  172 +-
 mne/time_frequency/psd.py                          |  179 +-
 mne/time_frequency/stft.py                         |   12 +-
 mne/time_frequency/tests/test_ar.py                |   46 +-
 mne/time_frequency/tests/test_csd.py               |   24 +-
 mne/time_frequency/tests/test_multitaper.py        |   52 +-
 mne/time_frequency/tests/test_psd.py               |   69 +-
 mne/time_frequency/tests/test_stockwell.py         |   11 +-
 mne/time_frequency/tests/test_tfr.py               |  153 +-
 mne/time_frequency/tfr.py                          |  723 +++---
 mne/transforms.py                                  |  701 +++++-
 mne/utils.py                                       |  693 +++---
 mne/viz/_3d.py                                     | 2237 ++++++++++++++++---
 mne/viz/__init__.py                                |   10 +-
 mne/viz/circle.py                                  |   14 +-
 mne/viz/decoding.py                                |   22 +-
 mne/viz/epochs.py                                  |  778 +++++--
 mne/viz/evoked.py                                  | 1229 +++++-----
 mne/viz/ica.py                                     |  158 +-
 mne/viz/misc.py                                    |  371 ++-
 mne/viz/montage.py                                 |   86 +-
 mne/viz/raw.py                                     |  578 +++--
 mne/viz/tests/__init__py                           |    0
 mne/viz/tests/test_3d.py                           |  297 ++-
 mne/viz/tests/test_decoding.py                     |  110 +-
 mne/viz/tests/test_epochs.py                       |   87 +-
 mne/viz/tests/test_evoked.py                       |   85 +-
 mne/viz/tests/test_ica.py                          |   61 +-
 mne/viz/tests/test_misc.py                         |   37 +-
 mne/viz/tests/test_montage.py                      |   42 +-
 mne/viz/tests/test_raw.py                          |  212 +-
 mne/viz/tests/test_topo.py                         |  106 +-
 mne/viz/tests/test_topomap.py                      |  156 +-
 mne/viz/tests/test_utils.py                        |   50 +-
 mne/viz/topo.py                                    |  334 ++-
 mne/viz/topomap.py                                 |  804 ++++---
 mne/viz/utils.py                                   |  955 ++++++--
 setup.cfg                                          |   29 +-
 setup.py                                           |   67 +-
 tutorials/plot_artifacts_correction_filtering.py   |   31 +-
 tutorials/plot_artifacts_correction_ica.py         |   66 +-
 .../plot_artifacts_correction_maxwell_filtering.py |    5 +-
 tutorials/plot_artifacts_correction_rejection.py   |   10 +-
 tutorials/plot_artifacts_correction_ssp.py         |   20 +-
 tutorials/plot_artifacts_detection.py              |    3 +-
 tutorials/plot_background_filtering.py             |  334 +--
 tutorials/plot_brainstorm_auditory.py              |   37 +-
 tutorials/plot_brainstorm_phantom_ctf.py           |   13 +-
 tutorials/plot_brainstorm_phantom_elekta.py        |   81 +-
 tutorials/plot_compute_covariance.py               |   79 +-
 tutorials/plot_configuration.py                    |   87 +
 tutorials/plot_creating_data_structures.py         |   55 +-
 tutorials/plot_dipole_fit.py                       |   11 +-
 tutorials/plot_dipole_orientations.py              |  206 ++
 tutorials/plot_ecog.py                             |   76 +
 tutorials/plot_eeg_erp.py                          |   20 +-
 tutorials/plot_epoching_and_averaging.py           |   11 +-
 tutorials/plot_epochs_to_data_frame.py             |   13 +-
 tutorials/plot_forward.py                          |   39 +-
 tutorials/plot_ica_from_raw.py                     |   18 +-
 tutorials/plot_info.py                             |   54 +-
 tutorials/plot_introduction.py                     |   16 +-
 tutorials/plot_mne_dspm_source_localization.py     |   49 +-
 tutorials/plot_modifying_data_inplace.py           |   36 +-
 tutorials/plot_object_epochs.py                    |   24 +-
 tutorials/plot_object_evoked.py                    |   16 +-
 tutorials/plot_object_raw.py                       |   42 +-
 tutorials/plot_point_spread.py                     |   25 +-
 tutorials/plot_python_intro.py                     |   48 +-
 tutorials/plot_receptive_field.py                  |  371 +++
 tutorials/plot_sensors_decoding.py                 |  141 +-
 tutorials/plot_sensors_time_frequency.py           |   10 +-
 tutorials/plot_source_alignment.py                 |   98 +
 ...plot_stats_cluster_1samp_test_time_frequency.py |    8 +-
 tutorials/plot_stats_cluster_methods.py            |   32 +-
 tutorials/plot_stats_cluster_spatio_temporal.py    |    9 +-
 ...ster_spatio_temporal_repeated_measures_anova.py |    2 +-
 tutorials/plot_stats_cluster_time_frequency.py     |   10 +-
 ...uster_time_frequency_repeated_measures_anova.py |   23 +-
 .../plot_stats_spatio_temporal_cluster_sensors.py  |   13 +-
 tutorials/plot_visualize_epochs.py                 |   73 +-
 tutorials/plot_visualize_evoked.py                 |   83 +-
 tutorials/plot_visualize_raw.py                    |   68 +-
 571 files changed, 50906 insertions(+), 25018 deletions(-)

diff --git a/.gitignore b/.gitignore
index d7cff50..aa61b3c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -37,10 +37,10 @@ MNE-somato-data*
 MNE-spm-face*
 MNE-testing-data*
 MNE-brainstorm-data*
-MNE-misc-data*
 MEGSIM*
 build
 coverage
+.cache/
 
 dist/
 doc/_build/
@@ -48,12 +48,14 @@ doc/generated/
 doc/auto_examples/
 doc/auto_tutorials/
 doc/modules/generated/
+doc/sphinxext/cachedir
 pip-log.txt
 .coverage
 tags
 doc/coverages
 doc/samples
 cover
+examples/visualization/foobar.html
 
 *.orig
 
diff --git a/.mailmap b/.mailmap
index cb4759f..672710e 100644
--- a/.mailmap
+++ b/.mailmap
@@ -33,8 +33,9 @@ Teon Brooks <teon.brooks at gmail.com> <teon at nyu.edu>
 Romain Trachel <romain.trachel at inria.fr>
 Roman Goj <roman.goj at gmail.com>
 Andrew Dykstra <andrew.r.dykstra at gmail.com>
-Yousra BEKHTI <yousra.bekhti at gmail.com> Yoursa BEKHTI <ybekhti at is222485.intra.cea.fr>
-Yousra BEKHTI <yousra.bekhti at gmail.com> Yoursa BEKHTI <yousra.bekhti at gmail.com>
+Yousra Bekhti <yousra.bekhti at gmail.com> Yoursa BEKHTI <ybekhti at is222485.intra.cea.fr>
+Yousra Bekhti <yousra.bekhti at gmail.com> Yoursa BEKHTI <yousra.bekhti at gmail.com>
+Yousra Bekhti <yousra.bekhti at gmail.com> yousrabk <yousra.bekhti at gmail.com>
 Alan Leggitt <leggitta3 at gmail.com> leggitta <leggitta3 at gmail.com>
 Praveen Sripad <pravsripad at gmail.com> prav <pravsripad at gmail.com>
 Praveen Sripad <pravsripad at gmail.com> prav <prav at prav-dell.(none)>
@@ -45,6 +46,7 @@ Mainak Jas <mainakjas at gmail.com> Mainak <mainakjas at gmail.com>
 Mainak Jas <mainakjas at gmail.com> Mainak Jas <mainak.jas at telecom-paristech.fr>
 Mainak Jas <mainakjas at gmail.com> mainakjas <mainakjas at users.noreply.github.com>
 Mainak Jas <mainakjas at gmail.com> Mainak Jas <mainak at neuro.hut.fi> <mainakjas at users.noreply.github.com>
+Mainak Jas <mainakjas at gmail.com> Mainak Jas <jasmainak at users.noreply.github.com>
 Dan G. Wakeman <dgwakeman at gmail.com> Daniel Wakeman <dwakeman at marcie.nmr.mgh.harvard.edu>
 Marmaduke Woodman <mmwoodman at gmail.com> maedoc <maedoc at mm.st>
 Brad Buran <bburan at galenea.com> Brad Buran <bburan at alum.mit.edu>
@@ -69,7 +71,8 @@ Jona Sassenhagen <jona.sassenhagen at gmail.com> sassenha <sassenha at fiebach1.rz.uni
 Jona Sassenhagen <jona.sassenhagen at gmail.com> jona.sassenhagen at gmail.com <jona.sassenhagen at gmail.com>
 Yousra Bekhti <yousra.bekhti at gmail.com> Yousra BEKHTI <yousra.bekhti at gmail.com>
 Ross Maddox <rkmaddox at uw.edu> unknown <rkmaddox at uw.edu>
-Jaakko Leppakangas <jaeilepp at student.jyu.fi> jaeilepp <jaeilepp at student.jyu.fi>
+Jaakko Leppakangas <jaeilepp at gmail.com> jaeilepp <jaeilepp at student.jyu.fi>
+Jaakko Leppakangas <jaeilepp at gmail.com> jaeilepp <jaeilepp at gmail.com>
 Jair Montoya <montoya.jair.m at gmail.com> jmontoyam <montoya.jair.m at gmail.com>
 Natalie Klein <neklein at andrew.cmu.edu> natalieklein <neklein at andrew.cmu.edu>
 Daniel McCloy <dan.mccloy at gmail.com> drammock <dan.mccloy at gmail.com>
@@ -81,4 +84,19 @@ Guillaume Dumas <deep at introspection.eu> Guillaume Dumas <deep-introspection at user
 Félix Raimundo <gamaz3ps at gmail.com> Felix Raimundo <gamaz3ps at gmail.com>
 Asish Panda <asishrocks95 at gmail.com> kaichogami <asishrocks95 at gmail.com>
 Mikolaj Magnuski <mmagnuski at swps.edu.pl> mmagnuski <mmagnuski at swps.edu.pl>
-Alexandre Barachant <alexandre.barachant at gmail.com> alexandre barachant <alexandre.barachant at gmail.com>
\ No newline at end of file
+Alexandre Barachant <alexandre.barachant at gmail.com> alexandre barachant <alexandre.barachant at gmail.com>
+Lorenzo Alfine <lorenzo.alfine at gmail.com> lorrandal <lorenzo.alfine at gmail.com>
+Paul Pasler <paul at ppasler.de> ppasler <paul at ppasler.de>
+Jon Houck <jon.houck at gmail.com> Jon Houck <jhouck at users.noreply.github.com>
+Cristóbal Moënne-Loccoz <cmmoenne at gmail.com> Cristóbal <cmmoenne at gmail.com>
+Chris Holdgraf <choldgraf at gmail.com> Chris Holdgraf <choldgraf at berkeley.edu>
+Britta Westner <britta.wstnr at gmail.com> britta-wstnr <britta.wstnr at gmail.com>
+Jesper Duemose Nielsen <jdue at dtu.dk> jdue <jdue at dtu.dk>
+Laetitia Grabot <laetitia.grabot at gmail.com> LaetitiaG <laetitia.grabot at gmail.com>
+Nicolas Barascud <nbara at users.noreply.github.com> nbara <nbara at users.noreply.github.com>
+Lukáš Hejtmánek <hejtmy at gmail.com> hejtmy <hejtmy at gmail.com>
+Ramonapariciog Apariciogarcia <moncho_apa at hotmail.com> ramonapariciog <moncho_apa at hotmail.com>
+Mathurin Massias <mathurin.massias at gmail.com> mathurinm <mathurinm at users.noreply.github.com>
+Simon Kern <simon.kern at online.de> skjerns <simon.kern at online.de>
+S. M. Gutstein <s.m.gutstein at gmail.com> smgutstein <s.m.gutstein at gmail.com>
+Robin Tibor Schirrmeister <robintibor at gmail.com> robintibor <robintibor at gmail.com>
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
index 90a13cb..57492f2 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,96 +1,97 @@
-language: python
-
-# Use container-based infrastructure
+language: c
+dist: trusty
 sudo: false
-
+cache:
+  apt: true
 env:
-    # Enable python 2 and python 3 builds
-    # DEPS=full: build optional dependencies: pandas, nitime, statsmodels,
-    #            scikit-learn, patsy, nibabel pillow;
-    #            in the case of Python 2, also mayavi, traits, pysurfer
-    # DEPS=minimal: don't build optional dependencies; tests that require those
-    #               dependencies are supposed to be skipped
-    #
-    # Note that we don't run coverage on Py3k anyway because it slows our tests
-    # by a factor of 2 (!), so we make this our "from install dir" run.
-    #
-    # Run one test (3.5) with a non-default stim channel to make sure our
-    # tests are explicit about channels.
-    #
-    # Must force libpng version to avoid silly libpng.so.15 error (MPL 1.1 needs it)
-    #
-    # Conda currently has packaging bug with mayavi/traits/numpy where 1.10 can't be used
-    # but breaks sklearn on install; hopefully eventually the NUMPY=1.9 on 2.7 full can be removed
-    # Mayavi=4.3 on old 2.7 installs, but doesn't work properly due to a traits bug
-    - PYTHON=2.7 DEPS=full TEST_LOCATION=src NUMPY="=1.9" SCIPY="=0.17"
-    - PYTHON=2.7 DEPS=nodata TEST_LOCATION=src MNE_DONTWRITE_HOME=true MNE_FORCE_SERIAL=true MNE_SKIP_NETWORK_TEST=1  # also runs flake8
-    - PYTHON=3.5 DEPS=full TEST_LOCATION=install MNE_STIM_CHANNEL=STI101
-    - PYTHON=2.7 DEPS=full TEST_LOCATION=src NUMPY="=1.8" SCIPY="=0.12" MPL="=1.3" SKLEARN="=0.14" PANDAS="=0.12"
-    - PYTHON=2.7 DEPS=minimal TEST_LOCATION=src
+    # TRAVIS_PYTHON_VERSION is only needed for neo's setup.py
+    global: PYTHON_VERSION=3.6 DISPLAY=:99.0 MNE_LOGGING_LEVEL=warning TEST_LOCATION=src
+            PIP_DEPENDENCIES="codecov pytest-faulthandler pytest-sugar" OPTION=""
+            TRAVIS_PYTHON_VERSION=3.6
+
+matrix:
+    include:
+        # No data + style testing
+        - os: linux
+          env: DEPS=nodata MNE_DONTWRITE_HOME=true MNE_FORCE_SERIAL=true MNE_SKIP_NETWORK_TEST=1
+               CONDA_DEPENDENCIES="numpy scipy matplotlib sphinx nose pytest pytest-cov"
+               PIP_DEPENDENCIES="flake8 numpydoc codespell git+git://github.com/PyCQA/pydocstyle.git codecov check-manifest pytest-sugar"
+               OPTION="--doctest-ignore-import-errors"
+
+        # Linux
+        - os: linux
+          env: CONDA_ENVIRONMENT="environment.yml"
+               SPLIT=0
+        - os: linux
+          env: CONDA_ENVIRONMENT="environment.yml"
+               SPLIT=1
+
+        # OSX
+        - os: osx
+          env: CONDA_ENVIRONMENT="environment.yml"
+               SPLIT=0
+        - os: osx
+          env: CONDA_ENVIRONMENT="environment.yml"
+               SPLIT=1
+
+        # 2.7 + non-default stim channel
+        - os: linux
+          env: CONDA_ENVIRONMENT="environment2.yml" MNE_STIM_CHANNEL=STI101
+               SPLIT=0
+        - os: linux
+          env: CONDA_ENVIRONMENT="environment2.yml" MNE_STIM_CHANNEL=STI101
+               SPLIT=1
+
+        # 2.7 Old dependencies
+        - os: linux
+          env: PYTHON_VERSION=2.7
+               CONDA_DEPENDENCIES="numpy=1.8 scipy=0.12 matplotlib=1.3 pandas=0.12 scikit-learn=0.14 nose pytest pytest-cov"
+               SPLIT=0
+        - os: linux
+          env: PYTHON_VERSION=2.7
+               CONDA_DEPENDENCIES="numpy=1.8 scipy=0.12 matplotlib=1.3 pandas=0.12 scikit-learn=0.14 nose pytest pytest-cov"
+               SPLIT=1
+
+        # Minimal
+        - os: linux
+          env: DEPS=minimial
+               CONDA_DEPENDENCIES="numpy scipy matplotlib nose pytest pytest-cov"
+               SPLIT=0
+        - os: linux
+          env: DEPS=minimial
+               CONDA_DEPENDENCIES="numpy scipy matplotlib nose pytest pytest-cov"
+               SPLIT=1
 
 # Setup anaconda
 before_install:
-  - wget -q http://repo.continuum.io/miniconda/Miniconda-latest-Linux-x86_64.sh -O miniconda.sh
-  - chmod +x miniconda.sh
-  - ./miniconda.sh -b -p /home/travis/miniconda
-  - export PATH=/home/travis/miniconda/bin:$PATH
-  - conda update --yes --quiet conda
-  # We need to create a (fake) display on Travis (allows Mayavi tests to run)
-  - export DISPLAY=:99.0
-  - /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -screen 0 1400x900x24 -ac +extension GLX +render -noreset
-
-install:
-    - conda create -n testenv --yes pip python=$PYTHON
-    - source activate testenv
-    - ENSURE_PACKAGES="numpy$NUMPY scipy$SCIPY matplotlib$MPL libpng$LIBPNG"
-    - conda install --yes --quiet $ENSURE_PACKAGES nose coverage
-    # We have to replicate e.g. numpy$NUMPY to ensure the recommended (higher) versions
-    # are not automatically installed below with multiple "conda install" calls!
-    - if [ "${DEPS}" == "full" ]; then
-        curl https://staff.washington.edu/larsoner/minimal_cmds.tar.gz | tar xz;
+    - if [ "${TRAVIS_OS_NAME}" == "linux" ]; then
+        /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -screen 0 1400x900x24 -ac +extension GLX +render -noreset;
+      fi;
+    - git clone https://github.com/astropy/ci-helpers.git
+    - source ci-helpers/travis/setup_conda.sh
+    - if [ ! -z $CONDA_ENVIRONMENT ]; then
+        pip uninstall --yes mne;
+      fi;
+    # Don't source mne_setup_sh here because changing PATH etc. can't be done in a script
+    - if [ "${DEPS}" == "" ]; then
         export MNE_ROOT="${PWD}/minimal_cmds";
-        export NEUROMAG2FT_ROOT="${PWD}/minimal_cmds/bin";
-        source ${MNE_ROOT}/bin/mne_setup_sh;
-        conda install --yes --quiet $ENSURE_PACKAGES pandas$PANDAS scikit-learn$SKLEARN patsy h5py pillow;
-        pip install -q joblib nibabel;
-        if [ "${PYTHON}" == "3.5" ]; then
-          conda install --yes --quiet $ENSURE_PACKAGES ipython;
+        export PATH=${MNE_ROOT}/bin:$PATH;
+        if [ "${TRAVIS_OS_NAME}" == "linux" ]; then
+          curl https://staff.washington.edu/larsoner/minimal_cmds.tar.gz | tar xz;
+          export LD_LIBRARY_PATH=${MNE_ROOT}/lib:$LD_LIBRARY_PATH;
+          export NEUROMAG2FT_ROOT="${PWD}/minimal_cmds/bin";
         else
-          conda install --yes --quiet $ENSURE_PACKAGES ipython==1.1.0 statsmodels pandas$PANDAS;
-          pip install nitime faulthandler;
-          if [ "${NUMPY}" != "=1.8" ]; then
-            conda install --yes --quiet $ENSURE_PACKAGES mayavi$MAYAVI;
-            pip install pysurfer;
-          fi;
+          curl https://staff.washington.edu/larsoner/minimal_cmds_osx.tar.gz | tar xz;
+          export DYLD_LIBRARY_PATH=${MNE_ROOT}/lib:$DYLD_LIBRARY_PATH;
         fi;
+        mne_surf2bem --version;
       fi;
-    - if [ "${DEPS}" == "nodata" ]; then
-        conda install --yes $ENSURE_PACKAGES sphinx;
-        pip install flake8 codespell numpydoc;
-      fi;
-    - pip install -q codecov nose-timer
-    # check our versions for the major packages
-    - NP_VERSION=`python -c 'import numpy; print(numpy.__version__)'`
-    - if [ -n "$NUMPY" ] && [ "${NUMPY:(-3)}" != "${NP_VERSION::3}" ]; then
-        echo "Incorrect numpy version $NP_VERSION";
-        exit 1;
-      fi;
-    - SP_VERSION=`python -c 'import scipy; print(scipy.__version__)'`
-    - if [ -n "$SCIPY" ] && [ "${SCIPY:(-4)}" != "${SP_VERSION::4}" ]; then
-        echo "Incorrect scipy version $SP_VERSION";
-        exit 1;
-      fi;
-    - MPL_VERSION=`python -c 'import matplotlib; print(matplotlib.__version__)'`
-    - if [ -n "$MPL" ] && [ "${MPL:(-3)}" != "${MPL_VERSION::3}" ]; then
-        echo "Incorrect matplotlib version $MPL_VERSION";
-        exit 1;
-      fi;
+
+install:
     # Suppress the parallel outputs for logging cleanliness
-    - export MNE_LOGGING_LEVEL=warning
     - python setup.py build
     - python setup.py install
-    - myscripts='browse_raw bti2fiff surf2bem'
-    - for script in $myscripts; do mne $script --help; done;
+    - python -c "import mne; mne.sys_info()"
     - SRC_DIR=$(pwd)
     - cd ~
     # Trigger download of testing data. Note that
@@ -98,7 +99,11 @@ install:
     # files to act as a FREESURFER_HOME for the coreg tests
     - if [ "${DEPS}" != "nodata" ]; then
         python -c 'import mne; mne.datasets.testing.data_path(verbose=True)';
-        if [ "${DEPS}" == "full" ]; then
+        ls -al $HOME/mne_data/;
+        ls -al $HOME/mne_data/MNE-testing-data/;
+        ls -al $HOME/mne_data/MNE-testing-data/MEG/;
+        ls -al $HOME/mne_data/MNE-testing-data/MEG/sample/;
+        if [ "${DEPS}" == "" ] && [ "${TRAVIS_OS_NAME}" == "linux" ]; then
           export FREESURFER_HOME=$(python -c 'import mne; print(mne.datasets.testing.data_path())');
           export MNE_SKIP_FS_FLASH_CALL=1;
         fi;
@@ -127,19 +132,40 @@ install:
         COVERAGE=--with-coverage;
       fi;
 
+
 script:
-    - nosetests -a '!ultra_slow_test' --with-timer --timer-top-n 30 --verbosity=2 $COVERAGE
-    - if [ "${DEPS}" == "nodata" ]; then
-        make flake;
+    - echo "Print locale "
+    - locale
+    - echo "Other stuff"
+    # OSX runs ~2x slower than Linux on Travis, so skip any slow ones there
+    - if [ "${TRAVIS_OS_NAME}" == "osx" ]; then
+        CONDITION='not slowtest';
+      else
+        CONDITION='not ultraslowtest';
       fi;
+    - python -c "import mne; print(mne.sys_info())"
+    # Determine directories to test
+    - if [ -z ${SPLIT} ]; then
+        USE_DIRS="mne/";
+      else
+        if [ "${SPLIT}" == "0" ]; then
+          MNE_DIRS=". beamformer channels commands connectivity datasets decoding forward gui inverse_sparse io";
+        else
+          MNE_DIRS="minimum_norm preprocessing realtime simulation stats time_frequency viz";
+        fi;
+        USE_DIRS="";
+        for DIR in ${MNE_DIRS}; do
+          USE_DIRS="mne/${DIR}/tests ${USE_DIRS}";
+        done;
+      fi;
+    - echo py.test -m "${CONDITION}" ${OPTION} ${USE_DIRS} --cov=mne;
+    - py.test -m "${CONDITION}" ${OPTION} ${USE_DIRS} --cov=mne;
     - if [ "${DEPS}" == "nodata" ]; then
-        make codespell-error;
+        make pep;
+        check-manifest --ignore doc,logo,mne/io/*/tests/data*,mne/io/tests/data,mne/preprocessing/tests/data;
       fi;
 
 after_success:
     # Need to run from source dir to exectue "git" commands
-    - if [ "${TEST_LOCATION}" == "src" ]; then
-        echo "Running codecov";
-        cd ${SRC_DIR};
-        codecov;
-      fi;
+    - cd ${SRC_DIR};
+    - codecov;
diff --git a/MANIFEST.in b/MANIFEST.in
index 0dbcc6f..77d13b5 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,29 +1,53 @@
 include *.rst
+include LICENSE.txt
 include mne/__init__.py
-# recursive-include doc *
+
 recursive-include examples *.py
 recursive-include examples *.txt
+recursive-include tutorials *.py
+recursive-include tutorials *.txt
+
 recursive-include mne *.py
-recursive-include mne/data *.dat
-recursive-include mne/data *.sel
-recursive-include mne/data *.fif.gz
-recursive-include mne/channels/data/montages *.elc
-recursive-include mne/channels/data/montages *.txt
-recursive-include mne/channels/data/montages *.csd
-recursive-include mne/channels/data/montages *.sfp
-recursive-include mne/channels/data/layouts *.lout
-recursive-include mne/channels/data/layouts *.lay
-recursive-include mne/channels/data/neighbors *.mat
-recursive-include mne/preprocessing/tests/data *.mat
+recursive-include mne/data *
+recursive-include mne/data/helmets *
+recursive-include mne/data/image *
+recursive-include mne/data/fsaverage *
+
+recursive-include mne/channels/data/layouts *
+recursive-include mne/channels/data/montages *
+recursive-include mne/channels/data/neighbors *
+
+recursive-include mne/gui/help *.json
+
 recursive-include mne/html *.js
 recursive-include mne/html *.css
-recursive-include mne/gui/help *.json
+
+recursive-include mne/io/artemis123/resources *
+
+
+### Exclude
+
 recursive-exclude examples/MNE-sample-data *
 recursive-exclude examples/MNE-testing-data *
 recursive-exclude examples/MNE-spm-face *
 recursive-exclude examples/MNE-somato-data *
-# recursive-exclude mne/io/tests/data *
-# recursive-exclude mne/io/bti/tests/data *
-# recursive-exclude mne/io/kit/tests/data *
-# recursive-exclude mne/io/edf/tests/data *
-# recursive-exclude mne/io/brainvision/tests/data *
+exclude Makefile
+exclude .coveragerc
+exclude *.yml
+exclude dictionary.txt
+exclude .mailmap
+recursive-exclude mne *.pyc
+
+recursive-exclude doc *
+recursive-exclude logo *
+
+# Test files
+
+recursive-exclude mne/io/tests/data *
+recursive-exclude mne/io/bti/tests/data *
+recursive-exclude mne/io/edf/tests/data *
+recursive-exclude mne/io/kit/tests/data *
+recursive-exclude mne/io/brainvision/tests/data *
+recursive-exclude mne/io/egi/tests/data *
+recursive-exclude mne/io/nicolet/tests/data *
+recursive-exclude mne/preprocessing/tests/data *
diff --git a/Makefile b/Makefile
index 9353cf1..edcddbf 100755
--- a/Makefile
+++ b/Makefile
@@ -3,9 +3,9 @@
 # caution: testing won't work on windows, see README
 
 PYTHON ?= python
-NOSETESTS ?= nosetests
+PYTESTS ?= py.test
 CTAGS ?= ctags
-CODESPELL_SKIPS ?= "*.fif,*.eve,*.gz,*.tgz,*.zip,*.mat,*.stc,*.label,*.w,*.bz2,*.annot,*.sulc,*.log,*.local-copy,*.orig_avg,*.inflated_avg,*.gii,*.pyc,*.doctree,*.pickle,*.inv,*.png,*.edf,*.touch,*.thickness,*.nofix,*.volume,*.defect_borders,*.mgh,lh.*,rh.*,COR-*,FreeSurferColorLUT.txt,*.examples,.xdebug_mris_calc,bad.segments,BadChannels,*.hist,empty_file,*.orig,*.js,*.map,*.ipynb"
+CODESPELL_SKIPS ?= "*.fif,*.eve,*.gz,*.tgz,*.zip,*.mat,*.stc,*.label,*.w,*.bz2,*.annot,*.sulc,*.log,*.local-copy,*.orig_avg,*.inflated_avg,*.gii,*.pyc,*.doctree,*.pickle,*.inv,*.png,*.edf,*.touch,*.thickness,*.nofix,*.volume,*.defect_borders,*.mgh,lh.*,rh.*,COR-*,FreeSurferColorLUT.txt,*.examples,.xdebug_mris_calc,bad.segments,BadChannels,*.hist,empty_file,*.orig,*.js,*.map,*.ipynb,searchindex.dat"
 CODESPELL_DIRS ?= mne/ doc/ tutorials/ examples/
 all: clean inplace test test-doc
 
@@ -39,44 +39,41 @@ testing_data:
 
 test: in
 	rm -f .coverage
-	$(NOSETESTS) -a '!ultra_slow_test' mne
+	$(PYTESTS) -m 'not ultraslowtest' mne
 
 test-verbose: in
 	rm -f .coverage
-	$(NOSETESTS) -a '!ultra_slow_test' mne --verbose
+	$(PYTESTS) -m 'not ultraslowtest' mne --verbose
 
 test-fast: in
 	rm -f .coverage
-	$(NOSETESTS) -a '!slow_test' mne
+	$(PYTESTS) -m 'not slowtest' mne
 
 test-full: in
 	rm -f .coverage
-	$(NOSETESTS) mne
+	$(PYTESTS) mne
 
 test-no-network: in
-	sudo unshare -n -- sh -c 'MNE_SKIP_NETWORK_TESTS=1 nosetests mne'
+	sudo unshare -n -- sh -c 'MNE_SKIP_NETWORK_TESTS=1 py.test mne'
 
 test-no-testing-data: in
 	@MNE_SKIP_TESTING_DATASET_TESTS=true \
-	$(NOSETESTS) mne
+	$(PYTESTS) mne
 
 test-no-sample-with-coverage: in testing_data
 	rm -rf coverage .coverage
-	$(NOSETESTS) --with-coverage --cover-package=mne --cover-html --cover-html-dir=coverage
+	$(PYTESTS) --cov=mne --cov-report html:coverage
 
 test-doc: sample_data testing_data
-	$(NOSETESTS) --with-doctest --doctest-tests --doctest-extension=rst doc/
+	$(PYTESTS) --doctest-modules --doctest-ignore-import-errors --doctest-glob='*.rst' ./doc/
 
 test-coverage: testing_data
 	rm -rf coverage .coverage
-	$(NOSETESTS) --with-coverage --cover-package=mne --cover-html --cover-html-dir=coverage
-
-test-profile: testing_data
-	$(NOSETESTS) --with-profile --profile-stats-file stats.pf mne
-	hotshot2dot stats.pf | dot -Tpng -o profile.png
+	$(PYTESTS) --cov=mne --cov-report html:coverage
+# whats the difference with test-no-sample-with-coverage?
 
 test-mem: in testing_data
-	ulimit -v 1097152 && $(NOSETESTS)
+	ulimit -v 1097152 && $(PYTESTS) mne
 
 trailing-spaces:
 	find . -name "*.py" | xargs perl -pi -e 's/[ \t]*$$//'
@@ -100,10 +97,21 @@ flake:
 	@echo "flake8 passed"
 
 codespell:  # running manually
-	@codespell.py -w -i 3 -q 3 -S $(CODESPELL_SKIPS) -D ./dictionary.txt $(CODESPELL_DIRS)
+	@codespell -w -i 3 -q 3 -S $(CODESPELL_SKIPS) -D ./dictionary.txt $(CODESPELL_DIRS)
 
 codespell-error:  # running on travis
-	@codespell.py -i 0 -q 7 -S $(CODESPELL_SKIPS) -D ./dictionary.txt $(CODESPELL_DIRS)
+	@codespell -i 0 -q 7 -S $(CODESPELL_SKIPS) -D ./dictionary.txt $(CODESPELL_DIRS)
+
+pydocstyle:
+	@echo "Running pydocstyle"
+	@pydocstyle
+
+docstring:
+	@echo "Running docstring tests"
+	@$(PYTESTS) --doctest-modules mne/tests/test_docstring_parameters.py
+
+pep:
+	@$(MAKE) -k flake pydocstyle docstring codespell-error
 
 manpages:
 	@echo "I: generating manpages"
@@ -122,3 +130,6 @@ build-doc-dev:
 build-doc-stable:
 	cd doc; make clean
 	cd doc; DISPLAY=:1.0 xvfb-run -n 1 -s "-screen 0 1280x1024x24 -noreset -ac +extension GLX +render" make html_stable
+
+docstyle:
+	@pydocstyle
diff --git a/README.rst b/README.rst
index f07d1ae..0888fe0 100644
--- a/README.rst
+++ b/README.rst
@@ -1,13 +1,18 @@
 .. -*- mode: rst -*-
 
 
-|Travis|_ |Appveyor|_ |Codecov|_ |Zenodo|_
+|Travis|_ |Appveyor|_ |Circle|_ |Codecov|_ |Zenodo|_
+
+|MNE|_
 
 .. |Travis| image:: https://api.travis-ci.org/mne-tools/mne-python.png?branch=master
 .. _Travis: https://travis-ci.org/mne-tools/mne-python
 
-.. |Appveyor| image:: https://ci.appveyor.com/api/projects/status/reccwk3filrasumg/branch/master?svg=true
-.. _Appveyor: https://ci.appveyor.com/project/Eric89GXL/mne-python/branch/master
+.. |Appveyor| image:: https://ci.appveyor.com/api/projects/status/7isroetnxsp7hgxv/branch/master?svg=true
+.. _Appveyor: https://ci.appveyor.com/project/mne-tools/mne-python/branch/master
+
+.. |Circle| image:: https://circleci.com/gh/mne-tools/mne-python.svg?style=svg
+.. _Circle: https://circleci.com/gh/mne-tools/mne-python
 
 .. |Codecov| image:: https://codecov.io/gh/mne-tools/mne-python/branch/master/graph/badge.svg
 .. _Codecov: https://codecov.io/gh/mne-tools/mne-python
@@ -15,6 +20,9 @@
 .. |Zenodo| image:: https://zenodo.org/badge/5822/mne-tools/mne-python.svg
 .. _Zenodo: https://zenodo.org/badge/latestdoi/5822/mne-tools/mne-python
 
+.. |MNE| image:: http://mne-tools.github.io/dev/_static/mne_logo.png
+.. _MNE: https://mne-tools.github.io
+
 `MNE-Python <http://mne-tools.github.io/>`_
 =======================================================
 
@@ -27,7 +35,7 @@ development environment .
 Get more information
 ^^^^^^^^^^^^^^^^^^^^
 
-If you're unfamiliar with MNE or MNE-Python, you can visit the
+If you're unfamiliar with MNE, you can visit the
 `MNE homepage <http://mne-tools.github.io/>`_ for full user documentation.
 
 Get the latest code
@@ -42,7 +50,7 @@ To get the latest code using `git <https://git-scm.com/>`_, simply type:
 If you don't have git installed, you can download a
 `zip of the latest code <https://github.com/mne-tools/mne-python/archive/master.zip>`_.
 
-Install mne-python
+Install MNE-python
 ^^^^^^^^^^^^^^^^^^
 
 As with most Python packages, to install the latest stable version of
@@ -78,10 +86,10 @@ to install the NVIDIA CUDA SDK, pycuda, and scikits.cuda. See the
 `getting started page <http://mne-tools.github.io/stable/getting_started.html>`_
 for more information.
 
-Contribute to mne-python
+Contribute to MNE-python
 ^^^^^^^^^^^^^^^^^^^^^^^^
 
-Please see the documentation on the mne-python homepage:
+Please see the documentation on the MNE-python homepage:
 
 http://martinos.org/mne/contributing.html
 
diff --git a/appveyor.yml b/appveyor.yml
index 3486ee1..bd2e19d 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,8 +1,8 @@
 environment:
   global:
       PYTHON: "C:\\conda"
-      MINICONDA_VERSION: "latest"
-      CONDA_DEPENDENCIES: "setuptools numpy scipy matplotlib scikit-learn nose mayavi pandas h5py PIL patsy pyside"
+      CONDA_ENVIRONMENT: "environment.yml"
+      PIP_DEPENDENCIES: "codecov pytest-faulthandler"
   matrix:
       - PYTHON_VERSION: "2.7"
         PYTHON_ARCH: "64"
@@ -12,9 +12,9 @@ install:
   - "powershell ci-helpers/appveyor/install-miniconda.ps1"
   - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
   - "activate test"
-  - "pip install nose-timer nibabel nitime"
+  - "pip uninstall -yq mne"
   - "python setup.py develop"
-  - "SET MNE_SKIP_NETWORK_TESTS=1"
+  - "python -c \"import mne; print(mne.sys_info())\""
   - "SET MNE_FORCE_SERIAL=true"  # otherwise joblib will bomb
   - "SET MNE_LOGGING_LEVEL=warning"
   - "python -c \"import mne; mne.datasets.testing.data_path()\""
@@ -23,4 +23,7 @@ build: false  # Not a C# project, build stuff at the test step instead.
 
 test_script:
   # Run the project tests, but (sadly) exclude ones that take a long time
-  - "nosetests --verbosity=2 -a !slow_test --with-timer --timer-top-n=20 --timer-ok 5 --timer-warning 15"
+  - "py.test -m \"not ultraslowtest\" mne --cov=mne"
+
+on_success:
+  - "codecov"
diff --git a/circle.yml b/circle.yml
index 251b41f..394d20c 100644
--- a/circle.yml
+++ b/circle.yml
@@ -4,10 +4,23 @@ machine:
     PATH: "/home/ubuntu/miniconda/envs/circleenv/bin:/home/ubuntu/miniconda/bin:$PATH"
     DISPLAY: ":99.0"
 
+# We should really build merged versions like on Travis, not the individual branches
+# Adapted from:
+# https://gist.github.com/amacneil/f14db753919e0af2d7d2f5a8da7fce65
+checkout:
+  post:
+    - echo $(git log -1 --pretty=%B) > gitlog.txt
+    - echo ${CI_PULL_REQUEST//*pull\//} > merge.txt
+    - if [[ $(cat merge.txt) != "" ]]; then
+        echo "Merging $(cat merge.txt)";
+        git pull --ff-only origin "refs/pull/$(cat merge.txt)/merge";
+      fi
+
 dependencies:
   cache_directories:
     - "~/miniconda"
     - "~/.mne"
+    - "~/mne_data/HF_SEF"
     - "~/mne_data/MNE-sample-data"
     - "~/mne_data/MNE-testing-data"
     - "~/mne_data/MNE-misc-data"
@@ -16,6 +29,8 @@ dependencies:
     - "~/mne_data/MNE-brainstorm-data"
     - "~/mne_data/MEGSIM"
     - "~/mne_data/MNE-eegbci-data"
+    - "~/mne_data/mTRF_1.5"
+    - "~/mne_data/MNE-fieldtrip_cmc-data"
     - "~/mne-tools.github.io"
   # Various dependencies
   pre:
@@ -32,58 +47,96 @@ dependencies:
         chmod +x ~/miniconda.sh;
         ~/miniconda.sh -b -p /home/ubuntu/miniconda;
         conda update --yes --quiet conda;
-        conda create -n circleenv --yes pip python=2.7 pip;
-        sed -i "s/ENABLE_USER_SITE = .*/ENABLE_USER_SITE = False/g" /home/ubuntu/miniconda/envs/circleenv/lib/python2.7/site.py;
       else
         echo "Conda already set up.";
       fi
-    - conda install -n circleenv --yes numpy scipy scikit-learn mayavi matplotlib sphinx pillow six IPython pandas;
+    - if ! conda env list | grep circleenv; then
+        conda create -n circleenv --yes pip python=2.7 pip;
+        sed -i "s/ENABLE_USER_SITE = .*/ENABLE_USER_SITE = False/g" /home/ubuntu/miniconda/envs/circleenv/lib/python2.7/site.py;
+      else
+        echo "Conda env already set up.";
+      fi;
+    - conda install -n circleenv --yes numpy scipy scikit-learn matplotlib sphinx pillow six IPython pandas mayavi nose;
     - ls -al /home/ubuntu/miniconda;
     - ls -al /home/ubuntu/miniconda/bin;
     - echo $PATH;
     - echo $CIRCLE_BRANCH
     - which python;
     - which pip;
+    - cat /home/ubuntu/miniconda/envs/circleenv/lib/python2.7/site.py | grep "ENABLE_USER_SITE"
     - pip install --upgrade pyface;
+    - pip install --upgrade git+https://github.com/enthought/mayavi.git
     - git clone https://github.com/sphinx-gallery/sphinx-gallery.git;
     - cd sphinx-gallery && pip install -r requirements.txt && python setup.py develop;
-    - pip install sphinx_bootstrap_theme git+git://github.com/nipy/PySurfer.git nilearn neo numpydoc;
+    - pip install sphinx_bootstrap_theme pysurfer nilearn neo numpydoc;
 
   override:
     # Figure out if we should run a full, pattern, or noplot version
     - cd /home/ubuntu/mne-python && python setup.py develop;
-    - git branch -a
-    - PATTERN="";
-      if [ "$CIRCLE_BRANCH" == "master" ] || [[ `git log -1 --pretty=%B` == *"[circle full]"* ]]; then
+    - if ! git remote -v | grep upstream ; then git remote add upstream git://github.com/mne-tools/mne-python.git; fi;
+    - git fetch upstream;
+    - git branch -a;
+    - mkdir -p ~/mne_data;
+    - if [[ $(cat gitlog.txt) == *"[circle front]"* ]]; then
+        PATTERN="plot_mne_dspm_source_localization.py\|plot_receptive_field.py\|plot_mne_inverse_label_connectivity.py\|plot_sensors_decoding.py\|plot_stats_cluster_spatio_temporal.py\|plot_visualize_evoked.py\|";
+      else
+        PATTERN="";
+      fi;
+      if [ "$CIRCLE_BRANCH" == "master" ] || [[ $(cat gitlog.txt) == *"[circle full]"* ]]; then
         echo html_dev > build.txt;
-      elif [ "$CIRCLE_BRANCH" == "maint/0.13" ]; then
+      elif [ "$CIRCLE_BRANCH" == "maint/0.15" ]; then
         echo html_stable > build.txt;
       else
-        FNAMES=$(git diff --name-only $CIRCLE_BRANCH $(git merge-base $CIRCLE_BRANCH origin/master));
+        FNAMES=$(git diff --name-only $CIRCLE_BRANCH $(git merge-base $CIRCLE_BRANCH upstream/master));
         echo FNAMES="$FNAMES";
         for FNAME in $FNAMES; do
           if [[ `expr match $FNAME "\(tutorials\|examples\)/.*plot_.*\.py"` ]] ; then
             echo "Checking example $FNAME ...";
             PATTERN=`basename $FNAME`"\\|"$PATTERN;
             if [[ $(cat $FNAME | grep -x ".*datasets.*sample.*" | wc -l) -gt 0 ]]; then
-              python -c "import mne; print(mne.datasets.sample.data_path())";
+              python -c "import mne; print(mne.datasets.sample.data_path(update_path=True))";
+            fi;
+            if [[ $(cat $FNAME | grep -x ".*datasets.*spm_face.*" | wc -l) -gt 0 ]]; then
+              python -c "import mne; print(mne.datasets.spm_face.data_path(update_path=True))";
+            fi;
+            if [[ $(cat $FNAME | grep -x ".*datasets.*somato.*" | wc -l) -gt 0 ]]; then
+              python -c "import mne; print(mne.datasets.somato.data_path(update_path=True))";
+            fi;
+            if [[ $(cat $FNAME | grep -x ".*datasets.*eegbci.*" | wc -l) -gt 0 ]]; then
+              python -c "import mne; print(mne.datasets.eegbci.load_data(1, [6, 10, 14], update_path=True))";
+            fi;
+            if [[ $(cat $FNAME | grep -x ".*datasets.*hf_sef.*" | wc -l) -gt 0 ]]; then
+              python -c "import mne; print(mne.datasets.hf_sef.data_path(update_path=True))";
             fi;
             if [[ $(cat $FNAME | grep -x ".*brainstorm.*bst_auditory.*" | wc -l) -gt 0 ]]; then
-              python -c "import mne; print(mne.datasets.brainstorm.bst_auditory.data_path())" --accept-brainstorm-license;
+              python -c "import mne; print(mne.datasets.brainstorm.bst_auditory.data_path(update_path=True))" --accept-brainstorm-license;
             fi;
             if [[ $(cat $FNAME | grep -x ".*brainstorm.*bst_raw.*" | wc -l) -gt 0 ]]; then
-              python -c "import mne; print(mne.datasets.brainstorm.bst_raw.data_path())" --accept-brainstorm-license;
+              python -c "import mne; print(mne.datasets.brainstorm.bst_raw.data_path(update_path=True))" --accept-brainstorm-license;
             fi;
             if [[ $(cat $FNAME | grep -x ".*brainstorm.*bst_phantom_ctf.*" | wc -l) -gt 0 ]]; then
-              python -c "import mne; print(mne.datasets.brainstorm.bst_phantom_ctf.data_path())" --accept-brainstorm-license;
+              python -c "import mne; print(mne.datasets.brainstorm.bst_phantom_ctf.data_path(update_path=True))" --accept-brainstorm-license;
             fi;
             if [[ $(cat $FNAME | grep -x ".*brainstorm.*bst_phantom_elekta.*" | wc -l) -gt 0 ]]; then
-              python -c "import mne; print(mne.datasets.brainstorm.bst_phantom_elekta.data_path())" --accept-brainstorm-license;
+              python -c "import mne; print(mne.datasets.brainstorm.bst_phantom_elekta.data_path(update_path=True))" --accept-brainstorm-license;
             fi;
             if [[ $(cat $FNAME | grep -x ".*datasets.*megsim.*" | wc -l) -gt 0 ]]; then
               python -c "import mne; print(mne.datasets.megsim.load_data(condition='visual', data_format='single-trial', data_type='simulation', update_path=True))";
               python -c "import mne; print(mne.datasets.megsim.load_data(condition='visual', data_format='raw', data_type='experimental', update_path=True))";
             fi;
+            if [[ $(cat $FNAME | grep -x ".*datasets.*hcp_mmp_parcellation.*" | wc -l) -gt 0 ]]; then
+              python -c "import mne; print(mne.datasets.sample.data_path(update_path=True))";
+              SUBJECTS_DIR=~/mne_data/MNE-sample-data/subjects python -c "import mne; print(mne.datasets.fetch_hcp_mmp_parcellation())" --accept-hcpmmp-license;
+            fi;
+            if [[ $(cat $FNAME | grep -x ".*datasets.*misc.*" | wc -l) -gt 0 ]]; then
+              python -c "import mne; print(mne.datasets.misc.data_path(update_path=True))";
+            fi;
+            if [[ $(cat $FNAME | grep -x ".*datasets.*mtrf.*" | wc -l) -gt 0 ]]; then
+              python -c "import mne; print(mne.datasets.mtrf.data_path(update_path=True))";
+            fi;
+            if [[ $(cat $FNAME | grep -x ".*datasets.*fieldtrip_cmc.*" | wc -l) -gt 0 ]]; then
+              python -c "import mne; print(mne.datasets.fieldtrip_cmc.data_path(update_path=True))";
+            fi;
           fi;
         done;
         echo PATTERN="$PATTERN";
@@ -97,10 +150,9 @@ dependencies:
       fi;
       echo "$PATTERN" > pattern.txt;
     - echo BUILD="$(cat build.txt)"
-    - mkdir -p ~/mne_data;
     - ls -al ~/mne_data;
     - if [[ $(cat build.txt) == "html_dev" ]] || [[ $(cat build.txt) == "html_stable" ]]; then
-        python -c "import mne; mne.datasets._download_all_example_data()";
+        SUBJECTS_DIR=/home/ubuntu/mne_data/MNE-sample-data/subjects python -c "import mne; mne.datasets._download_all_example_data()";
       fi;
     - python -c "import mne; mne.sys_info()";
     - >
@@ -111,16 +163,18 @@ dependencies:
 
 test:
   override:
-    - if [[ $(cat build.txt) == "html_dev-noplot" ]]; then cd doc && make html_dev-noplot; elif [[ $(cat build.txt) == "html_dev-pattern" ]]; then cd doc && PATTERN=$(cat ../pattern.txt) make html_dev-pattern; else make test-doc; cd doc; make $(cat ../build.txt); fi:
+    # Mayavi can bomb on exit (yay!), turning a successful build into a failure.
+    # We thus tee into a log (which returns 0) and then parse afterward for
+    # what immediately precedes the "Build finished." line.
+    - if [[ $(cat build.txt) == "html_dev-noplot" ]]; then cd doc && make html_dev-noplot | tee $CIRCLE_ARTIFACTS/log.txt; elif [[ $(cat build.txt) == "html_dev-pattern" ]]; then cd doc && PATTERN=$(cat ../pattern.txt) make html_dev-pattern | tee $CIRCLE_ARTIFACTS/log.txt; else make test-doc; cd doc; make $(cat ../build.txt) | tee $CIRCLE_ARTIFACTS/log.txt; fi:
           timeout: 1500
-
-general:
-  # branches:
-  #   only:
-  #     - master
-  # Open the doc to the API
-  artifacts:
-    - "doc/_build/html"
+    - tail $CIRCLE_ARTIFACTS/log.txt | grep -E "auto_tutorials\s*\[100%\]\s*plot_|Build finished"
+  post:
+    - if [[ $(cat build.txt) == "html_stable" ]]; then
+        cp -a doc/_build/html_stable $CIRCLE_ARTIFACTS/;
+      else
+        cp -a doc/_build/html $CIRCLE_ARTIFACTS/;
+      fi;
 
 deployment:
   production:
@@ -129,13 +183,13 @@ deployment:
       - git config --global user.email "circle at mne.com"
       - git config --global user.name "Circle Ci"
       - cd ../mne-tools.github.io && git checkout master && git pull origin master
-      - cd doc/_build/html && cp -rf * ~/mne-tools.github.io/dev
+      - cd doc/_build && rm -Rf ~/mne-tools.github.io/dev && cp -a html ~/mne-tools.github.io/dev
       - cd ../mne-tools.github.io && git add -A && git commit -m 'Automated update of dev docs.' && git push origin master
   stable:
-    branch: maint/0.13
+    branch: maint/0.15
     commands:
       - git config --global user.email "circle at mne.com"
       - git config --global user.name "Circle Ci"
       - cd ../mne-tools.github.io && git checkout master && git pull origin master
-      - cd doc/_build/html_stable && cp -rf * ~/mne-tools.github.io/stable
+      - cd doc/_build && rm -Rf ~/mne-tools.github.io/stable && cp -a html_stable ~/mne-tools.github.io/stable
       - cd ../mne-tools.github.io && git add -A && git commit -m 'Automated update of stable docs.' && git push origin master
diff --git a/doc/Makefile b/doc/Makefile
index 6bb71a3..eafc701 100644
--- a/doc/Makefile
+++ b/doc/Makefile
@@ -39,28 +39,33 @@ clean:
 	-rm -rf *.nii.gz
 
 html_stable:
-	$(SPHINXBUILD) -D raise_gallery=1 -b html $(ALLSPHINXOPTS) _build/html_stable
+	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) _build/html_stable
 	@echo
 	@echo "Build finished. The HTML pages are in _build/html_stable."
 
 html_dev:
-	BUILD_DEV_HTML=1 $(SPHINXBUILD) -D raise_gallery=1 -D abort_on_example_error=1 -b html $(ALLSPHINXOPTS) _build/html
+	BUILD_DEV_HTML=1 $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) _build/html
 	@echo
 	@echo "Build finished. The HTML pages are in _build/html"
 
 html_dev-pattern:
-	BUILD_DEV_HTML=1 $(SPHINXBUILD) -D plot_gallery=1 -D raise_gallery=1 -D abort_on_example_error=1 -D sphinx_gallery_conf.filename_pattern=$(PATTERN) -b html $(ALLSPHINXOPTS) _build/html
+	BUILD_DEV_HTML=1 $(SPHINXBUILD) -D plot_gallery=1 -D sphinx_gallery_conf.filename_pattern=$(PATTERN) -b html $(ALLSPHINXOPTS) _build/html
 	@echo
 	@echo "Build finished. The HTML pages are in _build/html"
 
-html_dev-noslow:
-	BUILD_DEV_HTML=1 $(SPHINXBUILD) -D plot_gallery=fast -b html $(ALLSPHINXOPTS) _build/html
-
 html_dev-noplot:
 	BUILD_DEV_HTML=1 $(SPHINXBUILD) -D plot_gallery=0 -b html $(ALLSPHINXOPTS) _build/html
 	@echo
 	@echo "Build finished. The HTML pages are in _build/html."
 
+html-noplot:
+	$(SPHINXBUILD) -D plot_gallery=0 -b html $(ALLSPHINXOPTS) _build/html_stable
+	@echo
+	@echo "Build finished. The HTML pages are in _build/html_stable."
+
+html_dev-front:
+	@PATTERN="\(plot_mne_dspm_source_localization.py\|plot_receptive_field.py\|plot_mne_inverse_label_connectivity.py\|plot_sensors_decoding.py\|plot_stats_cluster_spatio_temporal.py\|plot_visualize_evoked.py\)" make html_dev-pattern;
+
 dirhtml:
 	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) _build/dirhtml
 	@echo
diff --git a/doc/_static/copybutton.js b/doc/_static/copybutton.js
new file mode 100644
index 0000000..01ee2ba
--- /dev/null
+++ b/doc/_static/copybutton.js
@@ -0,0 +1,66 @@
+// Copyright 2014 PSF. Licensed under the PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
+// File originates from the cpython source found in Doc/tools/sphinxext/static/copybutton.js
+
+$(document).ready(function() {
+    /* Add a [>>>] button on the top-right corner of code samples to hide
+     * the >>> and ... prompts and the output and thus make the code
+     * copyable. */
+    var div = $('.highlight-python .highlight,' +
+                '.highlight-default .highlight,' +
+                '.highlight-python3 .highlight')
+    var pre = div.find('pre');
+
+    // get the styles from the current theme
+    pre.parent().parent().css('position', 'relative');
+    var hide_text = 'Hide the prompts and output';
+    var show_text = 'Show the prompts and output';
+    var border_width = pre.css('border-top-width');
+    var border_style = pre.css('border-top-style');
+    var border_color = pre.css('border-top-color');
+    var button_styles = {
+        'cursor':'pointer', 'position': 'absolute', 'top': '0', 'right': '0',
+        'border-color': border_color, 'border-style': border_style,
+        'border-width': border_width, 'color': border_color, 'text-size': '75%',
+        'font-family': 'monospace', 'padding-left': '0.2em', 'padding-right': '0.2em',
+        'border-radius': '0 3px 0 0'
+    }
+
+    // create and add the button to all the code blocks that contain >>>
+    div.each(function(index) {
+        var jthis = $(this);
+        if (jthis.find('.gp').length > 0) {
+            var button = $('<span class="copybutton">>>></span>');
+            button.css(button_styles)
+            button.attr('title', hide_text);
+            button.data('hidden', 'false');
+            jthis.prepend(button);
+        }
+        // tracebacks (.gt) contain bare text elements that need to be
+        // wrapped in a span to work with .nextUntil() (see later)
+        jthis.find('pre:has(.gt)').contents().filter(function() {
+            return ((this.nodeType == 3) && (this.data.trim().length > 0));
+        }).wrap('<span>');
+    });
+
+    // define the behavior of the button when it's clicked
+    $('.copybutton').click(function(e){
+        e.preventDefault();
+        var button = $(this);
+        if (button.data('hidden') === 'false') {
+            // hide the code output
+            button.parent().find('.go, .gp, .gt').hide();
+            button.next('pre').find('.gt').nextUntil('.gp, .go').css('visibility', 'hidden');
+            button.css('text-decoration', 'line-through');
+            button.attr('title', show_text);
+            button.data('hidden', 'true');
+        } else {
+            // show the code output
+            button.parent().find('.go, .gp, .gt').show();
+            button.next('pre').find('.gt').nextUntil('.gp, .go').css('visibility', 'visible');
+            button.css('text-decoration', 'none');
+            button.attr('title', hide_text);
+            button.data('hidden', 'false');
+        }
+    });
+});
+
diff --git a/doc/_static/favicon.ico b/doc/_static/favicon.ico
old mode 100755
new mode 100644
index 40dc94f..b0a1b57
Binary files a/doc/_static/favicon.ico and b/doc/_static/favicon.ico differ
diff --git a/doc/_static/flag-icon.css b/doc/_static/flag-icon.css
new file mode 100644
index 0000000..c2b4e5b
--- /dev/null
+++ b/doc/_static/flag-icon.css
@@ -0,0 +1,23 @@
+.flag-icon-background {
+  background-size: contain;
+  background-position: 50%;
+  background-repeat: no-repeat;
+}
+.flag-icon {
+  background-size: contain;
+  background-position: 50%;
+  background-repeat: no-repeat;
+  position: relative;
+  display: inline-block;
+  width: 1.33333333em;
+  line-height: 1em;
+}
+.flag-icon:before {
+  content: "\00a0";
+}
+.flag-icon-fr {
+  background-image: url(fr.svg);
+}
+.flag-icon-us {
+  background-image: url(us.svg);
+}
diff --git a/doc/_static/font-awesome.css b/doc/_static/font-awesome.css
new file mode 100644
index 0000000..c1ecf73
--- /dev/null
+++ b/doc/_static/font-awesome.css
@@ -0,0 +1,2337 @@
+/*!
+ *  Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome
+ *  License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
+ */
+/* FONT PATH
+ * -------------------------- */
+ at font-face {
+  font-family: 'FontAwesome';
+  src: url('./fontawesome-webfont.eot?v=4.7.0');
+  src: url('./fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'), url('./fontawesome-webfont.woff2?v=4.7.0') format('woff2'), url('./fontawesome-webfont.woff?v=4.7.0') format('woff'), url('./fontawesome-webfont.ttf?v=4.7.0') format('truetype');
+  font-weight: normal;
+  font-style: normal;
+}
+.fa {
+  display: inline-block;
+  font: normal normal normal 14px/1 FontAwesome;
+  font-size: inherit;
+  text-rendering: auto;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+/* makes the font 33% larger relative to the icon container */
+.fa-lg {
+  font-size: 1.33333333em;
+  line-height: 0.75em;
+  vertical-align: -15%;
+}
+.fa-2x {
+  font-size: 2em;
+}
+.fa-3x {
+  font-size: 3em;
+}
+.fa-4x {
+  font-size: 4em;
+}
+.fa-5x {
+  font-size: 5em;
+}
+.fa-fw {
+  width: 1.28571429em;
+  text-align: center;
+}
+.fa-ul {
+  padding-left: 0;
+  margin-left: 2.14285714em;
+  list-style-type: none;
+}
+.fa-ul > li {
+  position: relative;
+}
+.fa-li {
+  position: absolute;
+  left: -2.14285714em;
+  width: 2.14285714em;
+  top: 0.14285714em;
+  text-align: center;
+}
+.fa-li.fa-lg {
+  left: -1.85714286em;
+}
+.fa-border {
+  padding: .2em .25em .15em;
+  border: solid 0.08em #eeeeee;
+  border-radius: .1em;
+}
+.fa-pull-left {
+  float: left;
+}
+.fa-pull-right {
+  float: right;
+}
+.fa.fa-pull-left {
+  margin-right: .3em;
+}
+.fa.fa-pull-right {
+  margin-left: .3em;
+}
+/* Deprecated as of 4.4.0 */
+.pull-right {
+  float: right;
+}
+.pull-left {
+  float: left;
+}
+.fa.pull-left {
+  margin-right: .3em;
+}
+.fa.pull-right {
+  margin-left: .3em;
+}
+.fa-spin {
+  -webkit-animation: fa-spin 2s infinite linear;
+  animation: fa-spin 2s infinite linear;
+}
+.fa-pulse {
+  -webkit-animation: fa-spin 1s infinite steps(8);
+  animation: fa-spin 1s infinite steps(8);
+}
+ at -webkit-keyframes fa-spin {
+  0% {
+    -webkit-transform: rotate(0deg);
+    transform: rotate(0deg);
+  }
+  100% {
+    -webkit-transform: rotate(359deg);
+    transform: rotate(359deg);
+  }
+}
+ at keyframes fa-spin {
+  0% {
+    -webkit-transform: rotate(0deg);
+    transform: rotate(0deg);
+  }
+  100% {
+    -webkit-transform: rotate(359deg);
+    transform: rotate(359deg);
+  }
+}
+.fa-rotate-90 {
+  -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";
+  -webkit-transform: rotate(90deg);
+  -ms-transform: rotate(90deg);
+  transform: rotate(90deg);
+}
+.fa-rotate-180 {
+  -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";
+  -webkit-transform: rotate(180deg);
+  -ms-transform: rotate(180deg);
+  transform: rotate(180deg);
+}
+.fa-rotate-270 {
+  -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";
+  -webkit-transform: rotate(270deg);
+  -ms-transform: rotate(270deg);
+  transform: rotate(270deg);
+}
+.fa-flip-horizontal {
+  -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";
+  -webkit-transform: scale(-1, 1);
+  -ms-transform: scale(-1, 1);
+  transform: scale(-1, 1);
+}
+.fa-flip-vertical {
+  -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";
+  -webkit-transform: scale(1, -1);
+  -ms-transform: scale(1, -1);
+  transform: scale(1, -1);
+}
+:root .fa-rotate-90,
+:root .fa-rotate-180,
+:root .fa-rotate-270,
+:root .fa-flip-horizontal,
+:root .fa-flip-vertical {
+  filter: none;
+}
+.fa-stack {
+  position: relative;
+  display: inline-block;
+  width: 2em;
+  height: 2em;
+  line-height: 2em;
+  vertical-align: middle;
+}
+.fa-stack-1x,
+.fa-stack-2x {
+  position: absolute;
+  left: 0;
+  width: 100%;
+  text-align: center;
+}
+.fa-stack-1x {
+  line-height: inherit;
+}
+.fa-stack-2x {
+  font-size: 2em;
+}
+.fa-inverse {
+  color: #ffffff;
+}
+/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen
+   readers do not read off random characters that represent icons */
+.fa-glass:before {
+  content: "\f000";
+}
+.fa-music:before {
+  content: "\f001";
+}
+.fa-search:before {
+  content: "\f002";
+}
+.fa-envelope-o:before {
+  content: "\f003";
+}
+.fa-heart:before {
+  content: "\f004";
+}
+.fa-star:before {
+  content: "\f005";
+}
+.fa-star-o:before {
+  content: "\f006";
+}
+.fa-user:before {
+  content: "\f007";
+}
+.fa-film:before {
+  content: "\f008";
+}
+.fa-th-large:before {
+  content: "\f009";
+}
+.fa-th:before {
+  content: "\f00a";
+}
+.fa-th-list:before {
+  content: "\f00b";
+}
+.fa-check:before {
+  content: "\f00c";
+}
+.fa-remove:before,
+.fa-close:before,
+.fa-times:before {
+  content: "\f00d";
+}
+.fa-search-plus:before {
+  content: "\f00e";
+}
+.fa-search-minus:before {
+  content: "\f010";
+}
+.fa-power-off:before {
+  content: "\f011";
+}
+.fa-signal:before {
+  content: "\f012";
+}
+.fa-gear:before,
+.fa-cog:before {
+  content: "\f013";
+}
+.fa-trash-o:before {
+  content: "\f014";
+}
+.fa-home:before {
+  content: "\f015";
+}
+.fa-file-o:before {
+  content: "\f016";
+}
+.fa-clock-o:before {
+  content: "\f017";
+}
+.fa-road:before {
+  content: "\f018";
+}
+.fa-download:before {
+  content: "\f019";
+}
+.fa-arrow-circle-o-down:before {
+  content: "\f01a";
+}
+.fa-arrow-circle-o-up:before {
+  content: "\f01b";
+}
+.fa-inbox:before {
+  content: "\f01c";
+}
+.fa-play-circle-o:before {
+  content: "\f01d";
+}
+.fa-rotate-right:before,
+.fa-repeat:before {
+  content: "\f01e";
+}
+.fa-refresh:before {
+  content: "\f021";
+}
+.fa-list-alt:before {
+  content: "\f022";
+}
+.fa-lock:before {
+  content: "\f023";
+}
+.fa-flag:before {
+  content: "\f024";
+}
+.fa-headphones:before {
+  content: "\f025";
+}
+.fa-volume-off:before {
+  content: "\f026";
+}
+.fa-volume-down:before {
+  content: "\f027";
+}
+.fa-volume-up:before {
+  content: "\f028";
+}
+.fa-qrcode:before {
+  content: "\f029";
+}
+.fa-barcode:before {
+  content: "\f02a";
+}
+.fa-tag:before {
+  content: "\f02b";
+}
+.fa-tags:before {
+  content: "\f02c";
+}
+.fa-book:before {
+  content: "\f02d";
+}
+.fa-bookmark:before {
+  content: "\f02e";
+}
+.fa-print:before {
+  content: "\f02f";
+}
+.fa-camera:before {
+  content: "\f030";
+}
+.fa-font:before {
+  content: "\f031";
+}
+.fa-bold:before {
+  content: "\f032";
+}
+.fa-italic:before {
+  content: "\f033";
+}
+.fa-text-height:before {
+  content: "\f034";
+}
+.fa-text-width:before {
+  content: "\f035";
+}
+.fa-align-left:before {
+  content: "\f036";
+}
+.fa-align-center:before {
+  content: "\f037";
+}
+.fa-align-right:before {
+  content: "\f038";
+}
+.fa-align-justify:before {
+  content: "\f039";
+}
+.fa-list:before {
+  content: "\f03a";
+}
+.fa-dedent:before,
+.fa-outdent:before {
+  content: "\f03b";
+}
+.fa-indent:before {
+  content: "\f03c";
+}
+.fa-video-camera:before {
+  content: "\f03d";
+}
+.fa-photo:before,
+.fa-image:before,
+.fa-picture-o:before {
+  content: "\f03e";
+}
+.fa-pencil:before {
+  content: "\f040";
+}
+.fa-map-marker:before {
+  content: "\f041";
+}
+.fa-adjust:before {
+  content: "\f042";
+}
+.fa-tint:before {
+  content: "\f043";
+}
+.fa-edit:before,
+.fa-pencil-square-o:before {
+  content: "\f044";
+}
+.fa-share-square-o:before {
+  content: "\f045";
+}
+.fa-check-square-o:before {
+  content: "\f046";
+}
+.fa-arrows:before {
+  content: "\f047";
+}
+.fa-step-backward:before {
+  content: "\f048";
+}
+.fa-fast-backward:before {
+  content: "\f049";
+}
+.fa-backward:before {
+  content: "\f04a";
+}
+.fa-play:before {
+  content: "\f04b";
+}
+.fa-pause:before {
+  content: "\f04c";
+}
+.fa-stop:before {
+  content: "\f04d";
+}
+.fa-forward:before {
+  content: "\f04e";
+}
+.fa-fast-forward:before {
+  content: "\f050";
+}
+.fa-step-forward:before {
+  content: "\f051";
+}
+.fa-eject:before {
+  content: "\f052";
+}
+.fa-chevron-left:before {
+  content: "\f053";
+}
+.fa-chevron-right:before {
+  content: "\f054";
+}
+.fa-plus-circle:before {
+  content: "\f055";
+}
+.fa-minus-circle:before {
+  content: "\f056";
+}
+.fa-times-circle:before {
+  content: "\f057";
+}
+.fa-check-circle:before {
+  content: "\f058";
+}
+.fa-question-circle:before {
+  content: "\f059";
+}
+.fa-info-circle:before {
+  content: "\f05a";
+}
+.fa-crosshairs:before {
+  content: "\f05b";
+}
+.fa-times-circle-o:before {
+  content: "\f05c";
+}
+.fa-check-circle-o:before {
+  content: "\f05d";
+}
+.fa-ban:before {
+  content: "\f05e";
+}
+.fa-arrow-left:before {
+  content: "\f060";
+}
+.fa-arrow-right:before {
+  content: "\f061";
+}
+.fa-arrow-up:before {
+  content: "\f062";
+}
+.fa-arrow-down:before {
+  content: "\f063";
+}
+.fa-mail-forward:before,
+.fa-share:before {
+  content: "\f064";
+}
+.fa-expand:before {
+  content: "\f065";
+}
+.fa-compress:before {
+  content: "\f066";
+}
+.fa-plus:before {
+  content: "\f067";
+}
+.fa-minus:before {
+  content: "\f068";
+}
+.fa-asterisk:before {
+  content: "\f069";
+}
+.fa-exclamation-circle:before {
+  content: "\f06a";
+}
+.fa-gift:before {
+  content: "\f06b";
+}
+.fa-leaf:before {
+  content: "\f06c";
+}
+.fa-fire:before {
+  content: "\f06d";
+}
+.fa-eye:before {
+  content: "\f06e";
+}
+.fa-eye-slash:before {
+  content: "\f070";
+}
+.fa-warning:before,
+.fa-exclamation-triangle:before {
+  content: "\f071";
+}
+.fa-plane:before {
+  content: "\f072";
+}
+.fa-calendar:before {
+  content: "\f073";
+}
+.fa-random:before {
+  content: "\f074";
+}
+.fa-comment:before {
+  content: "\f075";
+}
+.fa-magnet:before {
+  content: "\f076";
+}
+.fa-chevron-up:before {
+  content: "\f077";
+}
+.fa-chevron-down:before {
+  content: "\f078";
+}
+.fa-retweet:before {
+  content: "\f079";
+}
+.fa-shopping-cart:before {
+  content: "\f07a";
+}
+.fa-folder:before {
+  content: "\f07b";
+}
+.fa-folder-open:before {
+  content: "\f07c";
+}
+.fa-arrows-v:before {
+  content: "\f07d";
+}
+.fa-arrows-h:before {
+  content: "\f07e";
+}
+.fa-bar-chart-o:before,
+.fa-bar-chart:before {
+  content: "\f080";
+}
+.fa-twitter-square:before {
+  content: "\f081";
+}
+.fa-facebook-square:before {
+  content: "\f082";
+}
+.fa-camera-retro:before {
+  content: "\f083";
+}
+.fa-key:before {
+  content: "\f084";
+}
+.fa-gears:before,
+.fa-cogs:before {
+  content: "\f085";
+}
+.fa-comments:before {
+  content: "\f086";
+}
+.fa-thumbs-o-up:before {
+  content: "\f087";
+}
+.fa-thumbs-o-down:before {
+  content: "\f088";
+}
+.fa-star-half:before {
+  content: "\f089";
+}
+.fa-heart-o:before {
+  content: "\f08a";
+}
+.fa-sign-out:before {
+  content: "\f08b";
+}
+.fa-linkedin-square:before {
+  content: "\f08c";
+}
+.fa-thumb-tack:before {
+  content: "\f08d";
+}
+.fa-external-link:before {
+  content: "\f08e";
+}
+.fa-sign-in:before {
+  content: "\f090";
+}
+.fa-trophy:before {
+  content: "\f091";
+}
+.fa-github-square:before {
+  content: "\f092";
+}
+.fa-upload:before {
+  content: "\f093";
+}
+.fa-lemon-o:before {
+  content: "\f094";
+}
+.fa-phone:before {
+  content: "\f095";
+}
+.fa-square-o:before {
+  content: "\f096";
+}
+.fa-bookmark-o:before {
+  content: "\f097";
+}
+.fa-phone-square:before {
+  content: "\f098";
+}
+.fa-twitter:before {
+  content: "\f099";
+}
+.fa-facebook-f:before,
+.fa-facebook:before {
+  content: "\f09a";
+}
+.fa-github:before {
+  content: "\f09b";
+}
+.fa-unlock:before {
+  content: "\f09c";
+}
+.fa-credit-card:before {
+  content: "\f09d";
+}
+.fa-feed:before,
+.fa-rss:before {
+  content: "\f09e";
+}
+.fa-hdd-o:before {
+  content: "\f0a0";
+}
+.fa-bullhorn:before {
+  content: "\f0a1";
+}
+.fa-bell:before {
+  content: "\f0f3";
+}
+.fa-certificate:before {
+  content: "\f0a3";
+}
+.fa-hand-o-right:before {
+  content: "\f0a4";
+}
+.fa-hand-o-left:before {
+  content: "\f0a5";
+}
+.fa-hand-o-up:before {
+  content: "\f0a6";
+}
+.fa-hand-o-down:before {
+  content: "\f0a7";
+}
+.fa-arrow-circle-left:before {
+  content: "\f0a8";
+}
+.fa-arrow-circle-right:before {
+  content: "\f0a9";
+}
+.fa-arrow-circle-up:before {
+  content: "\f0aa";
+}
+.fa-arrow-circle-down:before {
+  content: "\f0ab";
+}
+.fa-globe:before {
+  content: "\f0ac";
+}
+.fa-wrench:before {
+  content: "\f0ad";
+}
+.fa-tasks:before {
+  content: "\f0ae";
+}
+.fa-filter:before {
+  content: "\f0b0";
+}
+.fa-briefcase:before {
+  content: "\f0b1";
+}
+.fa-arrows-alt:before {
+  content: "\f0b2";
+}
+.fa-group:before,
+.fa-users:before {
+  content: "\f0c0";
+}
+.fa-chain:before,
+.fa-link:before {
+  content: "\f0c1";
+}
+.fa-cloud:before {
+  content: "\f0c2";
+}
+.fa-flask:before {
+  content: "\f0c3";
+}
+.fa-cut:before,
+.fa-scissors:before {
+  content: "\f0c4";
+}
+.fa-copy:before,
+.fa-files-o:before {
+  content: "\f0c5";
+}
+.fa-paperclip:before {
+  content: "\f0c6";
+}
+.fa-save:before,
+.fa-floppy-o:before {
+  content: "\f0c7";
+}
+.fa-square:before {
+  content: "\f0c8";
+}
+.fa-navicon:before,
+.fa-reorder:before,
+.fa-bars:before {
+  content: "\f0c9";
+}
+.fa-list-ul:before {
+  content: "\f0ca";
+}
+.fa-list-ol:before {
+  content: "\f0cb";
+}
+.fa-strikethrough:before {
+  content: "\f0cc";
+}
+.fa-underline:before {
+  content: "\f0cd";
+}
+.fa-table:before {
+  content: "\f0ce";
+}
+.fa-magic:before {
+  content: "\f0d0";
+}
+.fa-truck:before {
+  content: "\f0d1";
+}
+.fa-pinterest:before {
+  content: "\f0d2";
+}
+.fa-pinterest-square:before {
+  content: "\f0d3";
+}
+.fa-google-plus-square:before {
+  content: "\f0d4";
+}
+.fa-google-plus:before {
+  content: "\f0d5";
+}
+.fa-money:before {
+  content: "\f0d6";
+}
+.fa-caret-down:before {
+  content: "\f0d7";
+}
+.fa-caret-up:before {
+  content: "\f0d8";
+}
+.fa-caret-left:before {
+  content: "\f0d9";
+}
+.fa-caret-right:before {
+  content: "\f0da";
+}
+.fa-columns:before {
+  content: "\f0db";
+}
+.fa-unsorted:before,
+.fa-sort:before {
+  content: "\f0dc";
+}
+.fa-sort-down:before,
+.fa-sort-desc:before {
+  content: "\f0dd";
+}
+.fa-sort-up:before,
+.fa-sort-asc:before {
+  content: "\f0de";
+}
+.fa-envelope:before {
+  content: "\f0e0";
+}
+.fa-linkedin:before {
+  content: "\f0e1";
+}
+.fa-rotate-left:before,
+.fa-undo:before {
+  content: "\f0e2";
+}
+.fa-legal:before,
+.fa-gavel:before {
+  content: "\f0e3";
+}
+.fa-dashboard:before,
+.fa-tachometer:before {
+  content: "\f0e4";
+}
+.fa-comment-o:before {
+  content: "\f0e5";
+}
+.fa-comments-o:before {
+  content: "\f0e6";
+}
+.fa-flash:before,
+.fa-bolt:before {
+  content: "\f0e7";
+}
+.fa-sitemap:before {
+  content: "\f0e8";
+}
+.fa-umbrella:before {
+  content: "\f0e9";
+}
+.fa-paste:before,
+.fa-clipboard:before {
+  content: "\f0ea";
+}
+.fa-lightbulb-o:before {
+  content: "\f0eb";
+}
+.fa-exchange:before {
+  content: "\f0ec";
+}
+.fa-cloud-download:before {
+  content: "\f0ed";
+}
+.fa-cloud-upload:before {
+  content: "\f0ee";
+}
+.fa-user-md:before {
+  content: "\f0f0";
+}
+.fa-stethoscope:before {
+  content: "\f0f1";
+}
+.fa-suitcase:before {
+  content: "\f0f2";
+}
+.fa-bell-o:before {
+  content: "\f0a2";
+}
+.fa-coffee:before {
+  content: "\f0f4";
+}
+.fa-cutlery:before {
+  content: "\f0f5";
+}
+.fa-file-text-o:before {
+  content: "\f0f6";
+}
+.fa-building-o:before {
+  content: "\f0f7";
+}
+.fa-hospital-o:before {
+  content: "\f0f8";
+}
+.fa-ambulance:before {
+  content: "\f0f9";
+}
+.fa-medkit:before {
+  content: "\f0fa";
+}
+.fa-fighter-jet:before {
+  content: "\f0fb";
+}
+.fa-beer:before {
+  content: "\f0fc";
+}
+.fa-h-square:before {
+  content: "\f0fd";
+}
+.fa-plus-square:before {
+  content: "\f0fe";
+}
+.fa-angle-double-left:before {
+  content: "\f100";
+}
+.fa-angle-double-right:before {
+  content: "\f101";
+}
+.fa-angle-double-up:before {
+  content: "\f102";
+}
+.fa-angle-double-down:before {
+  content: "\f103";
+}
+.fa-angle-left:before {
+  content: "\f104";
+}
+.fa-angle-right:before {
+  content: "\f105";
+}
+.fa-angle-up:before {
+  content: "\f106";
+}
+.fa-angle-down:before {
+  content: "\f107";
+}
+.fa-desktop:before {
+  content: "\f108";
+}
+.fa-laptop:before {
+  content: "\f109";
+}
+.fa-tablet:before {
+  content: "\f10a";
+}
+.fa-mobile-phone:before,
+.fa-mobile:before {
+  content: "\f10b";
+}
+.fa-circle-o:before {
+  content: "\f10c";
+}
+.fa-quote-left:before {
+  content: "\f10d";
+}
+.fa-quote-right:before {
+  content: "\f10e";
+}
+.fa-spinner:before {
+  content: "\f110";
+}
+.fa-circle:before {
+  content: "\f111";
+}
+.fa-mail-reply:before,
+.fa-reply:before {
+  content: "\f112";
+}
+.fa-github-alt:before {
+  content: "\f113";
+}
+.fa-folder-o:before {
+  content: "\f114";
+}
+.fa-folder-open-o:before {
+  content: "\f115";
+}
+.fa-smile-o:before {
+  content: "\f118";
+}
+.fa-frown-o:before {
+  content: "\f119";
+}
+.fa-meh-o:before {
+  content: "\f11a";
+}
+.fa-gamepad:before {
+  content: "\f11b";
+}
+.fa-keyboard-o:before {
+  content: "\f11c";
+}
+.fa-flag-o:before {
+  content: "\f11d";
+}
+.fa-flag-checkered:before {
+  content: "\f11e";
+}
+.fa-terminal:before {
+  content: "\f120";
+}
+.fa-code:before {
+  content: "\f121";
+}
+.fa-mail-reply-all:before,
+.fa-reply-all:before {
+  content: "\f122";
+}
+.fa-star-half-empty:before,
+.fa-star-half-full:before,
+.fa-star-half-o:before {
+  content: "\f123";
+}
+.fa-location-arrow:before {
+  content: "\f124";
+}
+.fa-crop:before {
+  content: "\f125";
+}
+.fa-code-fork:before {
+  content: "\f126";
+}
+.fa-unlink:before,
+.fa-chain-broken:before {
+  content: "\f127";
+}
+.fa-question:before {
+  content: "\f128";
+}
+.fa-info:before {
+  content: "\f129";
+}
+.fa-exclamation:before {
+  content: "\f12a";
+}
+.fa-superscript:before {
+  content: "\f12b";
+}
+.fa-subscript:before {
+  content: "\f12c";
+}
+.fa-eraser:before {
+  content: "\f12d";
+}
+.fa-puzzle-piece:before {
+  content: "\f12e";
+}
+.fa-microphone:before {
+  content: "\f130";
+}
+.fa-microphone-slash:before {
+  content: "\f131";
+}
+.fa-shield:before {
+  content: "\f132";
+}
+.fa-calendar-o:before {
+  content: "\f133";
+}
+.fa-fire-extinguisher:before {
+  content: "\f134";
+}
+.fa-rocket:before {
+  content: "\f135";
+}
+.fa-maxcdn:before {
+  content: "\f136";
+}
+.fa-chevron-circle-left:before {
+  content: "\f137";
+}
+.fa-chevron-circle-right:before {
+  content: "\f138";
+}
+.fa-chevron-circle-up:before {
+  content: "\f139";
+}
+.fa-chevron-circle-down:before {
+  content: "\f13a";
+}
+.fa-html5:before {
+  content: "\f13b";
+}
+.fa-css3:before {
+  content: "\f13c";
+}
+.fa-anchor:before {
+  content: "\f13d";
+}
+.fa-unlock-alt:before {
+  content: "\f13e";
+}
+.fa-bullseye:before {
+  content: "\f140";
+}
+.fa-ellipsis-h:before {
+  content: "\f141";
+}
+.fa-ellipsis-v:before {
+  content: "\f142";
+}
+.fa-rss-square:before {
+  content: "\f143";
+}
+.fa-play-circle:before {
+  content: "\f144";
+}
+.fa-ticket:before {
+  content: "\f145";
+}
+.fa-minus-square:before {
+  content: "\f146";
+}
+.fa-minus-square-o:before {
+  content: "\f147";
+}
+.fa-level-up:before {
+  content: "\f148";
+}
+.fa-level-down:before {
+  content: "\f149";
+}
+.fa-check-square:before {
+  content: "\f14a";
+}
+.fa-pencil-square:before {
+  content: "\f14b";
+}
+.fa-external-link-square:before {
+  content: "\f14c";
+}
+.fa-share-square:before {
+  content: "\f14d";
+}
+.fa-compass:before {
+  content: "\f14e";
+}
+.fa-toggle-down:before,
+.fa-caret-square-o-down:before {
+  content: "\f150";
+}
+.fa-toggle-up:before,
+.fa-caret-square-o-up:before {
+  content: "\f151";
+}
+.fa-toggle-right:before,
+.fa-caret-square-o-right:before {
+  content: "\f152";
+}
+.fa-euro:before,
+.fa-eur:before {
+  content: "\f153";
+}
+.fa-gbp:before {
+  content: "\f154";
+}
+.fa-dollar:before,
+.fa-usd:before {
+  content: "\f155";
+}
+.fa-rupee:before,
+.fa-inr:before {
+  content: "\f156";
+}
+.fa-cny:before,
+.fa-rmb:before,
+.fa-yen:before,
+.fa-jpy:before {
+  content: "\f157";
+}
+.fa-ruble:before,
+.fa-rouble:before,
+.fa-rub:before {
+  content: "\f158";
+}
+.fa-won:before,
+.fa-krw:before {
+  content: "\f159";
+}
+.fa-bitcoin:before,
+.fa-btc:before {
+  content: "\f15a";
+}
+.fa-file:before {
+  content: "\f15b";
+}
+.fa-file-text:before {
+  content: "\f15c";
+}
+.fa-sort-alpha-asc:before {
+  content: "\f15d";
+}
+.fa-sort-alpha-desc:before {
+  content: "\f15e";
+}
+.fa-sort-amount-asc:before {
+  content: "\f160";
+}
+.fa-sort-amount-desc:before {
+  content: "\f161";
+}
+.fa-sort-numeric-asc:before {
+  content: "\f162";
+}
+.fa-sort-numeric-desc:before {
+  content: "\f163";
+}
+.fa-thumbs-up:before {
+  content: "\f164";
+}
+.fa-thumbs-down:before {
+  content: "\f165";
+}
+.fa-youtube-square:before {
+  content: "\f166";
+}
+.fa-youtube:before {
+  content: "\f167";
+}
+.fa-xing:before {
+  content: "\f168";
+}
+.fa-xing-square:before {
+  content: "\f169";
+}
+.fa-youtube-play:before {
+  content: "\f16a";
+}
+.fa-dropbox:before {
+  content: "\f16b";
+}
+.fa-stack-overflow:before {
+  content: "\f16c";
+}
+.fa-instagram:before {
+  content: "\f16d";
+}
+.fa-flickr:before {
+  content: "\f16e";
+}
+.fa-adn:before {
+  content: "\f170";
+}
+.fa-bitbucket:before {
+  content: "\f171";
+}
+.fa-bitbucket-square:before {
+  content: "\f172";
+}
+.fa-tumblr:before {
+  content: "\f173";
+}
+.fa-tumblr-square:before {
+  content: "\f174";
+}
+.fa-long-arrow-down:before {
+  content: "\f175";
+}
+.fa-long-arrow-up:before {
+  content: "\f176";
+}
+.fa-long-arrow-left:before {
+  content: "\f177";
+}
+.fa-long-arrow-right:before {
+  content: "\f178";
+}
+.fa-apple:before {
+  content: "\f179";
+}
+.fa-windows:before {
+  content: "\f17a";
+}
+.fa-android:before {
+  content: "\f17b";
+}
+.fa-linux:before {
+  content: "\f17c";
+}
+.fa-dribbble:before {
+  content: "\f17d";
+}
+.fa-skype:before {
+  content: "\f17e";
+}
+.fa-foursquare:before {
+  content: "\f180";
+}
+.fa-trello:before {
+  content: "\f181";
+}
+.fa-female:before {
+  content: "\f182";
+}
+.fa-male:before {
+  content: "\f183";
+}
+.fa-gittip:before,
+.fa-gratipay:before {
+  content: "\f184";
+}
+.fa-sun-o:before {
+  content: "\f185";
+}
+.fa-moon-o:before {
+  content: "\f186";
+}
+.fa-archive:before {
+  content: "\f187";
+}
+.fa-bug:before {
+  content: "\f188";
+}
+.fa-vk:before {
+  content: "\f189";
+}
+.fa-weibo:before {
+  content: "\f18a";
+}
+.fa-renren:before {
+  content: "\f18b";
+}
+.fa-pagelines:before {
+  content: "\f18c";
+}
+.fa-stack-exchange:before {
+  content: "\f18d";
+}
+.fa-arrow-circle-o-right:before {
+  content: "\f18e";
+}
+.fa-arrow-circle-o-left:before {
+  content: "\f190";
+}
+.fa-toggle-left:before,
+.fa-caret-square-o-left:before {
+  content: "\f191";
+}
+.fa-dot-circle-o:before {
+  content: "\f192";
+}
+.fa-wheelchair:before {
+  content: "\f193";
+}
+.fa-vimeo-square:before {
+  content: "\f194";
+}
+.fa-turkish-lira:before,
+.fa-try:before {
+  content: "\f195";
+}
+.fa-plus-square-o:before {
+  content: "\f196";
+}
+.fa-space-shuttle:before {
+  content: "\f197";
+}
+.fa-slack:before {
+  content: "\f198";
+}
+.fa-envelope-square:before {
+  content: "\f199";
+}
+.fa-wordpress:before {
+  content: "\f19a";
+}
+.fa-openid:before {
+  content: "\f19b";
+}
+.fa-institution:before,
+.fa-bank:before,
+.fa-university:before {
+  content: "\f19c";
+}
+.fa-mortar-board:before,
+.fa-graduation-cap:before {
+  content: "\f19d";
+}
+.fa-yahoo:before {
+  content: "\f19e";
+}
+.fa-google:before {
+  content: "\f1a0";
+}
+.fa-reddit:before {
+  content: "\f1a1";
+}
+.fa-reddit-square:before {
+  content: "\f1a2";
+}
+.fa-stumbleupon-circle:before {
+  content: "\f1a3";
+}
+.fa-stumbleupon:before {
+  content: "\f1a4";
+}
+.fa-delicious:before {
+  content: "\f1a5";
+}
+.fa-digg:before {
+  content: "\f1a6";
+}
+.fa-pied-piper-pp:before {
+  content: "\f1a7";
+}
+.fa-pied-piper-alt:before {
+  content: "\f1a8";
+}
+.fa-drupal:before {
+  content: "\f1a9";
+}
+.fa-joomla:before {
+  content: "\f1aa";
+}
+.fa-language:before {
+  content: "\f1ab";
+}
+.fa-fax:before {
+  content: "\f1ac";
+}
+.fa-building:before {
+  content: "\f1ad";
+}
+.fa-child:before {
+  content: "\f1ae";
+}
+.fa-paw:before {
+  content: "\f1b0";
+}
+.fa-spoon:before {
+  content: "\f1b1";
+}
+.fa-cube:before {
+  content: "\f1b2";
+}
+.fa-cubes:before {
+  content: "\f1b3";
+}
+.fa-behance:before {
+  content: "\f1b4";
+}
+.fa-behance-square:before {
+  content: "\f1b5";
+}
+.fa-steam:before {
+  content: "\f1b6";
+}
+.fa-steam-square:before {
+  content: "\f1b7";
+}
+.fa-recycle:before {
+  content: "\f1b8";
+}
+.fa-automobile:before,
+.fa-car:before {
+  content: "\f1b9";
+}
+.fa-cab:before,
+.fa-taxi:before {
+  content: "\f1ba";
+}
+.fa-tree:before {
+  content: "\f1bb";
+}
+.fa-spotify:before {
+  content: "\f1bc";
+}
+.fa-deviantart:before {
+  content: "\f1bd";
+}
+.fa-soundcloud:before {
+  content: "\f1be";
+}
+.fa-database:before {
+  content: "\f1c0";
+}
+.fa-file-pdf-o:before {
+  content: "\f1c1";
+}
+.fa-file-word-o:before {
+  content: "\f1c2";
+}
+.fa-file-excel-o:before {
+  content: "\f1c3";
+}
+.fa-file-powerpoint-o:before {
+  content: "\f1c4";
+}
+.fa-file-photo-o:before,
+.fa-file-picture-o:before,
+.fa-file-image-o:before {
+  content: "\f1c5";
+}
+.fa-file-zip-o:before,
+.fa-file-archive-o:before {
+  content: "\f1c6";
+}
+.fa-file-sound-o:before,
+.fa-file-audio-o:before {
+  content: "\f1c7";
+}
+.fa-file-movie-o:before,
+.fa-file-video-o:before {
+  content: "\f1c8";
+}
+.fa-file-code-o:before {
+  content: "\f1c9";
+}
+.fa-vine:before {
+  content: "\f1ca";
+}
+.fa-codepen:before {
+  content: "\f1cb";
+}
+.fa-jsfiddle:before {
+  content: "\f1cc";
+}
+.fa-life-bouy:before,
+.fa-life-buoy:before,
+.fa-life-saver:before,
+.fa-support:before,
+.fa-life-ring:before {
+  content: "\f1cd";
+}
+.fa-circle-o-notch:before {
+  content: "\f1ce";
+}
+.fa-ra:before,
+.fa-resistance:before,
+.fa-rebel:before {
+  content: "\f1d0";
+}
+.fa-ge:before,
+.fa-empire:before {
+  content: "\f1d1";
+}
+.fa-git-square:before {
+  content: "\f1d2";
+}
+.fa-git:before {
+  content: "\f1d3";
+}
+.fa-y-combinator-square:before,
+.fa-yc-square:before,
+.fa-hacker-news:before {
+  content: "\f1d4";
+}
+.fa-tencent-weibo:before {
+  content: "\f1d5";
+}
+.fa-qq:before {
+  content: "\f1d6";
+}
+.fa-wechat:before,
+.fa-weixin:before {
+  content: "\f1d7";
+}
+.fa-send:before,
+.fa-paper-plane:before {
+  content: "\f1d8";
+}
+.fa-send-o:before,
+.fa-paper-plane-o:before {
+  content: "\f1d9";
+}
+.fa-history:before {
+  content: "\f1da";
+}
+.fa-circle-thin:before {
+  content: "\f1db";
+}
+.fa-header:before {
+  content: "\f1dc";
+}
+.fa-paragraph:before {
+  content: "\f1dd";
+}
+.fa-sliders:before {
+  content: "\f1de";
+}
+.fa-share-alt:before {
+  content: "\f1e0";
+}
+.fa-share-alt-square:before {
+  content: "\f1e1";
+}
+.fa-bomb:before {
+  content: "\f1e2";
+}
+.fa-soccer-ball-o:before,
+.fa-futbol-o:before {
+  content: "\f1e3";
+}
+.fa-tty:before {
+  content: "\f1e4";
+}
+.fa-binoculars:before {
+  content: "\f1e5";
+}
+.fa-plug:before {
+  content: "\f1e6";
+}
+.fa-slideshare:before {
+  content: "\f1e7";
+}
+.fa-twitch:before {
+  content: "\f1e8";
+}
+.fa-yelp:before {
+  content: "\f1e9";
+}
+.fa-newspaper-o:before {
+  content: "\f1ea";
+}
+.fa-wifi:before {
+  content: "\f1eb";
+}
+.fa-calculator:before {
+  content: "\f1ec";
+}
+.fa-paypal:before {
+  content: "\f1ed";
+}
+.fa-google-wallet:before {
+  content: "\f1ee";
+}
+.fa-cc-visa:before {
+  content: "\f1f0";
+}
+.fa-cc-mastercard:before {
+  content: "\f1f1";
+}
+.fa-cc-discover:before {
+  content: "\f1f2";
+}
+.fa-cc-amex:before {
+  content: "\f1f3";
+}
+.fa-cc-paypal:before {
+  content: "\f1f4";
+}
+.fa-cc-stripe:before {
+  content: "\f1f5";
+}
+.fa-bell-slash:before {
+  content: "\f1f6";
+}
+.fa-bell-slash-o:before {
+  content: "\f1f7";
+}
+.fa-trash:before {
+  content: "\f1f8";
+}
+.fa-copyright:before {
+  content: "\f1f9";
+}
+.fa-at:before {
+  content: "\f1fa";
+}
+.fa-eyedropper:before {
+  content: "\f1fb";
+}
+.fa-paint-brush:before {
+  content: "\f1fc";
+}
+.fa-birthday-cake:before {
+  content: "\f1fd";
+}
+.fa-area-chart:before {
+  content: "\f1fe";
+}
+.fa-pie-chart:before {
+  content: "\f200";
+}
+.fa-line-chart:before {
+  content: "\f201";
+}
+.fa-lastfm:before {
+  content: "\f202";
+}
+.fa-lastfm-square:before {
+  content: "\f203";
+}
+.fa-toggle-off:before {
+  content: "\f204";
+}
+.fa-toggle-on:before {
+  content: "\f205";
+}
+.fa-bicycle:before {
+  content: "\f206";
+}
+.fa-bus:before {
+  content: "\f207";
+}
+.fa-ioxhost:before {
+  content: "\f208";
+}
+.fa-angellist:before {
+  content: "\f209";
+}
+.fa-cc:before {
+  content: "\f20a";
+}
+.fa-shekel:before,
+.fa-sheqel:before,
+.fa-ils:before {
+  content: "\f20b";
+}
+.fa-meanpath:before {
+  content: "\f20c";
+}
+.fa-buysellads:before {
+  content: "\f20d";
+}
+.fa-connectdevelop:before {
+  content: "\f20e";
+}
+.fa-dashcube:before {
+  content: "\f210";
+}
+.fa-forumbee:before {
+  content: "\f211";
+}
+.fa-leanpub:before {
+  content: "\f212";
+}
+.fa-sellsy:before {
+  content: "\f213";
+}
+.fa-shirtsinbulk:before {
+  content: "\f214";
+}
+.fa-simplybuilt:before {
+  content: "\f215";
+}
+.fa-skyatlas:before {
+  content: "\f216";
+}
+.fa-cart-plus:before {
+  content: "\f217";
+}
+.fa-cart-arrow-down:before {
+  content: "\f218";
+}
+.fa-diamond:before {
+  content: "\f219";
+}
+.fa-ship:before {
+  content: "\f21a";
+}
+.fa-user-secret:before {
+  content: "\f21b";
+}
+.fa-motorcycle:before {
+  content: "\f21c";
+}
+.fa-street-view:before {
+  content: "\f21d";
+}
+.fa-heartbeat:before {
+  content: "\f21e";
+}
+.fa-venus:before {
+  content: "\f221";
+}
+.fa-mars:before {
+  content: "\f222";
+}
+.fa-mercury:before {
+  content: "\f223";
+}
+.fa-intersex:before,
+.fa-transgender:before {
+  content: "\f224";
+}
+.fa-transgender-alt:before {
+  content: "\f225";
+}
+.fa-venus-double:before {
+  content: "\f226";
+}
+.fa-mars-double:before {
+  content: "\f227";
+}
+.fa-venus-mars:before {
+  content: "\f228";
+}
+.fa-mars-stroke:before {
+  content: "\f229";
+}
+.fa-mars-stroke-v:before {
+  content: "\f22a";
+}
+.fa-mars-stroke-h:before {
+  content: "\f22b";
+}
+.fa-neuter:before {
+  content: "\f22c";
+}
+.fa-genderless:before {
+  content: "\f22d";
+}
+.fa-facebook-official:before {
+  content: "\f230";
+}
+.fa-pinterest-p:before {
+  content: "\f231";
+}
+.fa-whatsapp:before {
+  content: "\f232";
+}
+.fa-server:before {
+  content: "\f233";
+}
+.fa-user-plus:before {
+  content: "\f234";
+}
+.fa-user-times:before {
+  content: "\f235";
+}
+.fa-hotel:before,
+.fa-bed:before {
+  content: "\f236";
+}
+.fa-viacoin:before {
+  content: "\f237";
+}
+.fa-train:before {
+  content: "\f238";
+}
+.fa-subway:before {
+  content: "\f239";
+}
+.fa-medium:before {
+  content: "\f23a";
+}
+.fa-yc:before,
+.fa-y-combinator:before {
+  content: "\f23b";
+}
+.fa-optin-monster:before {
+  content: "\f23c";
+}
+.fa-opencart:before {
+  content: "\f23d";
+}
+.fa-expeditedssl:before {
+  content: "\f23e";
+}
+.fa-battery-4:before,
+.fa-battery:before,
+.fa-battery-full:before {
+  content: "\f240";
+}
+.fa-battery-3:before,
+.fa-battery-three-quarters:before {
+  content: "\f241";
+}
+.fa-battery-2:before,
+.fa-battery-half:before {
+  content: "\f242";
+}
+.fa-battery-1:before,
+.fa-battery-quarter:before {
+  content: "\f243";
+}
+.fa-battery-0:before,
+.fa-battery-empty:before {
+  content: "\f244";
+}
+.fa-mouse-pointer:before {
+  content: "\f245";
+}
+.fa-i-cursor:before {
+  content: "\f246";
+}
+.fa-object-group:before {
+  content: "\f247";
+}
+.fa-object-ungroup:before {
+  content: "\f248";
+}
+.fa-sticky-note:before {
+  content: "\f249";
+}
+.fa-sticky-note-o:before {
+  content: "\f24a";
+}
+.fa-cc-jcb:before {
+  content: "\f24b";
+}
+.fa-cc-diners-club:before {
+  content: "\f24c";
+}
+.fa-clone:before {
+  content: "\f24d";
+}
+.fa-balance-scale:before {
+  content: "\f24e";
+}
+.fa-hourglass-o:before {
+  content: "\f250";
+}
+.fa-hourglass-1:before,
+.fa-hourglass-start:before {
+  content: "\f251";
+}
+.fa-hourglass-2:before,
+.fa-hourglass-half:before {
+  content: "\f252";
+}
+.fa-hourglass-3:before,
+.fa-hourglass-end:before {
+  content: "\f253";
+}
+.fa-hourglass:before {
+  content: "\f254";
+}
+.fa-hand-grab-o:before,
+.fa-hand-rock-o:before {
+  content: "\f255";
+}
+.fa-hand-stop-o:before,
+.fa-hand-paper-o:before {
+  content: "\f256";
+}
+.fa-hand-scissors-o:before {
+  content: "\f257";
+}
+.fa-hand-lizard-o:before {
+  content: "\f258";
+}
+.fa-hand-spock-o:before {
+  content: "\f259";
+}
+.fa-hand-pointer-o:before {
+  content: "\f25a";
+}
+.fa-hand-peace-o:before {
+  content: "\f25b";
+}
+.fa-trademark:before {
+  content: "\f25c";
+}
+.fa-registered:before {
+  content: "\f25d";
+}
+.fa-creative-commons:before {
+  content: "\f25e";
+}
+.fa-gg:before {
+  content: "\f260";
+}
+.fa-gg-circle:before {
+  content: "\f261";
+}
+.fa-tripadvisor:before {
+  content: "\f262";
+}
+.fa-odnoklassniki:before {
+  content: "\f263";
+}
+.fa-odnoklassniki-square:before {
+  content: "\f264";
+}
+.fa-get-pocket:before {
+  content: "\f265";
+}
+.fa-wikipedia-w:before {
+  content: "\f266";
+}
+.fa-safari:before {
+  content: "\f267";
+}
+.fa-chrome:before {
+  content: "\f268";
+}
+.fa-firefox:before {
+  content: "\f269";
+}
+.fa-opera:before {
+  content: "\f26a";
+}
+.fa-internet-explorer:before {
+  content: "\f26b";
+}
+.fa-tv:before,
+.fa-television:before {
+  content: "\f26c";
+}
+.fa-contao:before {
+  content: "\f26d";
+}
+.fa-500px:before {
+  content: "\f26e";
+}
+.fa-amazon:before {
+  content: "\f270";
+}
+.fa-calendar-plus-o:before {
+  content: "\f271";
+}
+.fa-calendar-minus-o:before {
+  content: "\f272";
+}
+.fa-calendar-times-o:before {
+  content: "\f273";
+}
+.fa-calendar-check-o:before {
+  content: "\f274";
+}
+.fa-industry:before {
+  content: "\f275";
+}
+.fa-map-pin:before {
+  content: "\f276";
+}
+.fa-map-signs:before {
+  content: "\f277";
+}
+.fa-map-o:before {
+  content: "\f278";
+}
+.fa-map:before {
+  content: "\f279";
+}
+.fa-commenting:before {
+  content: "\f27a";
+}
+.fa-commenting-o:before {
+  content: "\f27b";
+}
+.fa-houzz:before {
+  content: "\f27c";
+}
+.fa-vimeo:before {
+  content: "\f27d";
+}
+.fa-black-tie:before {
+  content: "\f27e";
+}
+.fa-fonticons:before {
+  content: "\f280";
+}
+.fa-reddit-alien:before {
+  content: "\f281";
+}
+.fa-edge:before {
+  content: "\f282";
+}
+.fa-credit-card-alt:before {
+  content: "\f283";
+}
+.fa-codiepie:before {
+  content: "\f284";
+}
+.fa-modx:before {
+  content: "\f285";
+}
+.fa-fort-awesome:before {
+  content: "\f286";
+}
+.fa-usb:before {
+  content: "\f287";
+}
+.fa-product-hunt:before {
+  content: "\f288";
+}
+.fa-mixcloud:before {
+  content: "\f289";
+}
+.fa-scribd:before {
+  content: "\f28a";
+}
+.fa-pause-circle:before {
+  content: "\f28b";
+}
+.fa-pause-circle-o:before {
+  content: "\f28c";
+}
+.fa-stop-circle:before {
+  content: "\f28d";
+}
+.fa-stop-circle-o:before {
+  content: "\f28e";
+}
+.fa-shopping-bag:before {
+  content: "\f290";
+}
+.fa-shopping-basket:before {
+  content: "\f291";
+}
+.fa-hashtag:before {
+  content: "\f292";
+}
+.fa-bluetooth:before {
+  content: "\f293";
+}
+.fa-bluetooth-b:before {
+  content: "\f294";
+}
+.fa-percent:before {
+  content: "\f295";
+}
+.fa-gitlab:before {
+  content: "\f296";
+}
+.fa-wpbeginner:before {
+  content: "\f297";
+}
+.fa-wpforms:before {
+  content: "\f298";
+}
+.fa-envira:before {
+  content: "\f299";
+}
+.fa-universal-access:before {
+  content: "\f29a";
+}
+.fa-wheelchair-alt:before {
+  content: "\f29b";
+}
+.fa-question-circle-o:before {
+  content: "\f29c";
+}
+.fa-blind:before {
+  content: "\f29d";
+}
+.fa-audio-description:before {
+  content: "\f29e";
+}
+.fa-volume-control-phone:before {
+  content: "\f2a0";
+}
+.fa-braille:before {
+  content: "\f2a1";
+}
+.fa-assistive-listening-systems:before {
+  content: "\f2a2";
+}
+.fa-asl-interpreting:before,
+.fa-american-sign-language-interpreting:before {
+  content: "\f2a3";
+}
+.fa-deafness:before,
+.fa-hard-of-hearing:before,
+.fa-deaf:before {
+  content: "\f2a4";
+}
+.fa-glide:before {
+  content: "\f2a5";
+}
+.fa-glide-g:before {
+  content: "\f2a6";
+}
+.fa-signing:before,
+.fa-sign-language:before {
+  content: "\f2a7";
+}
+.fa-low-vision:before {
+  content: "\f2a8";
+}
+.fa-viadeo:before {
+  content: "\f2a9";
+}
+.fa-viadeo-square:before {
+  content: "\f2aa";
+}
+.fa-snapchat:before {
+  content: "\f2ab";
+}
+.fa-snapchat-ghost:before {
+  content: "\f2ac";
+}
+.fa-snapchat-square:before {
+  content: "\f2ad";
+}
+.fa-pied-piper:before {
+  content: "\f2ae";
+}
+.fa-first-order:before {
+  content: "\f2b0";
+}
+.fa-yoast:before {
+  content: "\f2b1";
+}
+.fa-themeisle:before {
+  content: "\f2b2";
+}
+.fa-google-plus-circle:before,
+.fa-google-plus-official:before {
+  content: "\f2b3";
+}
+.fa-fa:before,
+.fa-font-awesome:before {
+  content: "\f2b4";
+}
+.fa-handshake-o:before {
+  content: "\f2b5";
+}
+.fa-envelope-open:before {
+  content: "\f2b6";
+}
+.fa-envelope-open-o:before {
+  content: "\f2b7";
+}
+.fa-linode:before {
+  content: "\f2b8";
+}
+.fa-address-book:before {
+  content: "\f2b9";
+}
+.fa-address-book-o:before {
+  content: "\f2ba";
+}
+.fa-vcard:before,
+.fa-address-card:before {
+  content: "\f2bb";
+}
+.fa-vcard-o:before,
+.fa-address-card-o:before {
+  content: "\f2bc";
+}
+.fa-user-circle:before {
+  content: "\f2bd";
+}
+.fa-user-circle-o:before {
+  content: "\f2be";
+}
+.fa-user-o:before {
+  content: "\f2c0";
+}
+.fa-id-badge:before {
+  content: "\f2c1";
+}
+.fa-drivers-license:before,
+.fa-id-card:before {
+  content: "\f2c2";
+}
+.fa-drivers-license-o:before,
+.fa-id-card-o:before {
+  content: "\f2c3";
+}
+.fa-quora:before {
+  content: "\f2c4";
+}
+.fa-free-code-camp:before {
+  content: "\f2c5";
+}
+.fa-telegram:before {
+  content: "\f2c6";
+}
+.fa-thermometer-4:before,
+.fa-thermometer:before,
+.fa-thermometer-full:before {
+  content: "\f2c7";
+}
+.fa-thermometer-3:before,
+.fa-thermometer-three-quarters:before {
+  content: "\f2c8";
+}
+.fa-thermometer-2:before,
+.fa-thermometer-half:before {
+  content: "\f2c9";
+}
+.fa-thermometer-1:before,
+.fa-thermometer-quarter:before {
+  content: "\f2ca";
+}
+.fa-thermometer-0:before,
+.fa-thermometer-empty:before {
+  content: "\f2cb";
+}
+.fa-shower:before {
+  content: "\f2cc";
+}
+.fa-bathtub:before,
+.fa-s15:before,
+.fa-bath:before {
+  content: "\f2cd";
+}
+.fa-podcast:before {
+  content: "\f2ce";
+}
+.fa-window-maximize:before {
+  content: "\f2d0";
+}
+.fa-window-minimize:before {
+  content: "\f2d1";
+}
+.fa-window-restore:before {
+  content: "\f2d2";
+}
+.fa-times-rectangle:before,
+.fa-window-close:before {
+  content: "\f2d3";
+}
+.fa-times-rectangle-o:before,
+.fa-window-close-o:before {
+  content: "\f2d4";
+}
+.fa-bandcamp:before {
+  content: "\f2d5";
+}
+.fa-grav:before {
+  content: "\f2d6";
+}
+.fa-etsy:before {
+  content: "\f2d7";
+}
+.fa-imdb:before {
+  content: "\f2d8";
+}
+.fa-ravelry:before {
+  content: "\f2d9";
+}
+.fa-eercast:before {
+  content: "\f2da";
+}
+.fa-microchip:before {
+  content: "\f2db";
+}
+.fa-snowflake-o:before {
+  content: "\f2dc";
+}
+.fa-superpowers:before {
+  content: "\f2dd";
+}
+.fa-wpexplorer:before {
+  content: "\f2de";
+}
+.fa-meetup:before {
+  content: "\f2e0";
+}
+.sr-only {
+  position: absolute;
+  width: 1px;
+  height: 1px;
+  padding: 0;
+  margin: -1px;
+  overflow: hidden;
+  clip: rect(0, 0, 0, 0);
+  border: 0;
+}
+.sr-only-focusable:active,
+.sr-only-focusable:focus {
+  position: static;
+  width: auto;
+  height: auto;
+  margin: 0;
+  overflow: visible;
+  clip: auto;
+}
diff --git a/doc/_static/fontawesome-webfont.eot b/doc/_static/fontawesome-webfont.eot
new file mode 100644
index 0000000..e9f60ca
Binary files /dev/null and b/doc/_static/fontawesome-webfont.eot differ
diff --git a/doc/_static/fontawesome-webfont.ttf b/doc/_static/fontawesome-webfont.ttf
new file mode 100644
index 0000000..35acda2
Binary files /dev/null and b/doc/_static/fontawesome-webfont.ttf differ
diff --git a/doc/_static/fontawesome-webfont.woff b/doc/_static/fontawesome-webfont.woff
new file mode 100644
index 0000000..400014a
Binary files /dev/null and b/doc/_static/fontawesome-webfont.woff differ
diff --git a/doc/_static/fontawesome-webfont.woff2 b/doc/_static/fontawesome-webfont.woff2
new file mode 100644
index 0000000..4d13fc6
Binary files /dev/null and b/doc/_static/fontawesome-webfont.woff2 differ
diff --git a/doc/_static/fr.svg b/doc/_static/fr.svg
new file mode 100644
index 0000000..b17c8ad
--- /dev/null
+++ b/doc/_static/fr.svg
@@ -0,0 +1,7 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="480" width="640" viewBox="0 0 640 480">
+  <g fill-rule="evenodd" stroke-width="1pt">
+    <path fill="#fff" d="M0 0h640v480H0z"/>
+    <path fill="#00267f" d="M0 0h213.337v480H0z"/>
+    <path fill="#f31830" d="M426.662 0H640v480H426.662z"/>
+  </g>
+</svg>
diff --git a/doc/_static/institutions.png b/doc/_static/institutions.png
index 931d724..e4ebef5 100644
Binary files a/doc/_static/institutions.png and b/doc/_static/institutions.png differ
diff --git a/doc/_static/mne_logo.png b/doc/_static/mne_logo.png
index 6f4e842..fa720bf 100644
Binary files a/doc/_static/mne_logo.png and b/doc/_static/mne_logo.png differ
diff --git a/doc/_static/mne_logo_small.png b/doc/_static/mne_logo_small.png
index 30652de..df55643 100644
Binary files a/doc/_static/mne_logo_small.png and b/doc/_static/mne_logo_small.png differ
diff --git a/doc/_static/style.css b/doc/_static/style.css
index a1da6d9..cf4b4a6 100644
--- a/doc/_static/style.css
+++ b/doc/_static/style.css
@@ -1,5 +1,10 @@
+html {
+  position: relative;
+  min-height: 100%;
+}
+
 body {
-    font-family: 'Open Sans', 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva', 'Verdana', sans-serif;
+    margin-bottom: 150px;
 }
 
 .navbar-version {
@@ -15,26 +20,6 @@ a:hover {
     color: rgb(102, 179, 255);
 }
 
-h2 a {
-    color: rgb(0,0,0);
-    text-decoration: none;
-}
-
-h1 {
-    font-weight: 1000;
-}
-
-h2 {
-    background-color: rgb(255,255,255);
-    font-size: 140%;
-    font-weight: 800;
-}
-
-h3 {
-    font-size: 110%;
-    font-weight: 600;
-}
-
 blockquote {
     font-size: 100% !important;
 }
@@ -45,26 +30,40 @@ code {
 }
 
 .devbar {
-    background-color: rgb(242, 80, 80);
-    color: white;
-    font-weight:bold;
     text-align: center;
-    padding: 10px;
-    min-width: 910px;
+    padding: 5px;
+    margin-bottom: 5px;
 }
 
-.devbar a {
-    color: rgb(255, 238, 0);
-    text-decoration: none;
+#navbar a:hover {
+    color: rgb(242, 80, 80);
 }
 
-.devbar a:hover {
+div.crop-wrapper {
+  width: 380px;
+  height: 190px;
+  overflow: hidden
+}
+
+a.button-front {
+    width: 32%;
+    height: 100%;
+    margin: 5px;
+    float:left;
+    background-color: #e7e7e7;
     color: black;
-    text-decoration: underline;
+    border: 1px solid;
+    font-size: larger;
+    display: flex;
+    align-items: center;
+    justify-content: center;
 }
 
-#navbar a:hover {
-    color: rgb(242, 80, 80);
+.anchor-doc {
+    position: relative;
+    padding-top: 200px !important;
+    display: none !important;
+
 }
 
 .note a {
@@ -108,12 +107,24 @@ blockquote {
 }
 
 /* OVERRIDES */
-.sphx-glr-download {
-    background-color: rgb(204, 230, 255);
-    border-radius: 10px;
-    border-style: solid;
-    border-color: rgb(143, 173, 204);
-    max-width: 55ex;
+div.sphx-glr-download a {
+  background-color: inherit;
+  background-image: inherit;
+  border-radius: inherit;
+  border: 1px solid #000;
+  color: inherit;
+  display: inherit;
+  /* Not valid in old browser, hence we keep the line above to override */
+  display: inherit;
+  font-weight: inherit;
+  padding: 0px;
+  text-align: inherit;
+}
+div.sphx-glr-download a:hover {
+  box-shadow: inset 0 1px 0 rgba(255,255,255,.1), 0 1px 5px rgba(0,0,0,.25);
+  text-decoration: none;
+  background-image: none;
+  background-color: inherit;
 }
 .highlight pre {
     background-color: rgb(242, 249, 255) !important;
@@ -136,3 +147,137 @@ dt:target code {
 .label {  /* Necessary for multiple refs, from bootstrap.min.css:7 */
     color: #2c3e50;
 }
+/* Override bootstrap.min.css to avoid .. container:: padding problems */
+.nosize {
+    width:  auto;
+}
+.nopad {
+    padding:  0px;
+}
+.halfpad {
+    margin-left: 1%;
+    margin-right: 1%;
+    width: 48%;
+    padding:  0px;
+    margin: 10px;
+    float: left;
+}
+/* our carousel */
+.chopper {
+    overflow: hidden;
+    height: 250px;
+}
+.carousel-image {
+    position: absolute;
+    width: 100%;
+    opacity: 1.0;
+    left: 50%;
+    top: 50%;
+    transform: translateY(-50%) translateX(-50%);
+}
+
+.cont{
+    width: 100%;
+    display: table;
+    border-spacing: 20px 5px;
+}
+.cont:before {
+    content: "";
+}
+.cont:after {
+    content: "";
+}
+.btn-cont {
+    display: table-cell;
+    width: 100%;
+}
+.footer {
+    position: absolute;
+    bottom: 0;
+    width: 100%;
+}
+.footer a {
+    font-weight: 700;
+}
+.support-front {
+    font-size: 10px;
+}
+.support-front ul{
+    list-style-type: none;
+}
+.ul-2col ul{
+    columns: 2;
+}
+.carousel-caption {
+    text-shadow: 0 1px 1px rgba(0,0,0,1.0);
+    background-color: rgba(0,0,0,0.5);
+    box-shadow: 0 0 5px rgba(0,0,0,0.5);
+}
+.carousel-caption p {
+    font-size: 16px;
+}
+.support-front ul {
+    padding-left: 15px;
+}
+.topmargin {
+    margin-top: 10px;
+}
+.bottommargin {
+    margin-bottom: 10px;
+}
+h4.list-group-item-heading {
+    font-size: 15px;
+}
+.table-like {
+    display: table;
+}
+.cell-like {
+    display: table-cell;
+    vertical-align: middle;
+    float: none;
+}
+.limitedwidth {
+    max-width: 1024px;
+    float: none;
+    margin: auto;
+}
+.tagline {
+    font-size: 20px;
+}
+/* Fix collapsing */
+ at media (max-width: 991px) {
+  .navbar-header {
+      float: none;
+  }
+  .navbar-left,.navbar-right {
+      float: none !important;
+  }
+  .navbar-toggle {
+      display: block;
+  }
+  .navbar-collapse {
+      border-top: 1px solid transparent;
+      box-shadow: inset 0 1px 0 rgba(255,255,255,0.1);
+  }
+  .navbar-fixed-top {
+      top: 0;
+      border-width: 0 0 1px;
+  }
+  .navbar-collapse.collapse {
+      display: none!important;
+  }
+  .navbar-nav {
+      float: none!important;
+      margin-top: 7.5px;
+  }
+  .navbar-nav>li {
+      float: none;
+  }
+  .navbar-nav>li>a {
+      padding-top: 10px;
+      padding-bottom: 10px;
+  }
+  .collapse.in{
+      display:block !important;
+  }
+}
\ No newline at end of file
diff --git a/doc/_static/us.svg b/doc/_static/us.svg
new file mode 100644
index 0000000..95e707b
--- /dev/null
+++ b/doc/_static/us.svg
@@ -0,0 +1,18 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="480" width="640" viewBox="0 0 640 480">
+  <g fill-rule="evenodd" transform="scale(.9375)">
+    <g stroke-width="1pt">
+      <path d="M0 0h972.81v39.385H0zm0 78.77h972.81v39.385H0zm0 78.77h972.81v39.385H0zm0 78.77h972.81v39.385H0zm0 78.77h972.81v39.385H0zm0 78.77h972.81v39.385H0zm0 78.77h972.81v39.385H0z" fill="#bd3d44"/>
+      <path d="M0 39.385h972.81V78.77H0zm0 78.77h972.81v39.385H0zm0 78.77h972.81v39.385H0zm0 78.77h972.81v39.385H0zm0 78.77h972.81v39.385H0zm0 78.77h972.81v39.385H0z" fill="#fff"/>
+    </g>
+    <path fill="#192f5d" d="M0 0h389.12v275.69H0z"/>
+    <g fill="#fff">
+      <path d="M32.427 11.8l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.27-6.734-9.268 6.734 3.54-10.896-9.269-6.735h11.457zm64.853 0l3.541 10.896h11.458l-9.27 6.735 3.541 10.896-9.27-6.734-9.268 6.734 3.54-10.896-9.269-6.735H93.74zm64.856 0l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.269-6.734-9.269 6.734 3.54-10.896-9.269-6.735h11.458zm64.852 0l3.54 10.896h11.457l-9.269 6.735 3.54 10.896-9.268-6.734-9.27 6.734 3.541-10.896-9.27-6.735h11.458zm64.855 0l3.54 10.896h11.458l-9.27 6.735  [...]
+      <g>
+        <path d="M32.427 177.217l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.27-6.734-9.268 6.734 3.54-10.896-9.269-6.735h11.457zm64.853 0l3.541 10.896h11.458l-9.27 6.735 3.541 10.896-9.27-6.734-9.268 6.734 3.54-10.896-9.269-6.735H93.74zm64.856 0l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.269-6.734-9.269 6.734 3.54-10.896-9.269-6.735h11.458zm64.852 0l3.54 10.896h11.457l-9.269 6.735 3.54 10.896-9.268-6.734-9.27 6.734 3.541-10.896-9.27-6.735h11.458zm64.855 0l3.54 10.896h11.458l-9.27 6 [...]
+      </g>
+      <g>
+        <path d="M32.427 232.356l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.27-6.734-9.268 6.734 3.54-10.896-9.269-6.735h11.457zm64.853 0l3.541 10.896h11.458l-9.27 6.735 3.541 10.896-9.27-6.734-9.268 6.734 3.54-10.896-9.269-6.735H93.74zm64.856 0l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.269-6.734-9.269 6.734 3.54-10.896-9.269-6.735h11.458zm64.852 0l3.54 10.896h11.457l-9.269 6.735 3.54 10.896-9.268-6.734-9.27 6.734 3.541-10.896-9.27-6.735h11.458zm64.855 0l3.54 10.896h11.458l-9.27 6 [...]
+      </g>
+    </g>
+  </g>
+</svg>
diff --git a/doc/_templates/layout.html b/doc/_templates/layout.html
index 290d0a6..4bc9cef 100755
--- a/doc/_templates/layout.html
+++ b/doc/_templates/layout.html
@@ -1,11 +1,8 @@
 {% extends "!layout.html" %}
 
-{# Custom CSS overrides #}
-{% set bootswatch_css_custom = ['_static/style.css'] %}
-
 {% block extrahead %}
 
-<link href='http://fonts.googleapis.com/css?family=Open+Sans:400italic,700italic,400,700' rel='stylesheet' type='text/css'>
+    <script type="text/javascript" src="{{ pathto('_static/copybutton.js', 1) }}"></script>
 
 {% if use_google_analytics|tobool %}
     <script type="text/javascript">
@@ -21,10 +18,15 @@
     </script>
 {% endif %}
 
+    <link rel="stylesheet" href="{{ pathto('_static/style.css', 1) }} " type="text/css" />
+    <link rel="stylesheet" href="{{ pathto('_static/font-awesome.css', 1) }}" type="text/css" />
+    <link rel="stylesheet" href="{{ pathto('_static/flag-icon.css', 1) }}" type="text/css" />
+
+
 {% if (use_twitter|tobool) or (use_media_buttons|tobool) %}
     <script type="text/javascript">
     !function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);
-    js.id=id;js.src="http://platform.twitter.com/widgets.js";
+    js.id=id;js.src="https://platform.twitter.com/widgets.js";
     fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");
     </script>
 {% endif %}
@@ -44,8 +46,8 @@
 
 {% block relbar1 %}
 {% if build_dev_html|tobool %}
-<div class="devbar">
-This documentation is for the development version ({{ release }}) - <a href="http://martinos.org/mne/stable">Stable version</a>
+<div class="row devbar alert alert-danger">
+This documentation is for <strong>development version {{ release }}</strong>.
 </div>
 {% endif %}
 
@@ -54,4 +56,25 @@ This documentation is for the development version ({{ release }}) - <a href="htt
 
 {# put the sidebar before the body #}
 {% block sidebar1 %}{{ sidebar() }}{% endblock %}
-{% block sidebar2 %}{% endblock %}
\ No newline at end of file
+{% block sidebar2 %}{% endblock %}
+
+{%- block footer %}
+<footer class="footer">
+  <div class="container"><img src="{{ pathto('_static/institutions.png', 1) }}" alt="Institutions"></div>
+  <div class="container">
+    <ul class="list-inline">
+      <li><a href="https://github.com/mne-tools/mne-python">GitHub</a></li>
+      <li>·</li>
+      <li><a href="https://mail.nmr.mgh.harvard.edu/mailman/listinfo/mne_analysis">Mailing list</a></li>
+      <li>·</li>
+      <li><a href="https://gitter.im/mne-tools/mne-python">Gitter</a></li>
+      <li>·</li>
+      <li><a href="whats_new.html">What's new</a></li>
+      <li>·</li>
+      <li><a href="faq.html#cite">Cite MNE</a></li>
+      <li class="pull-right"><a href="#">Back to top</a></li>
+    </ul>
+    <p>© Copyright {{ copyright }}.</p>
+  </div>
+</footer>
+{%- endblock %}
diff --git a/doc/_templates/navbar.html b/doc/_templates/navbar.html
new file mode 100644
index 0000000..e55260e
--- /dev/null
+++ b/doc/_templates/navbar.html
@@ -0,0 +1,20 @@
+{% extends "!navbar.html" %}
+
+{% block navbarsearch %}
+<div class="navbar-form navbar-right navbar-btn dropdown btn-group-sm" style="margin-left: 20px; margin-top: 5px; margin-bottom: 5px">
+  <button type="button" class="btn btn-{% if (build_dev_html|tobool) %}danger{% else %}primary{% endif %} navbar-btn dropdown-toggle" id="dropdownMenu1" data-toggle="dropdown">
+    v{{ release }}
+    <span class="caret"></span>
+  </button>
+  <ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
+    <li><a href="https://mne-tools.github.io/dev/index.html">Development</a></li>
+    <li><a href="https://mne-tools.github.io/stable/index.html">v0.15 (stable)</a></li>
+    <li><a href="https://mne-tools.github.io/0.14/index.html">v0.14</a></li>
+    <li><a href="https://mne-tools.github.io/0.13/index.html">v0.13</a></li>
+    <li><a href="https://mne-tools.github.io/0.12/index.html">v0.12</a></li>
+    <li><a href="https://mne-tools.github.io/0.11/index.html">v0.11</a></li>
+  </ul>
+</div>
+
+{{ super() }}
+{% endblock %}
diff --git a/doc/advanced_setup.rst b/doc/advanced_setup.rst
new file mode 100644
index 0000000..64cf017
--- /dev/null
+++ b/doc/advanced_setup.rst
@@ -0,0 +1,163 @@
+:orphan:
+
+.. include:: links.inc
+
+.. _advanced_setup:
+
+Advanced setup and troubleshooting
+----------------------------------
+
+.. contents:: Steps
+   :local:
+   :depth: 1
+
+.. _installing_master:
+
+Using the development version of MNE
+####################################
+
+It is possible to update your version of MNE between releases for
+bugfixes or new features.
+
+.. warning:: In between releases, function and class APIs can change without
+             warning.
+
+You can use ``pip`` for a one-time update:
+
+.. code-block:: console
+
+   $ pip install --upgrade --no-deps git+https://github.com/mne-tools/mne-python.git
+
+Or, if you prefer to be set up for frequent updates, you can use ``git`` directly:
+
+.. code-block:: console
+
+   $ git clone git://github.com/mne-tools/mne-python.git
+   $ cd mne-python
+   $ python setup.py develop
+
+A feature of ``python setup.py develop`` is that any changes made to
+the files (e.g., by updating to latest ``master``) will be reflected in
+``mne`` as soon as you restart your Python interpreter. So to update to
+the latest version of the ``master`` development branch, you can do:
+
+   .. code-block:: console
+
+   $ git pull origin master
+
+and MNE will be updated to have the latest changes.
+
+
+If you plan to contribute to MNE, please continue reading how to
+:ref:`contribute_to_mne`.
+
+.. _CUDA:
+
+CUDA
+####
+
+We have developed specialized routines to make use of
+`NVIDIA CUDA GPU processing <http://www.nvidia.com/object/cuda_home_new.html>`_
+to speed up some operations (e.g. FIR filtering) by up to 10x.
+If you want to use NVIDIA CUDA, you should install:
+
+1. `the NVIDIA toolkit on your system <https://developer.nvidia.com/cuda-downloads>`_
+2. `PyCUDA <http://wiki.tiker.net/PyCuda/Installation/>`_
+3. `skcuda <https://github.com/lebedov/scikits.cuda>`_
+
+For example, on Ubuntu 15.10, a combination of system packages and ``git``
+packages can be used to install the CUDA stack:
+
+.. code-block:: console
+
+    # install system packages for CUDA
+    $ sudo apt-get install nvidia-cuda-dev nvidia-modprobe
+    # install PyCUDA
+    $ git clone http://git.tiker.net/trees/pycuda.git
+    $ cd pycuda
+    $ ./configure.py --cuda-enable-gl
+    $ git submodule update --init
+    $ make -j 4
+    $ python setup.py install
+    # install skcuda
+    $ cd ..
+    $ git clone https://github.com/lebedov/scikit-cuda.git
+    $ cd scikit-cuda
+    $ python setup.py install
+
+To initialize mne-python cuda support, after installing these dependencies
+and running their associated unit tests (to ensure your installation is correct)
+you can run:
+
+.. code-block:: console
+
+    $ MNE_USE_CUDA=true MNE_LOGGING_LEVEL=info python -c "import mne; mne.cuda.init_cuda()"
+    Enabling CUDA with 1.55 GB available memory
+
+If you have everything installed correctly, you should see an INFO-level log
+message telling you your CUDA hardware's available memory. To have CUDA
+initialized on startup, you can do::
+
+    >>> mne.utils.set_config('MNE_USE_CUDA', 'true') # doctest: +SKIP
+
+You can test if MNE CUDA support is working by running the associated test:
+
+.. code-block:: console
+
+    $ pytest mne/tests/test_filter.py
+
+If ``MNE_USE_CUDA=true`` and all tests pass with none skipped, then
+MNE-Python CUDA support works.
+
+IPython / Jupyter notebooks
+###########################
+
+In Jupyter, we strongly recommend using the Qt matplotlib backend for
+fast and correct rendering:
+
+.. code-block:: console
+
+    $ ipython --matplotlib=qt
+
+On Linux, for example, QT is the only matplotlib backend for which 3D rendering
+will work correctly. On Mac OS X for other backends certain matplotlib
+functions might not work as expected.
+
+To take full advantage of MNE-Python's visualization capacities in combination
+with IPython notebooks and inline displaying, please explicitly add the
+following magic method invocation to your notebook or configure your notebook
+runtime accordingly::
+
+    In [1]: %matplotlib inline
+
+If you use another Python setup and you encounter some difficulties please
+report them on the MNE mailing list or on github to get assistance.
+
+Troubleshooting
+###############
+
+If you run into trouble when visualizing source estimates (or anything else
+using mayavi), you can try setting the ``ETS_TOOLKIT`` environment variable::
+
+    >>> import os
+    >>> os.environ['ETS_TOOLKIT'] = 'qt4'
+    >>> os.environ['QT_API'] = 'pyqt'
+
+This will tell Traits that we will use Qt with PyQt bindings.
+
+If you get an error saying::
+
+    ValueError: API 'QDate' has already been set to version 1
+
+you have run into a conflict with Traits. You can work around this by telling
+the interpreter to use QtGui and QtCore from pyface::
+
+    >>> from pyface.qt import QtGui, QtCore
+
+This line should be added before any imports from mne-python.
+
+To avoid Qt conflicts, you can also try using the ``wx`` backend via
+``conda install wxpython`` and ``ETS_TOOLKIT=wx``.
+
+For more information, see
+http://docs.enthought.com/mayavi/mayavi/building_applications.html.
diff --git a/doc/carousel.inc b/doc/carousel.inc
new file mode 100644
index 0000000..bfc0533
--- /dev/null
+++ b/doc/carousel.inc
@@ -0,0 +1,87 @@
+.. raw:: html
+
+    <div id="index_carousel_tn" class="thumbnail nopad">
+      <div id="examples_carousel" class="carousel slide nopad" data-ride="carousel">
+        <ol id="mne_index_indicators" class="carousel-indicators">
+          <li data-target="#examples_carousel" data-slide-to="0" class="active"></li>
+          <li data-target="#examples_carousel" data-slide-to="1" class=""></li>
+          <li data-target="#examples_carousel" data-slide-to="2" class=""></li>
+          <li data-target="#examples_carousel" data-slide-to="3" class=""></li>
+          <li data-target="#examples_carousel" data-slide-to="4" class=""></li>
+          <li data-target="#examples_carousel" data-slide-to="5" class=""></li>
+        </ol>
+        <!-- Carousel items -->
+        <div class="carousel-inner">
+
+          <div class="item active">
+            <div class="chopper container"><img src="_images/sphx_glr_plot_mne_dspm_source_localization_009.png" alt="dSPM" class="carousel-image"></div>
+            <div class="container">
+              <div class="carousel-caption">
+                <h2>Source estimation</h2>
+                <p>Distributed, sparse, mixed-norm, beamformers, dipole fitting, and more.</p>
+                <p><a class="btn btn-info btn-sm" href="auto_tutorials/plot_mne_dspm_source_localization.html" role="button">Check it out</a></p>
+              </div>
+            </div>
+          </div>
+
+          <div class="item">
+            <div class="chopper container"><img src="_images/sphx_glr_plot_stats_cluster_spatio_temporal_001.png" alt="Clusters" class="carousel-image"></div>
+            <div class="container">
+              <div class="carousel-caption">
+                <h2>Statistics</h2>
+                <p>Parametric and non-parametric, permutation tests and clustering.</p>
+                <p><a class="btn btn-info btn-sm" href="auto_tutorials/plot_stats_cluster_spatio_temporal.html" role="button">Check it out</a></p>
+              </div>
+            </div>
+          </div>
+
+          <div class="item">
+            <div class="chopper container"><img src="_images/sphx_glr_plot_sensors_decoding_004.png" alt="Decoding" class="carousel-image"></div>
+            <div class="container">
+              <div class="carousel-caption">
+                <h2>Machine learning</h2>
+                <p>Advanced decoding models including time generalization.</p>
+                <p><a class="btn btn-info btn-sm" href="auto_tutorials/plot_sensors_decoding.html" role="button">Check it out</a></p>
+              </div>
+            </div>
+          </div>
+
+          <div class="item">
+            <div class="chopper container"><img src="_images/sphx_glr_plot_receptive_field_0031.png" alt="STRF" class="carousel-image"></div>
+            <div class="container">
+              <div class="carousel-caption">
+                <h2>Encoding models</h2>
+                <p>Receptive field estimation with optional smoothness priors.</p>
+                <p><a class="btn btn-info btn-sm" href="auto_tutorials/plot_receptive_field.html" role="button">Check it out</a></p>
+              </div>
+            </div>
+          </div>
+
+          <div class="item">
+            <div class="chopper container"><img src="_images/sphx_glr_plot_mne_inverse_label_connectivity_001.png" alt="Connectivity" class="carousel-image"></div>
+            <div class="container">
+              <div class="carousel-caption">
+                <h2>Connectivity</h2>
+                <p>All-to-all spectral and effective connectivity measures.</p>
+                <p><a class="btn btn-info btn-sm" href="auto_examples/connectivity/plot_mne_inverse_label_connectivity.html" role="button">Check it out</a></p>
+              </div>
+            </div>
+          </div>
+
+          <div class="item">
+            <div class="chopper container"><img src="_images/sphx_glr_plot_visualize_evoked_009.png" alt="Visualization" class="carousel-image"></div>
+            <div class="container">
+              <div class="carousel-caption">
+                <h2>Data visualization.</h2>
+                <p>Explore your data from multiple perspectives.</p>
+                <p><a class="btn btn-info btn-sm" href="auto_tutorials/plot_visualize_evoked.html" role="button">Check it out</a></p>
+              </div>
+            </div>
+          </div>
+
+        </div>
+        <!-- Carousel nav -->
+        <a class="carousel-control left" href="#examples_carousel" role="button" data-slide="prev"><span class="glyphicon glyphicon-chevron-left"></span></a>
+        <a class="carousel-control right" href="#examples_carousel" role="button" data-slide="next"><span class="glyphicon glyphicon-chevron-right"></span></a>
+      </div>
+    </div>
diff --git a/doc/cite.rst b/doc/cite.rst
deleted file mode 100644
index 3c4e0df..0000000
--- a/doc/cite.rst
+++ /dev/null
@@ -1,17 +0,0 @@
-.. _cite:
-
-How to cite MNE
----------------
-
-If you use the implementations provided by the MNE software in your research, you should cite:
-
-    [1] A. Gramfort, M. Luessi, E. Larson, D. Engemann, D. Strohmeier, C. Brodbeck, L. Parkkonen, M. Hämäläinen, `MNE software for processing MEG and EEG data <http://www.ncbi.nlm.nih.gov/pubmed/24161808>`_, NeuroImage, Volume 86, 1 February 2014, Pages 446-460, ISSN 1053-8119, `[DOI] <http://dx.doi.org/10.1016/j.neuroimage.2013.10.027>`__
-
-If you use the Python code you should cite as well:
-
-    [2] A. Gramfort, M. Luessi, E. Larson, D. Engemann, D. Strohmeier, C. Brodbeck, R. Goj, M. Jas, T. Brooks, L. Parkkonen, M. Hämäläinen, `MEG and EEG data analysis with MNE-Python <http://journal.frontiersin.org/article/10.3389/fnins.2013.00267/abstract>`_, Frontiers in Neuroscience, Volume 7, 2013, ISSN 1662-453X, `[DOI] <http://dx.doi.org/10.3389/fnins.2013.00267>`__
-
-To cite specific versions of the software, you can use the DOIs provided by
-`Zenodo <https://zenodo.org/search?ln=en&p=mne-python>`_.
-
-You should as well cite the related method papers, some of which are listed in :ref:`ch_reading`.
diff --git a/doc/cited.rst b/doc/cited.rst
index b641148..562beae 100644
--- a/doc/cited.rst
+++ b/doc/cited.rst
@@ -1,112 +1,199 @@
+:orphan:
+
 .. _cited:
 
-Publications from MNE users
-===========================
+Publications by users
+=====================
 
-Papers citing MNE as extracted from Google Scholar (on February 29, 2016).
+Papers citing MNE (194) as extracted from Google Scholar (on February 03, 2017).
 
-1. `Linkage mapping with paralogs exposes regions of residual tetrasomic inheritance in chum salmon (Oncorhynchus keta). <http://onlinelibrary.wiley.com/doi/10.1111/1755-0998.12394/full>`_. RK Waples,LW Seeb,JE Seeb- Molecular ecology resources, 2016 - Wiley Online Library.
-2. `Examining the N400m in affectively negative sentences: A magnetoencephalography study. <http://onlinelibrary.wiley.com/doi/10.1111/psyp.12601/full>`_. L Parkes,C Perry, P Goodin - Psychophysiology, 2016 - Wiley Online Library.
-3. `Selective maintenance mechanisms of seen and unseen sensory features in the human brain. <http://www.biorxiv.org/content/early/2016/02/18/040030.abstract>`_. K Jean-Remi, N Pescetelli, S Dehaene - bioRxiv, 2016 - biorxiv.org.
-4. `Early-latency categorical speech sound representations in the left inferior frontal gyrus. <http://www.sciencedirect.com/science/article/pii/S1053811916000227>`_. J Alho, BM Green, PJC May,M Sams, H Tiitinen… - NeuroImage, 2016 - Elsevier.
-5. `Spectral and source structural development of mu and alpha rhythms from infancy through adulthood. <http://www.sciencedirect.com/science/article/pii/S1388245715001698>`_. SG Thorpe,EN Cannon,NA Fox- Clinical Neurophysiology, 2016 - Elsevier.
-6. `Previous exposure to intact speech increases intelligibility of its digitally degraded counterpart as a function of stimulus complexity. <http://www.sciencedirect.com/science/article/pii/S1053811915009398>`_. M Hakonen, PJC May,J Alho, P Alku, E Jokinen… - NeuroImage, 2016 - Elsevier.
-7. `Development of a Group Dynamic Functional Connectivity Pipeline for Magnetoencephalography Data and its Application to the Human Face Processing Network. <http://repository.unm.edu/handle/1928/31729>`_. P Lysne - 2016 - repository.unm.edu.
-8. `Test‐retest reliability of resting‐state magnetoencephalography power in sensor and source space. <http://onlinelibrary.wiley.com/doi/10.1002/hbm.23027/full>`_. MC Martín‐Buro,P Garcés,F Maestú- Human brain mapping, 2016 - Wiley Online Library.
-9. `The inhibition/excitation ratio related to task-induced oscillatory modulations during a working memory task: A multimodal-imaging study using MEG and MRS. <http://www.sciencedirect.com/science/article/pii/S1053811916000069>`_. Y Takei, K Fujihara, M Tagawa, N Hironaga,J Near… - NeuroImage, 2016 - Elsevier.
-10. `Interacting parallel pathways associate sounds with visual identity in auditory cortices. <http://www.sciencedirect.com/science/article/pii/S1053811915008599>`_. J Ahveninen, S Huang,SP Ahlfors,M Hämäläinen… - NeuroImage, 2016 - Elsevier.
-11. `Within-and between-session replicability of cognitive brain processes: An MEG study with an N-back task. <http://www.sciencedirect.com/science/article/pii/S0031938416300506>`_. L Ahonen, M Huotilainen, E Brattico - Physiology & behavior, 2016 - Elsevier.
-12. `Neuromagnetic evidence for hippocampal modulation of auditory processing. <http://www.sciencedirect.com/science/article/pii/S1053811915008034>`_. H Chatani, K Hagiwara, N Hironaga, K Ogata… - NeuroImage, 2016 - Elsevier.
-13. `Fast optimal transport averaging of neuroimaging data. <http://link.springer.com/chapter/10.1007/978-3-319-19992-4_20>`_. A Gramfort,G Peyré,M Cuturi- Information Processing in Medical Imaging, 2015 - Springer.
-14. `低频振荡电位的能量和相位稳定性与偶极子电流活动相关性的仿真. <http://wulixb.iphy.ac.cn/fileup/PDF/2015-14-148701.pdf>`_. 葛曼玲, 魏孟佳, 师鹏飞, 陈营, 付晓璇, 郭宝强… - 物理学报, 2015 - wulixb.iphy.ac.cn.
-15. `Enhanced neural synchrony between left auditory and premotor cortex is associated with successful phonetic categorization. <https://books.google.co.in/books?hl=en&lr=&id=GX2PCgAAQBAJ&oi=fnd&pg=PA8&ots=RkkQ_HWwLc&sig=0H-rc0sfU5pSPb-tsMyWLZdLJCE>`_. J Alho,FH Lin,M Sato, H Tiitinen,M Sams… - … in speech perception, 2015 - books.google.com.
-16. `Estimating Learning Effects: A Short-Time Fourier Transform Regression Model for MEG Source Localization. <http://arxiv.org/abs/1512.00899>`_. Y Yang,MJ Tarr,RE Kass- arXiv preprint arXiv:1512.00899, 2015 - arxiv.org.
-17. `Evidence for morphological composition in compound words using MEG. <http://www.ncbi.nlm.nih.gov/pmc/articles/PMC4412057/>`_. TL Brooks, DC de Garcia - Frontiers in human neuroscience, 2015 - ncbi.nlm.nih.gov.
-18. `Leveraging anatomical information to improve transfer learning in brain–computer interfaces. <http://iopscience.iop.org/article/10.1088/1741-2560/12/4/046027/meta>`_. M Wronkiewicz, E Larson,AKC Lee- Journal of neural  …, 2015 - iopscience.iop.org.
-19. `Influence of Intracranial Electrode Density and Spatial Configuration on Interictal Spike Localization: A Case Study. <http://journals.lww.com/clinicalneurophys/Abstract/2015/10000/Influence_of_Intracranial_Electrode_Density_and.14.aspx>`_. OV Lie,AM Papanastassiou,JE Cavazos… - Journal of Clinical  …, 2015 - journals.lww.com.
-20. `Facilitated early cortical processing of nude human bodies. <http://www.sciencedirect.com/science/article/pii/S0301051115001039>`_. J Alho,N Salminen,M Sams, JK Hietanen… - Biological  …, 2015 - Elsevier.
-21. `Evidence of syntactic working memory usage in MEG data. <http://www.ling.ohio-state.edu/%7Evanschm/resources/uploads/cmcl/proceedings/cdrom/pdf/CMCL9.pdf>`_. M van Schijndel,B Murphy, W Schuler - Proceedings of CMCL, 2015 - ling.ohio-state.edu.
-22. `Flexible multi-layer sparse approximations of matrices and applications. <http://arxiv.org/abs/1506.07300>`_. LL Magoarou,R Gribonval- arXiv preprint arXiv:1506.07300, 2015 - arxiv.org.
-23. `EEGNET: An Open Source Tool for Analyzing and Visualizing M/EEG Connectome. <http://journals.plos.org/plosone/article?id=10.1371/journal.pone.0138297>`_. M Hassan, M Shamas,M Khalil,W El Falou… - PloS one, 2015 - journals.plos.org.
-24. `Exploring spatio-temporal neural correlates of face learning. <http://www.ml.cmu.edu/research/dap-papers/dap_yang_ying.pdf>`_. Y Yang - 2015 - ml.cmu.edu.
-25. `FAμST: speeding up linear transforms for tractable inverse problems. <http://ieeexplore.ieee.org/xpls/abs_all.jsp?arnumber=7362838>`_. L Le Magoarou,R Gribonval… - …  (EUSIPCO), 2015 23rd  …, 2015 - ieeexplore.ieee.org.
-26. `Early visual word processing is flexible: Evidence from spatiotemporal brain dynamics. <http://www.mitpressjournals.org/doi/abs/10.1162/jocn_a_00815>`_. Y Chen,MH Davis,F Pulvermüller,O Hauk- Journal of cognitive  …, 2015 - MIT Press.
-27. `EEG can track the time course of successful reference resolution in small visual worlds. <http://www.ncbi.nlm.nih.gov/pmc/articles/PMC4653275/>`_. C Brodbeck,L Gwilliams,L Pylkkänen- Frontiers in psychology, 2015 - ncbi.nlm.nih.gov.
-28. `EEG functional connectivity is partially predicted by underlying white matter connectivity. <http://www.sciencedirect.com/science/article/pii/S1053811914010258>`_. CJ Chu, N Tanaka, J Diaz,BL Edlow, O Wu… - Neuroimage, 2015 - Elsevier.
-29. `Automated model selection in covariance estimation and spatial whitening of MEG and EEG signals. <http://www.sciencedirect.com/science/article/pii/S1053811914010325>`_. DA Engemann,A Gramfort- NeuroImage, 2015 - Elsevier.
-30. `A hierarchical Krylov–Bayes iterative inverse solver for MEG with physiological preconditioning. <http://iopscience.iop.org/article/10.1088/0266-5611/31/12/125005/meta>`_. D Calvetti,A Pascarella,F Pitolli,E Somersalo… - Inverse  …, 2015 - iopscience.iop.org.
-31. `A multi-subject, multi-modal human neuroimaging dataset. <http://www.ncbi.nlm.nih.gov/pmc/articles/PMC4412149/>`_. DG Wakeman,RN Henson- Scientific data, 2015 - ncbi.nlm.nih.gov.
-32. `Accumulated source imaging of brain activity with both low and high-frequency neuromagnetic signals. <https://books.google.co.in/books?hl=en&lr=&id=j9BnCwAAQBAJ&oi=fnd&pg=PA302&ots=tzXYPVPctS&sig=fXSvk-cevK2GivxxjOI9CaiapXk>`_. J Xiang, Q Luo, R Kotecha, A Korman… - …  Advances and the  …, 2015 - books.google.com.
-33. `An internet-based real-time audiovisual link for dual meg recordings. <http://journals.plos.org/plosone/article?id=10.1371/journal.pone.0128485>`_. A Zhdanov,J Nurminen, P Baess, L Hirvenkari… - PloS one, 2015 - journals.plos.org.
-34. `AnyWave: a cross-platform and modular software for visualizing and processing electrophysiological signals. <http://www.sciencedirect.com/science/article/pii/S0165027015000187>`_. B Colombet, M Woodman, JM Badier… - Journal of neuroscience  …, 2015 - Elsevier.
-35. `Attention drives synchronization of alpha and beta rhythms between right inferior frontal and primary sensory neocortex. <http://www.jneurosci.org/content/35/5/2074.short>`_. MD Sacchet, RA LaPlante, Q Wan… - The Journal of  …, 2015 - Soc Neuroscience.
-36. `Automated Measurement and Prediction of Consciousness in Vegetative and Minimally Conscious Patients. <https://hal.inria.fr/hal-01225254/>`_. D Engemann,F Raimondo,JR King,M Jas… - ICML Workshop on  …, 2015 - hal.inria.fr.
-37. `Bayesian Structured Sparsity Priors for EEG Source Localization Technical Report. <http://arxiv.org/abs/1509.04576>`_. F Costa, H Batatia, T Oberlin,JY Tourneret- arXiv preprint arXiv: …, 2015 - arxiv.org.
-38. `Magnetoencephalography for Clinical Pediatrics: Recent Advances in Hardware, Methods, and Clinical Applications. <https://www.thieme-connect.com/products/ejournals/html/10.1055/s-0035-1563726>`_. W Gaetz, RS Gordon,C Papadelis… - Journal of Pediatric  …, 2015 - thieme-connect.com.
-39. `Decoding covert shifts of attention induced by ambiguous visuospatial cues. <http://www.ncbi.nlm.nih.gov/pmc/articles/PMC4471354/>`_. RE Trachel,M Clerc,TG Brochier- Frontiers in human  …, 2015 - ncbi.nlm.nih.gov.
-40. `Deep Feature Learning for EEG Recordings. <http://arxiv.org/abs/1511.04306>`_. S Stober,A Sternin,AM Owen,JA Grahn- arXiv preprint arXiv:1511.04306, 2015 - arxiv.org.
-41. `Design and implementation of a brain computer interface system. <https://depositonce.tu-berlin.de/handle/11303/4734>`_. B Venthur- 2015 - depositonce.tu-berlin.de.
-42. `Developmental evaluation of atypical auditory sampling in dyslexia: Functional and structural evidence. <http://onlinelibrary.wiley.com/doi/10.1002/hbm.22986/full>`_. M Lizarazu, M Lallier,N Molinaro… - Human brain  …, 2015 - Wiley Online Library.
-43. `Distinct Effects of Memory Retrieval and Articulatory Preparation when Learning and Accessing New Word Forms. <http://journals.plos.org/plosone/article?id=10.1371/journal.pone.0126652>`_. A Nora, H Renvall, JY Kim,R Salmelin- PloS one, 2015 - journals.plos.org.
-44. `Distinct cortical codes and temporal dynamics for conscious and unconscious percepts. <http://elifesciences.org/content/4/e05652.abstract>`_. M Salti, S Monto,L Charles,JR King,L Parkkonen… - Elife, 2015 - elifesciences.org.
-45. `Does the mismatch negativity operate on a consciously accessible memory trace?. <http://advances.sciencemag.org/content/1/10/e1500677.abstract>`_. AR Dykstra, A Gutschalk - Science advances, 2015 - advances.sciencemag.org.
-46. `MEM-diffusion MRI framework to solve MEEG inverse problem. <http://ieeexplore.ieee.org/xpls/abs_all.jsp?arnumber=7362709>`_. B Belaoucha, JM Lina,M Clerc… - … (EUSIPCO), 2015 23rd …, 2015 - ieeexplore.ieee.org.
-47. `基于有限元方法的 theta 节律能量与导电媒质关系的研究. <http://www.cqvip.com/qk/96363x/201504/665924065.html>`_. 葛曼玲, 郭宝强, 闫志强, 王向阳, 陈盛华, 孙英… - 北京生物医学 …, 2015 - cqvip.com.
-48. `Non-linear processing of a linear speech stream: The influence of morphological structure on the recognition of spoken Arabic words. <http://www.sciencedirect.com/science/article/pii/S0093934X15000929>`_. L Gwilliams,A Marantz- Brain and language, 2015 - Elsevier.
-49. `The role of temporal predictability in semantic expectation: An MEG investigation. <http://www.sciencedirect.com/science/article/pii/S0010945215000945>`_. EF Lau, E Nguyen - Cortex, 2015 - Elsevier.
-50. `The New York Head—A precise standardized volume conductor model for EEG source localization and tES targeting. <http://www.sciencedirect.com/science/article/pii/S1053811915011325>`_. Y Huang,LC Parra,S Haufe- NeuroImage, 2015 - Elsevier.
-51. `Medidas espectrales y de conectividad funcional con magnetoencefalografía: fiabilidad y aplicaciones a deterioro cognitivo leve. <http://eprints.ucm.es/33593/>`_. P Garcés López - 2015 - eprints.ucm.es.
-52. `Reference-free removal of EEG-fMRI ballistocardiogram artifacts with harmonic regression. <http://www.sciencedirect.com/science/article/pii/S1053811915005935>`_. P Krishnaswamy,G Bonmassar, C Poulsen, ET Pierce… - NeuroImage, 2015 - Elsevier.
-53. `Real-time machine learning of MEG: Decoding signatures of selective attention. <https://aaltodoc.aalto.fi/handle/123456789/15550>`_. M Jas- 2015 - aaltodoc.aalto.fi.
-54. `Real-Time Magnetoencephalography for Neurofeedback and Closed-Loop Experiments. <http://link.springer.com/chapter/10.1007/978-4-431-55037-2_17>`_. L Parkkonen- Clinical Systems Neuroscience, 2015 - Springer.
-55. `Real-Time MEG Source Localization Using Regional Clustering. <http://link.springer.com/article/10.1007/s10548-015-0431-9>`_. C Dinh, D Strohmeier,M Luessi, D Güllmar… - Brain topography, 2015 - Springer.
-56. `Physiological consequences of abnormal connectivity in a developmental epilepsy. <http://onlinelibrary.wiley.com/doi/10.1002/ana.24343/full>`_. MM Shafi,M Vernet, D Klooster,CJ Chu… - Annals of  …, 2015 - Wiley Online Library.
-57. `Online visualization of brain connectivity. <http://www.sciencedirect.com/science/article/pii/S0165027015003222>`_. M Billinger,C Brunner,GR Müller-Putz- Journal of neuroscience methods, 2015 - Elsevier.
-58. `Occipital MEG activity in the early time range (< 300 ms) predicts graded changes in perceptual consciousness. <https://cercor.oxfordjournals.org/content/early/2015/05/24/cercor.bhv108.full>`_. LM Andersen, MN Pedersen,K Sandberg… - Cerebral  …, 2015 - Oxford Univ Press.
-59. `Somatosensory cortex functional connectivity abnormalities in autism show opposite trends, depending on direction and spatial scale. <http://brain.oxfordjournals.org/content/early/2015/03/11/brain.awv043.abstract>`_. S Khan,K Michmizos, M Tommerdahl, S Ganesan… - Brain, 2015 - Oxford Univ Press.
-60. `Neuroplasticity in Human Alcoholism: Studies of Extended Abstinence with Potential Treatment Implications. <http://www.ncbi.nlm.nih.gov/pmc/articles/PMC4476599/>`_. G Fein,VA Cardenas- Alcohol research: current reviews, 2015 - ncbi.nlm.nih.gov.
-61. `Sparse EEG Source Localization Using Bernoulli Laplacian Priors. <http://ieeexplore.ieee.org/xpls/abs_all.jsp?arnumber=7134742>`_. F Costa, H Batatia,L Chaari… - … , IEEE Transactions on, 2015 - ieeexplore.ieee.org.
-62. `Towards music imagery information retrieval: Introducing the openmiir dataset of eeg recordings from music perception and imagination. <http://bib.sebastianstober.de/ismir2015.pdf>`_. S Stober,A Sternin,AM Owen… - … International Society for …, 2015 - bib.sebastianstober.de.
-63. `Transcutaneous Vagus Nerve Stimulation Modulates Tinnitus-Related Beta-and Gamma-Band Activity. <http://journals.lww.com/ear-hearing/Abstract/2015/05000/Transcutaneous_Vagus_Nerve_Stimulation_Modulates.12.aspx>`_. P Hyvärinen, S Yrttiaho, J Lehtimäki… - Ear and  …, 2015 - journals.lww.com.
-64. `Neuromagnetic Decomposition of Social Interaction. <http://kups.ub.uni-koeln.de/6262/1/thesis_engemann_da.pdf>`_. DA Engemann- 2015 - kups.ub.uni-koeln.de.
-65. `Mind the noise covariance when localizing brain sources with M/EEG. <http://ieeexplore.ieee.org/xpls/abs_all.jsp?arnumber=7270835>`_. D Engemann, D Strohmeier, E Larson… - Pattern Recognition  …, 2015 - ieeexplore.ieee.org.
-66. `Neuroimaging, neural population models for. <http://link.springer.com/10.1007/978-1-4614-6675-8_70>`_. I Bojak,M Breakspear- Encyclopedia of Computational Neuroscience, 2015 - Springer.
-67. `Wyrm: A brain-computer interface toolbox in Python. <http://link.springer.com/article/10.1007/s12021-015-9271-8>`_. B Venthur,S Dähne,J Höhne, H Heller,B Blankertz- Neuroinformatics, 2015 - Springer.
-68. `Modulation of the~ 20‐Hz motor‐cortex rhythm to passive movement and tactile stimulation. <http://onlinelibrary.wiley.com/doi/10.1002/brb3.328/full>`_. E Parkkonen, K Laaksonen,H Piitulainen… - Brain and  …, 2015 - Wiley Online Library.
-69. `Decoding perceptual thresholds from MEG/EEG. <http://ieeexplore.ieee.org/xpls/abs_all.jsp?arnumber=6858510>`_. Y Bekhti, N Zilber,F Pedregosa… - Pattern Recognition  …, 2014 - ieeexplore.ieee.org.
-70. `Spatiotemporal Signatures of Lexical–Semantic Prediction. <https://cercor.oxfordjournals.org/content/early/2014/10/14/cercor.bhu219.full>`_. EF Lau,K Weber,A Gramfort,MS Hämäläinen… - Cerebral  …, 2014 - Oxford Univ Press.
-71. `Supramodal processing optimizes visual perceptual learning and plasticity. <http://www.sciencedirect.com/science/article/pii/S1053811914001165>`_. N Zilber,P Ciuciu,A Gramfort, L Azizi… - Neuroimage, 2014 - Elsevier.
-72. `The connectome visualization utility: Software for visualization of human brain networks. <http://journals.plos.org/plosone/article?id=10.1371/journal.pone.0113838>`_. RA LaPlante,L Douw, W Tang,SM Stufflebeam- PloS one, 2014 - journals.plos.org.
-73. `Whole brain functional connectivity using phase locking measures of resting state magnetoencephalography. <http://d-scholarship.pitt.edu/24758/1/fnins-08-00141.pdf>`_. BT Schmidt,AS Ghuman, TJ Huppert - Front. Neurosci, 2014 - d-scholarship.pitt.edu.
-74. `Voxel-wise resting-state MEG source magnitude imaging study reveals neurocircuitry abnormality in active-duty service members and veterans with PTSD. <http://www.sciencedirect.com/science/article/pii/S2213158214001132>`_. MX Huang, KA Yurgil, A Robb, A Angeles… - NeuroImage: Clinical, 2014 - Elsevier.
-75. `Vector ℓ 0 latent-space principal component analysis. <http://ieeexplore.ieee.org/xpls/abs_all.jsp?arnumber=6854399>`_. M Luessi, MS Hamalainen… - Acoustics, Speech and  …, 2014 - ieeexplore.ieee.org.
-76. `The iterative reweighted Mixed-Norm Estimate for spatio-temporal MEG/EEG source reconstruction. <https://hal.archives-ouvertes.fr/hal-01079530/>`_. D Strohmeier,J Haueisen,A Gramfort- 2014 - hal.archives-ouvertes.fr.
-77. `Blind denoising with random greedy pursuits. <http://ieeexplore.ieee.org/xpls/abs_all.jsp?arnumber=6847117>`_. M Moussallam,A Gramfort,L Daudet… - Signal Processing  …, 2014 - ieeexplore.ieee.org.
-78. `Covariance shrinkage for autocorrelated data. <http://papers.nips.cc/paper/5399-covariance-shrinkage-for-autocorrelated-data>`_. D Bartz,KR Müller- Advances in Neural Information Processing  …, 2014 - papers.nips.cc.
-79. `Two distinct dynamic modes subtend the detection of unexpected sounds. <http://journals.plos.org/plosone/article?id=10.1371/journal.pone.0085791>`_. JR King,A Gramfort,A Schurger,L Naccache… - PloS one, 2014 - journals.plos.org.
-80. `Cortical oscillations as temporal reference frames for perception. <https://tel.archives-ouvertes.fr/tel-01069219/>`_. A Kosem - 2014 - tel.archives-ouvertes.fr.
-81. `Auditory Conflict Resolution Correlates with Medial–Lateral Frontal Theta/Alpha Phase Synchrony. <http://www.ncbi.nlm.nih.gov/pmc/articles/PMC4208834/>`_. S Huang, S Rossi, M Hämäläinen, J Ahveninen - PloS one, 2014 - ncbi.nlm.nih.gov.
-82. `Brain network connectivity during language comprehension: Interacting linguistic and perceptual subsystems. <http://cercor.oxfordjournals.org/content/early/2014/12/01/cercor.bhu283.short>`_. E Fonteneau, M Bozic,WD Marslen-Wilson- Cerebral Cortex, 2014 - Oxford Univ Press.
-83. `Mapping tonotopic organization in human temporal cortex: representational similarity analysis in EMEG source space. <http://www.ncbi.nlm.nih.gov/pmc/articles/PMC4228977/>`_. L Su, I Zulfiqar, F Jamshed, E Fonteneau… - Frontiers in  …, 2014 - ncbi.nlm.nih.gov.
-84. `Early parallel activation of semantics and phonology in picture naming: Evidence from a multiple linear regression MEG study. <https://cercor.oxfordjournals.org/content/early/2014/07/08/cercor.bhu137.full>`_. M Miozzo,F Pulvermüller,O Hauk- Cerebral Cortex, 2014 - Oxford Univ Press.
-85. `Functional Roles of 10 Hz Alpha-Band Power Modulating Engagement and Disengagement of Cortical Networks in a Complex Visual Motion Task. <http://journals.plos.org/plosone/article?id=10.1371/journal.pone.0107715>`_. KD Rana,LM Vaina- PloS one, 2014 - journals.plos.org.
-86. `Online Distributed Source Localization from EEG/MEG Data. <http://www.computingonline.net/index.php/computing/article/view/617>`_. C Pieloth,TR Knosche, B Maess… - International Journal of  …, 2014 - computingonline.net.
-87. `Localization of MEG human brain responses to retinotopic visual stimuli with contrasting source reconstruction approaches. <https://www.researchgate.net/profile/Kristine_Krug/publication/262931429_Localization_of_MEG_human_brain_responses_to_retinotopic_visual_stimuli_with_contrasting_source_reconstruction_approaches/links/00b4953b50d323bd8e000000.pdf>`_. N Cicmil,H Bridge,AJ Parker,MW Woolrich… - Front.  …, 2014 - researchgate.net.
-88. `MoBILAB: an open source toolbox for analysis and visualization of mobile brain/body imaging data. <https://books.google.co.in/books?hl=en&lr=&id=DpogBQAAQBAJ&oi=fnd&pg=PA50&ots=rlaZ5bB6Bg&sig=fXHwX96mBQCSXItdK3gHSqx4WWA>`_. A Ojeda,N Bigdely-Shamlo,S Makeig- Front. Hum. Neurosci, 2014 - books.google.com.
-89. `Integrating neuroinformatics tools in TheVirtualBrain. <http://www.ncbi.nlm.nih.gov/pmc/articles/PMC4001068/>`_. MM Woodman, L Pezard, L Domide… - Frontiers in  …, 2014 - ncbi.nlm.nih.gov.
-90. `Infants' brain responses to speech suggest analysis by synthesis. <http://www.pnas.org/content/111/31/11238.short>`_. PK Kuhl, RR Ramírez, A Bosseler… - Proceedings of the  …, 2014 - National Acad Sciences.
-91. `Improving spatial localization in MEG inverse imaging by leveraging intersubject anatomical differences. <http://faculty.washington.edu/rkmaddox/papers/Larson_2014_Improving_spatial.pdf>`_. E Larson,RK Maddox,AKC Lee- Front. Neurosci, 2014 - faculty.washington.edu.
-92. `Improved MEG/EEG source localization with reweighted mixed-norms. <http://ieeexplore.ieee.org/xpls/abs_all.jsp?arnumber=6858545>`_. D Strohmeier,J Haueisen… - Pattern Recognition in  …, 2014 - ieeexplore.ieee.org.
-93. `A finite-element reciprocity solution for EEG forward modeling with realistic individual head models. <http://www.sciencedirect.com/science/article/pii/S1053811914007307>`_. E Ziegler, SL Chellappa, G Gaggioni, JQM Ly… - NeuroImage, 2014 - Elsevier.
-94. `Protocoles d'interaction cerveau-machine pour améliorer la performance d'attention visuo-spatiale chez l'homme. <https://tel.archives-ouvertes.fr/tel-01077931/>`_. R Trachel - 2014 - tel.archives-ouvertes.fr.
-95. `Encoding of event timing in the phase of neural oscillations. <http://www.sciencedirect.com/science/article/pii/S1053811914001013>`_. A Kösem,A Gramfort,V van Wassenhove- NeuroImage, 2014 - Elsevier.
-96. `Encoding cortical dynamics in sparse features. <https://books.google.co.in/books?hl=en&lr=&id=THImCwAAQBAJ&oi=fnd&pg=PA78&ots=ymsuynCDU2&sig=UQK7Z-7wMlx4wq90_3pA9RYrI-4>`_. S Khan,J Lefèvre,S Baillet,KP Michmizos, S Ganesan… - 2014 - books.google.com.
-97. `ERF and scale-free analyses of source-reconstructed MEG brain signals during a multisensory learning paradigm. <http://www.theses.fr/2014PA112040>`_. N Zilber - 2014 - theses.fr.
-98. `MEG and EEG data analysis with MNE-Python. <http://dash.harvard.edu/handle/1/11879699>`_. A Gramfort,M Luessi, E Larson,DA Engemann… - 2013 - dash.harvard.edu.
-99. `Interoperability of Free Software Packages to Analyze Functional Human Brain Data. <http://www.synesisjournal.com/vol4_g/Sander_2013_G85-89.pdf>`_. T Sander-Thömmes, A Schlögl - 2010 - synesisjournal.com.
-100. `Neuroplasticity in Human Alcoholism: Studies of Extended Abstinence with Potential Treatment Implications George Fein1, 2 and Valerie A. Cardenas1  …. <http://www.nbresearch.com/PDF/2014/Neuroplasticity%20in%20Human%20Alcoholism-%20Studies%20of%20Extended%20Abstinence%20with%20Potential%20Treatment%20Implications_Fein%20G,%20Cardenas%20V.pdf>`_. G Fein, AMP Center - nbresearch.com.
-101. `Signal Processing and visualization of neuroscience data in a web browser. <https://www.researchgate.net/profile/Chirag_Deepak_Agrawal/publication/277954533_Signal_Processing_and_visualization_of_neuroscience_data_in_a_web_browser/links/55771da708aeacff20004656.pdf>`_. C Agrawal,A Gramfort- researchgate.net.
-102. `VECTOR l0 LATENT-SPACE PRINCIPAL COMPONENT ANALYSIS. <http://www.mirlab.org/conference_papers/International_Conference/ICASSP%202014/papers/p4262-luessi.pdf>`_. M Luessi, MS Hämäläinen, V Solo - mirlab.org.
-103. `Tempo Estimation from the EEG signal during perception and imagination of music. <http://bib.sebastianstober.de/bcmi2015.pdf>`_. A Sternin,S Stober,AM Owen,JA Grahn- bib.sebastianstober.de.
-104. `Règles de sélection de variables pour accélerer la localisation de sources en MEG et EEG sous contrainte de parcimonie. <http://www.josephsalmon.eu/papers/gretsi2015.pdf>`_. O FERCOQ,A GRAMFORT,J SALMON- josephsalmon.eu.
-105. `MEG connectivity and power detections with Minimum Norm Estimates require different regularization parameters. <http://downloads.hindawi.com/journals/cin/aip/541897.pdf>`_. AS Hincapié, J Kujala, J Mattout, S Daligault… - downloads.hindawi.com.
+1. `Development of advanced signal processing and source imaging methods for superparamagnetic relaxometry. <http://iopscience.iop.org/article/10.1088/1361-6560/aa553b/meta>`_. MX Huang, B Anderson, CW Huang… - Physics in Medicine  …, 2017 - iopscience.iop.org.
+2. `Decoding the categorization of visual motion with magnetoencephalography. <http://biorxiv.org/content/early/2017/01/25/103044.abstract>`_. Y Bekhti,A Gramfort, N Zilber,V van Wassenhove- bioRxiv, 2017 - biorxiv.org.
+3. `Diminished auditory sensory gating during active auditory verbal hallucinations. <http://www.sciencedirect.com/science/article/pii/S0920996417300348>`_. RJ Thoma, A Meier,J Houck,VP Clark,JD Lewine… - Schizophrenia  …, 2017 - Elsevier.
+4. `Disambiguating brain functional connectivity. <http://biorxiv.org/content/early/2017/01/25/103002.abstract>`_. E Duff,T Makin,SM Smith,MW Woolrich- bioRxiv, 2017 - biorxiv.org.
+5. `Assessing and tuning brain decoders: cross-validation, caveats, and guidelines. <http://www.sciencedirect.com/science/article/pii/S105381191630595X>`_. G Varoquaux,PR Raamana,DA Engemann… - NeuroImage, 2017 - Elsevier.
+6. `Spatiotemporal dynamics of similarity-based neural representations of facial identity. <http://www.pnas.org/content/114/2/388.short>`_. MD Vida, A Nestor,DC Plaut… - Proceedings of the  …, 2017 - National Acad Sciences.
+7. `Bayesian EEG source localization using a structured sparsity prior. <http://www.sciencedirect.com/science/article/pii/S1053811916304554>`_. F Costa,H Batatia,T Oberlin, C D'Giano,JY Tourneret- NeuroImage, 2017 - Elsevier.
+8. `Updating temporal expectancy of an aversive event engages striatal plasticity under amygdala control. <http://www.nature.com/articles/ncomms13920>`_. G Dallérac,M Graupner, J Knippenberg… - Nature  …, 2017 - nature.com.
+9. `Source analysis of auditory steady-state responses in acoustic and electric hearing. <http://www.sciencedirect.com/science/article/pii/S1053811916306255>`_. R Luke, A De Vos,J Wouters- NeuroImage, 2017 - Elsevier.
+10. `Comparison of group-level, source localized activity for simultaneous functional near-infrared spectroscopy-magnetoencephalography and simultaneous  …. <http://neurophotonics.spiedigitallibrary.org/article.aspx?articleid=2599313>`_. T Huppert, J Barker, B Schmidt… -  …, 2017 - neurophotonics.spiedigitallibrary.org.
+11. `Simultaneous spatio-temporal matching pursuit decomposition of evoked brain responses in MEG. <http://link.springer.com/article/10.1007/s00422-016-0707-5>`_. P Kordowski, A Matysiak, R König,C Sielużycki- Biological Cybernetics, 2017 - Springer.
+12. `An improved artifacts removal method for high dimensional EEG. <http://www.sciencedirect.com/science/article/pii/S0165027016300814>`_. J Hou, K Morgan,DM Tucker, A Konyn… - Journal of neuroscience  …, 2016 - Elsevier.
+13. `Brain mechanisms underlying the brief maintenance of seen and unseen sensory information. <http://www.sciencedirect.com/science/article/pii/S0896627316308017>`_. JR King,N Pescetelli, S Dehaene - Neuron, 2016 - Elsevier.
+14. `EEG and MEG: sensitivity to epileptic spike activity as function of source orientation and depth. <http://iopscience.iop.org/article/10.1088/0967-3334/37/7/1146/meta>`_. A Hunold, ME Funke, R Eichardt… - Physiological  …, 2016 - iopscience.iop.org.
+15. `Matrices efficientes pour le traitement du signal et l'apprentissage automatique. <http://www.theses.fr/2016ISAR0008>`_. L Le Magoarou- 2016 - theses.fr.
+16. `Measuring MEG closer to the brain: Performance of on-scalp sensor arrays. <http://www.sciencedirect.com/science/article/pii/S1053811916307704>`_. J Iivanainen,M Stenroos,L Parkkonen- NeuroImage, 2016 - Elsevier.
+17. `Dynamic Decomposition of Spatiotemporal Neural Signals. <https://arxiv.org/abs/1605.02609>`_. L Ambrogioni,MAJ van Gerven,E Maris- arXiv preprint arXiv:1605.02609, 2016 - arxiv.org.
+18. `Measuring brain signals to evaluate the role of creativity in interceptive human movement. <http://ieeexplore.ieee.org/abstract/document/7473035/>`_. D Pathak,H Yang, TK Chen… - …  (SOSE), 2016 IEEE  …, 2016 - ieeexplore.ieee.org.
+19. `BabyMEG: A whole-head pediatric magnetoencephalography system for human brain development research. <http://aip.scitation.org/doi/abs/10.1063/1.4962020>`_. Y Okada, M Hämäläinen, K Pratt… - Review of Scientific  …, 2016 - aip.scitation.org.
+20. `M/EEG source localization with multi-scale time-frequency dictionaries. <http://ieeexplore.ieee.org/abstract/document/7552337/>`_. Y Bekhti, D Strohmeiery,M Jas… - Pattern Recognition  …, 2016 - ieeexplore.ieee.org.
+21. `An Information-Theoretic View of EEG Sensing. <http://ieeexplore.ieee.org/abstract/document/7782724/>`_. P Grover, P Venkatesh - Proceedings of the IEEE, 2016 - ieeexplore.ieee.org.
+22. `Musical intervention enhances infants' neural processing of temporal structure in music and speech. <http://www.pnas.org/content/early/2016/04/20/1603984113.short>`_. TC Zhao,PK Kuhl- …  of the National Academy of Sciences, 2016 - National Acad Sciences.
+23. `Neurofeedback and creativity in interceptive human movement: a theoretical model for neurocybernetics based kinaesthetic multimodal learning agent. <http://ieeexplore.ieee.org/abstract/document/7573764/>`_. D Pathak, H Yang, TK Chen - Software Quality, Reliability and  …, 2016 - ieeexplore.ieee.org.
+24. `Neuromagnetic evidence for hippocampal modulation of auditory processing. <http://www.sciencedirect.com/science/article/pii/S1053811915008034>`_. H Chatani, K Hagiwara, N Hironaga, K Ogata… - NeuroImage, 2016 - Elsevier.
+25. `Amygdala and insula contributions to dorsal-ventral pathway integration in the prosodic neural network. <https://arxiv.org/abs/1611.01643>`_. DI Leitman, C Edgar, J Berman, K Gamez… - arXiv preprint arXiv: …, 2016 - arxiv.org.
+26. `Normal Evoked Response to Rapid Sequences of Tactile Pulses in Autism Spectrum Disorders. <https://www.ncbi.nlm.nih.gov/pmc/articles/PMC5025534/>`_. S Ganesan,S Khan, KLA Garel… - Frontiers in Human  …, 2016 - ncbi.nlm.nih.gov.
+27. `Occipital MEG activity in the early time range (< 300 ms) predicts graded changes in perceptual consciousness. <http://cercor.oxfordjournals.org/content/26/6/2677.short>`_. LM Andersen, MN Pedersen,K Sandberg… - Cerebral  …, 2016 - Oxford Univ Press.
+28. `On Speech, Music, and Audiomotor Interactions. <https://repository.library.georgetown.edu/handle/10822/1042839>`_. BM Green - 2016 - repository.library.georgetown.edu.
+29. `MEG Connectivity and Power Detections with Minimum Norm Estimates Require Different Regularization Parameters. <http://dl.acm.org/citation.cfm?id=2985916>`_. AS Hincapié,J Kujala, J Mattout, S Daligault… - Computational  …, 2016 - dl.acm.org.
+30. `Linkage mapping with paralogs exposes regions of residual tetrasomic inheritance in chum salmon (Oncorhynchus keta). <http://onlinelibrary.wiley.com/doi/10.1111/1755-0998.12394/full>`_. RK Waples,LW Seeb,JE Seeb- Molecular ecology resources, 2016 - Wiley Online Library.
+31. `EEG reconstruction and skull conductivity estimation using a Bayesian model promoting structured sparsity. <https://arxiv.org/abs/1609.06874>`_. F Costa,H Batatia,T Oberlin,JY Tourneret- arXiv preprint arXiv: …, 2016 - arxiv.org.
+32. `Functional characterisation of letter-specific responses in time, space and current polarity using magnetoencephalography. <http://www.sciencedirect.com/science/article/pii/S105381191600166X>`_. L Gwilliams,GA Lewis,A Marantz- NeuroImage, 2016 - Elsevier.
+33. `Examining the N400m in affectively negative sentences: A magnetoencephalography study. <http://onlinelibrary.wiley.com/doi/10.1111/psyp.12601/pdf>`_. L Parkes,C Perry, P Goodin - Psychophysiology, 2016 - Wiley Online Library.
+34. `Automated rejection and repair of bad trials in MEG/EEG. <http://ieeexplore.ieee.org/abstract/document/7552336/>`_. M Jas,D Engemann,F Raimondo… - Pattern Recognition  …, 2016 - ieeexplore.ieee.org.
+35. `Effects of physical activity on neural markers of attention in children. <https://jyx.jyu.fi/dspace/handle/123456789/51960>`_. J Vähämaa - 2016 - jyx.jyu.fi.
+36. `Early-latency categorical speech sound representations in the left inferior frontal gyrus. <http://www.sciencedirect.com/science/article/pii/S1053811916000227>`_. J Alho, BM Green, PJC May,M Sams, H Tiitinen… - NeuroImage, 2016 - Elsevier.
+37. `Flexible multilayer sparse approximations of matrices and applications. <http://ieeexplore.ieee.org/abstract/document/7435254/>`_. L Le Magoarou,R Gribonval- IEEE Journal of Selected Topics  …, 2016 - ieeexplore.ieee.org.
+38. `From evoked potentials to cortical currents: Resolving V1 and V2 components using retinotopy constrained source estimation without fMRI. <http://onlinelibrary.wiley.com/doi/10.1002/hbm.23128/full>`_. SA Inverso, XL Goh,L Henriksson,S Vanni… - Human brain  …, 2016 - Wiley Online Library.
+39. `Frontal Cortex Supports the Early Structuring of Multiple Solution Steps in Symbolic Problem-solving. <http://www.mitpressjournals.org/doi/abs/10.1162/jocn_a_01027>`_. N Tschentscher,O Hauk- Journal of Cognitive Neuroscience, 2016 - MIT Press.
+40. `Frontal and parietal cortices show different spatiotemporal dynamics across problem-solving stages. <http://www.mitpressjournals.org/doi/abs/10.1162/jocn_a_00960>`_. N Tschentscher,O Hauk- Journal of cognitive neuroscience, 2016 - MIT Press.
+41. `Frontal preparatory neural oscillations associated with cognitive control: A developmental study comparing young adults and adolescents. <http://www.sciencedirect.com/science/article/pii/S1053811916301379>`_. K Hwang,AS Ghuman, DS Manoach,SR Jones… - NeuroImage, 2016 - Elsevier.
+42. `Functional Organization of Flash-Induced V1 Offline Reactivation. <http://www.jneurosci.org/content/36/46/11727.abstract>`_. K Funayama, N Hagura, H Ban… - Journal of  …, 2016 - Soc Neuroscience.
+43. `Auditory processing in noise is associated with complex patterns of disrupted functional connectivity in autism spectrum disorder. <http://onlinelibrary.wiley.com/doi/10.1002/aur.1714/full>`_. F Mamashli,S Khan,H Bharadwaj… - Autism  …, 2016 - Wiley Online Library.
+44. `Population genomics of Salish Sea chum salmon: The legacy of the salmonid whole genome duplication. <https://digital.lib.washington.edu/researchworks/handle/1773/35204>`_. RK Waples- 2016 - digital.lib.washington.edu.
+45. `Hemispheric contributions to language reorganisation: An MEG study of neuroplasticity in chronic post stroke aphasia. <http://www.sciencedirect.com/science/article/pii/S0028393216301142>`_. B Mohr, LJ MacGregor, S Difrancesco, K Harrington… - Neuropsychologia, 2016 - Elsevier.
+46. `High-frequency neural activity predicts word parsing in ambiguous speech streams. <http://jn.physiology.org/content/116/6/2497.abstract>`_. A Kösem, A Basirat, L Azizi… - Journal of  …, 2016 - Am Physiological Soc.
+47. `脳磁図解析のためのノイズを含む多チャンネルデータからのノイズ共分散行列の直接推定法. <https://www.jstage.jst.go.jp/article/jsiamt/26/3/26_353/_article/-char/ja/>`_. 山岸弘幸, 鈴木貴 - 日本応用数理学会論文誌, 2016 - jstage.jst.go.jp.
+48. `Incorporating modern neuroscience findings to improve brain–computer interfaces: tracking auditory attention. <http://iopscience.iop.org/article/10.1088/1741-2560/13/5/056017/meta>`_. M Wronkiewicz,E Larson,AKC Lee- Journal of Neural  …, 2016 - iopscience.iop.org.
+49. `Autoreject: Automated artifact rejection for MEG and EEG data. <https://arxiv.org/abs/1612.08194>`_. M Jas,DA Engemann,Y Bekhti,F Raimondo… - arXiv preprint arXiv: …, 2016 - arxiv.org.
+50. `Association between theta power in 6-month old infants at rest and maternal PTSD severity: A pilot study. <http://www.sciencedirect.com/science/article/pii/S0304394016305420>`_. PM Sanjuan, C Poremba, LR Flynn, R Savich… - Neuroscience  …, 2016 - Elsevier.
+51. `Inter-Subject Alignment of MEG Datasets at the Neural Representational Space. <http://biorxiv.org/content/early/2016/12/22/096040.abstract>`_. Q Zhang,J Borst,RE Kass,JR Anderson- bioRxiv, 2016 - biorxiv.org.
+52. `Interacting parallel pathways associate sounds with visual identity in auditory cortices. <http://www.sciencedirect.com/science/article/pii/S1053811915008599>`_. J Ahveninen, S Huang,SP Ahlfors,M Hämäläinen… - NeuroImage, 2016 - Elsevier.
+53. `Is Neural Activity Detected by ERP-Based Brain-Computer Interfaces Task Specific?. <http://journals.plos.org/plosone/article?id=10.1371/journal.pone.0165556>`_. MA Wenzel,I Almeida,B Blankertz- PloS one, 2016 - journals.plos.org.
+54. `Language in Context: MEG Evidence for Modality-General and-Specific Responses to Reference Resolution. <http://eneuro.org/content/3/6/ENEURO.0145-16.2016.abstract>`_. C Brodbeck,L Gwilliams,L Pylkkänen- eneuro, 2016 - eneuro.org.
+55. `Altered onset response dynamics in somatosensory processing in autism spectrum disorder. <https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4896941/>`_. S Khan,JA Hashmi,F Mamashli… - Frontiers in  …, 2016 - ncbi.nlm.nih.gov.
+56. `간질 환자의뇌 흥분을 연결 관련 평가하는 멀티 모달 Imaging-및 자극 기반 방법. <https://www.jove.com/video/53727/-imaging-?language=Korean>`_. MM Shafi, S Whitfield-Gabrieli,CJ Chu… - 2016 - jove.com.
+57. `Prediction signatures in the brain: Semantic pre-activation during language comprehension. <https://www.ncbi.nlm.nih.gov/pmc/articles/PMC5108799/>`_. B Maess,F Mamashli,J Obleser, L Helle… - Frontiers in Human  …, 2016 - ncbi.nlm.nih.gov.
+58. `Tonotopic representation of loudness in the human cortex. <http://www.sciencedirect.com/science/article/pii/S0378595516303306>`_. A Thwaites, J Schlittenlacher, I Nimmo-Smith… - Hearing Research, 2016 - Elsevier.
+59. `Decoding Dynamic Brain Patterns from Evoked Responses: A Tutorial on Multivariate Pattern Analysis Applied to Time Series Neuroimaging Data. <http://www.mitpressjournals.org/doi/abs/10.1162/jocn_a_01068>`_. T Grootswagers,SG Wardle,TA Carlson- Journal of Cognitive  …, 2016 - MIT Press.
+60. `The New York Head—a precise standardized volume conductor model for EEG source localization and tES targeting. <http://www.sciencedirect.com/science/article/pii/S1053811915011325>`_. Y Huang,LC Parra,S Haufe- NeuroImage, 2016 - Elsevier.
+61. `The Role of Electrophysiological Testing in Pediatric Cochlear Implantation. <http://link.springer.com/chapter/10.1007/978-1-4939-2788-3_8>`_. KA Gordon - Pediatric Cochlear Implantation, 2016 - Springer.
+62. `A simulation framework for benchmarking EEG-based brain connectivity estimation methodologies. <http://link.springer.com/article/10.1007/s10548-016-0498-y>`_. S Haufe,A Ewald- Brain topography, 2016 - Springer.
+63. `Bayesian electromagnetic spatio-temporal imaging of extended sources with Markov Random Field and temporal basis expansion. <http://www.sciencedirect.com/science/article/pii/S1053811916302762>`_. K Liu, ZL Yu,W Wu, Z Gu, Y Li, S Nagarajan - NeuroImage, 2016 - Elsevier.
+64. `The inhibition/excitation ratio related to task-induced oscillatory modulations during a working memory task: A multtimodal-imaging study using MEG and MRS. <http://www.sciencedirect.com/science/article/pii/S1053811916000069>`_. Y Takei, K Fujihara, M Tagawa, N Hironaga,J Near… - NeuroImage, 2016 - Elsevier.
+65. `The interactive electrode localization utility: software for automatic sorting and labeling of intracranial subdural electrodes. <http://link.springer.com/article/10.1007/s11548-016-1504-2>`_. RA LaPlante,W Tang, N Peled, DI Vallejo… - International Journal of  …, 2016 - Springer.
+66. `The iterative reweighted Mixed-Norm Estimate for spatio-temporal MEG/EEG source reconstruction. <http://ieeexplore.ieee.org/abstract/document/7452415/>`_. D Strohmeier,Y Bekhti,J Haueisen… - IEEE transactions on  …, 2016 - ieeexplore.ieee.org.
+67. `The lexical categorization model: A computational model of left ventral occipito-temporal cortex activation in visual word recognition. <http://biorxiv.org/content/early/2016/11/03/085332.abstract>`_. B Gagl,F Richlan,P Ludersdorfer, J Sassenhagen… - bioRxiv, 2016 - biorxiv.org.
+68. `Consistency of EEG source localization and connectivity estimates. <https://pdfs.semanticscholar.org/af94/912ef628414cf3483314ba0a508f826b77c6.pdf>`_. K Mahjoory,VV Nikulin,L Botrel… -  …, 2016 - pdfs.semanticscholar.org.
+69. `Conforming Discretizations of Boundary Element Solutions of the Electroencephalography Forward Problem. <https://arxiv.org/abs/1603.06283>`_. L Rahmouni, S Adrian,K Cools,FP Andriulli- arXiv preprint arXiv: …, 2016 - arxiv.org.
+70. `Temporal alignment of pupillary response with stimulus events via deconvolution a. <http://asa.scitation.org/doi/abs/10.1121/1.4943787>`_. DR McCloy,ED Larson, B Lau… - The Journal of the  …, 2016 - asa.scitation.org.
+71. `Tracking cognitive processing stages with MEG: A spatio-temporal model of associative recognition in the brain. <http://www.sciencedirect.com/science/article/pii/S1053811916303780>`_. JP Borst,AS Ghuman, JR Anderson - NeuroImage, 2016 - Elsevier.
+72. `A graph framework for multimodal medical information processing. <https://arxiv.org/abs/1608.00134>`_. G Drakopoulos,V Megalooikonomou- arXiv preprint arXiv:1608.00134, 2016 - arxiv.org.
+73. `Variability of ICA decomposition may impact EEG signals when used to remove eyeblink artifacts. <http://onlinelibrary.wiley.com/doi/10.1111/psyp.12804/full>`_. MB Pontifex,KL Gwizdala,AC Parks… -  …, 2016 - Wiley Online Library.
+74. `Comparing the Performance of Popular MEG/EEG Artifact Correction Methods in an Evoked-Response Study. <http://dl.acm.org/citation.cfm?id=2984845>`_. NT Haumann,L Parkkonen,M Kliuchko… - Computational  …, 2016 - dl.acm.org.
+75. `A brain-controlled exoskeleton with cascaded event-related desynchronization classifiers. <http://www.sciencedirect.com/science/article/pii/S0921889016304948>`_. K Lee, D Liu, L Perroud,R Chavarriaga… - Robotics and Autonomous …, 2016 - Elsevier.
+76. `A Multimodal Imaging-and Stimulation-based Method of Evaluating Connectivity-related Brain Excitability in Patients with Epilepsy. <https://www.jove.com/video/53727/a-multimodal-imaging-stimulation-based-method-evaluating-connectivity>`_. MM Shafi, S Whitfield-Gabrieli,CJ Chu… - JoVE (Journal of  …, 2016 - jove.com.
+77. `Within-and between-session replicability of cognitive brain processes: An MEG study with an N-back task. <http://www.sciencedirect.com/science/article/pii/S0031938416300506>`_. L Ahonen,M Huotilainen,E Brattico- Physiology & behavior, 2016 - Elsevier.
+78. `Comparing Features for Classification of MEG Responses to Motor Imagery. <http://journals.plos.org/plosone/article?id=10.1371/journal.pone.0168766>`_. HL Halme,L Parkkonen- PloS one, 2016 - journals.plos.org.
+79. `CoSMoMVPA: multi-modal multivariate pattern analysis of neuroimaging data in Matlab/GNU Octave. <https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4956688/>`_. NN Oosterhof,AC Connolly… - Frontiers in  …, 2016 - ncbi.nlm.nih.gov.
+80. `Capturing complex behavior in brain imaging: strategies and instrumentation. <https://aaltodoc.aalto.fi/handle/123456789/20773>`_. A Zhdanov - 2016 - aaltodoc.aalto.fi.
+81. `The Importance of Properly Compensating for Head Movements During MEG Acquisition Across Different Age Groups. <http://link.springer.com/article/10.1007/s10548-016-0523-1>`_. E Larson,S Taulu- Brain Topography, 2016 - Springer.
+82. `Test‐retest reliability of resting‐state magnetoencephalography power in sensor and source space. <http://onlinelibrary.wiley.com/doi/10.1002/hbm.23027/full>`_. MC Martín‐Buro,P Garcés,F Maestú- Human brain mapping, 2016 - Wiley Online Library.
+83. `Quantifying the test-retest reliability of magnetoencephalography resting-state functional connectivity. <http://online.liebertpub.com/doi/abs/10.1089/brain.2015.0416>`_. P Garcés,MC Martín-Buro,F Maestú- Brain connectivity, 2016 - online.liebertpub.com.
+84. `A theory of working memory without consciousness or sustained activity. <http://biorxiv.org/content/early/2016/12/14/093815.abstract>`_. D Trubutschek, S Marti, A Ojeda,JR King, Y Mi… - bioRxiv, 2016 - biorxiv.org.
+85. `Recovering TMS-evoked EEG responses masked by muscle artifacts. <http://www.sciencedirect.com/science/article/pii/S1053811916301495>`_. TP Mutanen, M Kukkonen,JO Nieminen,M Stenroos… - Neuroimage, 2016 - Elsevier.
+86. `Reference-free removal of EEG-fMRI ballistocardiogram artifacts with harmonic regression. <http://www.sciencedirect.com/science/article/pii/S1053811915005935>`_. P Krishnaswamy,G Bonmassar, C Poulsen, ET Pierce… - NeuroImage, 2016 - Elsevier.
+87. `Previous exposure to intact speech increases intelligibility of its digitally degraded counterpart as a function of stimulus complexity. <http://www.sciencedirect.com/science/article/pii/S1053811915009398>`_. M Hakonen, PJC May,J Alho, P Alku, E Jokinen… - NeuroImage, 2016 - Elsevier.
+88. `Alcohol hits you when it is hard: intoxication, task difficulty, and theta brain oscillations. <http://onlinelibrary.wiley.com/doi/10.1111/acer.13014/full>`_. BQ Rosen, N Padovan… - Alcoholism: Clinical and  …, 2016 - Wiley Online Library.
+89. `Spectral and source structural development of mu and alpha rhythms from infancy through adulthood. <http://www.sciencedirect.com/science/article/pii/S1388245715001698>`_. SG Thorpe,EN Cannon,NA Fox- Clinical Neurophysiology, 2016 - Elsevier.
+90. `APPLICATION OF SUPPORT VECTOR MACHINES TO LONGITUDINAL FUNCTIONAL NEUROIMAGING DATA. <https://dalspace.library.dal.ca/handle/10222/72586>`_. A Rudiuk - 2016 - dalspace.library.dal.ca.
+91. `Decoding the Dynamics of Conscious Perception: The Temporal Generalization Method. <http://link.springer.com/chapter/10.1007/978-3-319-28802-4_7>`_. S Dehaene,JR King- Micro-, Meso-and Macro-Dynamics of the Brain, 2016 - Springer.
+92. `Selective maintenance mechanisms of seen and unseen sensory features in the human brain. <http://www.biorxiv.org/content/early/2016/02/18/040030.abstract>`_. K Jean-Remi,N Pescetelli, S Dehaene - Biorxiv, 2016 - biorxiv.org.
+93. `Development of a Group Dynamic Functional Connectivity Pipeline for Magnetoencephalography Data and its Application to the Human Face Processing Network. <http://repository.unm.edu/handle/1928/31729>`_. P Lysne- 2016 - repository.unm.edu.
+94. `Review of analytical instruments for EEG analysis. <https://arxiv.org/abs/1605.01381>`_. SN Agapov, VA Bulanov, AV Zakharov… - arXiv preprint arXiv: …, 2016 - arxiv.org.
+95. `RELATING AUTISM OCCURRENCE TO FUNCTIONAL CONNECTIVITY IN RESTING-STATE MEG (2014 ADA REPORT). <http://civilstat.com/portfolio/Wieczorek_ADA.pdf>`_. J WIECZOREK- 2016 - civilstat.com.
+96. `Rapid tuning shifts in human auditory cortex enhance speech intelligibility. <https://www.ncbi.nlm.nih.gov/pmc/articles/PMC5187445/>`_. CR Holdgraf, W De Heer,B Pasley, J Rieger… - Nature  …, 2016 - ncbi.nlm.nih.gov.
+97. `Relating dynamic brain states to dynamic machine states: human and machine solutions to the speech recognition problem. <https://www.researchgate.net/profile/William_Marslen-Wilson/publication/308033147_Relating_dynamic_brain_states_to_dynamic_machine_states_human_and_machine_solutions_to_the_speech_recognition_problem/links/57d7ccf808ae601b39ae5b54.pdf>`_. C Wingfield, L Su, X Liu,C Zhang,P Woodland… - bioRxiv, 2016 - researchgate.net.
+98. `Advances in high-dimensional covariance matrix estimation. <https://depositonce.tu-berlin.de/handle/11303/5357>`_. D Bartz - 2016 - depositonce.tu-berlin.de.
+99. `Decoding covert shifts of attention induced by ambiguous visuospatial cues. <https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4471354/>`_. RE Trachel,M Clerc,TG Brochier- Frontiers in human  …, 2015 - ncbi.nlm.nih.gov.
+100. `Design and implementation of a brain computer interface system. <https://depositonce.tu-berlin.de/handle/11303/4734>`_. B Venthur- 2015 - depositonce.tu-berlin.de.
+101. `Automated model selection in covariance estimation and spatial whitening of MEG and EEG signals. <http://www.sciencedirect.com/science/article/pii/S1053811914010325>`_. DA Engemann,A Gramfort- NeuroImage, 2015 - Elsevier.
+102. `Developmental evaluation of atypical auditory sampling in dyslexia: Functional and structural evidence. <http://onlinelibrary.wiley.com/doi/10.1002/hbm.22986/full>`_. M Lizarazu,M Lallier,N Molinaro… - Human brain  …, 2015 - Wiley Online Library.
+103. `Designing workflows for the reproducible analysis of electrophysiological data. <http://link.springer.com/chapter/10.1007/978-3-319-50862-7_5>`_. M Denker,S Grün- International Workshop on Brain-Inspired Computing, 2015 - Springer.
+104. `Early visual word processing is flexible: Evidence from spatiotemporal brain dynamics. <http://www.mitpressjournals.org/doi/abs/10.1162/jocn_a_00815>`_. Y Chen,MH Davis,F Pulvermüller,O Hauk- Journal of Cognitive  …, 2015 - MIT Press.
+105. `Distinct Effects of Memory Retrieval and Articulatory Preparation when Learning and Accessing New Word Forms. <http://journals.plos.org/plosone/article?id=10.1371/journal.pone.0126652>`_. A Nora, H Renvall, JY Kim,R Salmelin- PloS one, 2015 - journals.plos.org.
+106. `Distinct cortical codes and temporal dynamics for conscious and unconscious percepts. <https://elifesciences.org/content/4/e05652>`_. M Salti, S Monto,L Charles,JR King,L Parkkonen… - Elife, 2015 - elifesciences.org.
+107. `Does the mismatch negativity operate on a consciously accessible memory trace?. <http://advances.sciencemag.org/content/1/10/e1500677.abstract>`_. AR Dykstra, A Gutschalk - Science advances, 2015 - advances.sciencemag.org.
+108. `Early parallel activation of semantics and phonology in picture naming: Evidence from a multiple linear regression MEG study. <http://cercor.oxfordjournals.org/content/25/10/3343.short>`_. M Miozzo,F Pulvermüller,O Hauk- Cerebral Cortex, 2015 - Oxford Univ Press.
+109. `Deep feature learning for EEG recordings. <https://arxiv.org/abs/1511.04306>`_. S Stober,A Sternin,AM Owen,JA Grahn- arXiv preprint arXiv:1511.04306, 2015 - arxiv.org.
+110. `Bayesian Structured Sparsity Priors for EEG Source Localization Technical Report. <https://arxiv.org/abs/1509.04576>`_. F Costa,H Batatia,T Oberlin,JY Tourneret- arXiv preprint arXiv: …, 2015 - arxiv.org.
+111. `EEGNET: an open source tool for analyzing and visualizing M/EEG connectome. <http://journals.plos.org/plosone/article?id=10.1371/journal.pone.0138297>`_. M Hassan,M Shamas,M Khalil,W El Falou… - PloS one, 2015 - journals.plos.org.
+112. `EEG can track the time course of successful reference resolution in small visual worlds. <https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4653275/>`_. C Brodbeck,L Gwilliams,L Pylkkänen- Frontiers in psychology, 2015 - ncbi.nlm.nih.gov.
+113. `Automated measurement and prediction of consciousness in vegetative and minimally conscious patients. <https://hal.inria.fr/hal-01225254/>`_. D Engemann,F Raimondo,JR King,M Jas… - ICML Workshop on  …, 2015 - hal.inria.fr.
+114. `EEG functional connectivity is partially predicted by underlying white matter connectivity. <http://www.sciencedirect.com/science/article/pii/S1053811914010258>`_. CJ Chu,N Tanaka, J Diaz,BL Edlow, O Wu… - NeuroImage, 2015 - Elsevier.
+115. `Enhanced neural synchrony between left auditory and premotor cortex is associated with successful phonetic categorization. <https://books.google.co.in/books?hl=en&lr=&id=GX2PCgAAQBAJ&oi=fnd&pg=PA8&ots=RknQUEYxCa&sig=vgpx5ID2c1j8BR3pUe6Rh49Ei-U>`_. J Alho,FH Lin,M Sato, H Tiitinen,M Sams… - … in speech perception, 2015 - books.google.com.
+116. `Magnetoencephalography for Clinical Pediatrics: Recent Advances in Hardware, Methods, and Clinical Applications. <https://www.thieme-connect.com/products/ejournals/html/10.1055/s-0035-1563726>`_. W Gaetz, RS Gordon,C Papadelis… - Journal of Pediatric  …, 2015 - thieme-connect.com.
+117. `Evidence for morphological composition in compound words using MEG. <https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4412057/>`_. TL Brooks, DC de Garcia - Frontiers in human neuroscience, 2015 - ncbi.nlm.nih.gov.
+118. `Evidence of syntactic working memory usage in MEG data. <http://www.ling.ohio-state.edu/~vanschm/resources/uploads/cmcl/proceedings/cdrom/pdf/CMCL9.pdf>`_. M van Schijndel,B Murphy, W Schuler - Proceedings of CMCL, 2015 - ling.ohio-state.edu.
+119. `Real-Time Magnetoencephalography for Neurofeedback and Closed-Loop Experiments. <http://link.springer.com/chapter/10.1007/978-4-431-55037-2_17>`_. L Parkkonen- Clinical Systems Neuroscience, 2015 - Springer.
+120. `Real-time MEG source localization using regional clustering. <http://link.springer.com/article/10.1007/s10548-015-0431-9>`_. C Dinh, D Strohmeier,M Luessi,D Güllmar… - Brain topography, 2015 - Springer.
+121. `Real-time machine learning of MEG: Decoding signatures of selective attention. <https://aaltodoc.aalto.fi/handle/123456789/15550>`_. M Jas- 2015 - aaltodoc.aalto.fi.
+122. `Report Date: December 10, 2015. <http://surveygizmoresponseuploads.s3.amazonaws.com/fileuploads/11364/363557/36-436fdac9508245b2466ae88a11884603_Final_report.pdf>`_. AKC Lee, M Wronkiewicz - 2015 - … .s3.amazonaws.com.
+123. `Accumulated source imaging of brain activity with both low and high-frequency neuromagnetic signals. <https://books.google.co.in/books?hl=en&lr=&id=j9BnCwAAQBAJ&oi=fnd&pg=PA302&ots=tz_YJSRdkQ&sig=1zT4kH0LJJPE_i9KUigF7PnfdNk>`_. J Xiang, Q Luo, R Kotecha, A Korman… - …  Advances and the  …, 2015 - books.google.com.
+124. `Somatosensory cortex functional connectivity abnormalities in autism show opposite trends, depending on direction and spatial scale. <http://brain.oxfordjournals.org/content/early/2015/03/11/brain.awv043.abstract>`_. S Khan,K Michmizos, M Tommerdahl, S Ganesan… - Brain, 2015 - Oxford Univ Press.
+125. `Sparse EEG source localization using Bernoulli laplacian priors. <http://ieeexplore.ieee.org/abstract/document/7134742/>`_. F Costa,H Batatia,L Chaari… - IEEE Transactions on  …, 2015 - ieeexplore.ieee.org.
+126. `Tempo estimation from the EEG signal during perception and imagination of music. <http://bib.sebastianstober.de/bcmi2015.pdf>`_. A Sternin,S Stober,JA Grahn… - …  Workshop on Brain- …, 2015 - bib.sebastianstober.de.
+127. `The Neural Representation of Polysemy: The Case of Dot-objects. <http://eprints-phd.biblio.unitn.it/1583/>`_. Y Tao - 2015 - eprints-phd.biblio.unitn.it.
+128. `A multi-subject, multi-modal human neuroimaging dataset. <https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4412149/>`_. DG Wakeman,RN Henson- Scientific data, 2015 - ncbi.nlm.nih.gov.
+129. `A hierarchical Krylov–Bayes iterative inverse solver for MEG with physiological preconditioning. <http://iopscience.iop.org/article/10.1088/0266-5611/31/12/125005/meta>`_. D Calvetti,A Pascarella,F Pitolli,E Somersalo… - Inverse  …, 2015 - iopscience.iop.org.
+130. `The role of temporal predictability in semantic expectation: An MEG investigation. <http://www.sciencedirect.com/science/article/pii/S0010945215000945>`_. EF Lau, E Nguyen - Cortex, 2015 - Elsevier.
+131. `Towards Music Imagery Information Retrieval: Introducing the OpenMIIR Dataset of EEG Recordings from Music Perception and Imagination.. <http://bib.sebastianstober.de/ismir2015.pdf>`_. S Stober,A Sternin,AM Owen,JA Grahn- ISMIR, 2015 - bib.sebastianstober.de.
+132. `Transcutaneous vagus nerve stimulation modulates tinnitus-related beta-and gamma-band activity. <http://journals.lww.com/ear-hearing/Abstract/2015/05000/Transcutaneous_Vagus_Nerve_Stimulation_Modulates.12.aspx>`_. P Hyvärinen, S Yrttiaho, J Lehtimäki… - Ear and  …, 2015 - journals.lww.com.
+133. `Wyrm: A brain-computer interface toolbox in python. <http://link.springer.com/article/10.1007/s12021-015-9271-8>`_. B Venthur,S Dähne,J Höhne, H Heller,B Blankertz- Neuroinformatics, 2015 - Springer.
+134. `低频振荡电位的能量和相位稳定性与偶极子电流活动相关性的仿真. <http://wulixb.iphy.ac.cn/fileup/PDF/2015-14-148701.pdf>`_. 葛曼玲, 魏孟佳, 师鹏飞, 陈营, 付晓璇, 郭宝强… - 物理学报, 2015 - wulixb.iphy.ac.cn.
+135. `基于有限元方法的 theta 节律能量与导电媒质关系的研究. <http://www.cqvip.com/qk/96363x/201504/665924065.html>`_. 葛曼玲, 郭宝强, 闫志强, 王向阳, 陈盛华, 孙英… - 北京生物医学 …, 2015 - cqvip.com.
+136. `Physiological consequences of abnormal connectivity in a developmental epilepsy. <http://onlinelibrary.wiley.com/doi/10.1002/ana.24343/full>`_. MM Shafi,M Vernet, D Klooster,CJ Chu… - Annals of  …, 2015 - Wiley Online Library.
+137. `Online visualization of brain connectivity. <http://www.sciencedirect.com/science/article/pii/S0165027015003222>`_. M Billinger,C Brunner,GR Müller-Putz- Journal of neuroscience methods, 2015 - Elsevier.
+138. `Non-linear processing of a linear speech stream: The influence of morphological structure on the recognition of spoken Arabic words. <http://www.sciencedirect.com/science/article/pii/S0093934X15000929>`_. L Gwilliams,A Marantz- Brain and language, 2015 - Elsevier.
+139. `Neuroplasticity in human alcoholism: studies of extended abstinence with potential treatment implications. <https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4476599/>`_. G Fein,VA Cardenas- Alcohol research: current reviews, 2015 - ncbi.nlm.nih.gov.
+140. `Exploring spatio-temporal neural correlates of face learning. <http://ai2-s2-pdfs.s3.amazonaws.com/b7e5/bc52d7fc0dcaee8287ab8838e936550e618e.pdf>`_. Y Yang- 2015 - ai2-s2-pdfs.s3.amazonaws.com.
+141. `FAμST: Speeding up linear transforms for tractable inverse problems. <http://ieeexplore.ieee.org/abstract/document/7362838/>`_. L Le Magoarou,R Gribonval… - …  (EUSIPCO), 2015 23rd  …, 2015 - ieeexplore.ieee.org.
+142. `Facilitated early cortical processing of nude human bodies. <http://www.sciencedirect.com/science/article/pii/S0301051115001039>`_. J Alho,N Salminen,M Sams,JK Hietanen… - Biological  …, 2015 - Elsevier.
+143. `Fast optimal transport averaging of neuroimaging data. <http://link.springer.com/chapter/10.1007/978-3-319-19992-4_20>`_. A Gramfort,G Peyré,M Cuturi- International Conference on Information  …, 2015 - Springer.
+144. `Attention drives synchronization of alpha and beta rhythms between right inferior frontal and primary sensory neocortex. <http://www.jneurosci.org/content/35/5/2074.short>`_. MD Sacchet, RA LaPlante, Q Wan… - Journal of  …, 2015 - Soc Neuroscience.
+145. `Influence of intracranial electrode density and spatial configuration on interictal spike localization: a case study. <http://journals.lww.com/clinicalneurophys/Abstract/2015/10000/Influence_of_Intracranial_Electrode_Density_and.14.aspx>`_. OV Lie,AM Papanastassiou,JE Cavazos… - Journal of Clinical  …, 2015 - journals.lww.com.
+146. `Leveraging anatomical information to improve transfer learning in brain–computer interfaces. <http://iopscience.iop.org/article/10.1088/1741-2560/12/4/046027/meta>`_. M Wronkiewicz,E Larson,AKC Lee- Journal of neural  …, 2015 - iopscience.iop.org.
+147. `AnyWave: a cross-platform and modular software for visualizing and processing electrophysiological signals. <http://www.sciencedirect.com/science/article/pii/S0165027015000187>`_. B Colombet, M Woodman,JM Badier… - Journal of neuroscience  …, 2015 - Elsevier.
+148. `An internet-based real-time audiovisual link for dual MEG recordings. <http://journals.plos.org/plosone/article?id=10.1371/journal.pone.0128485>`_. A Zhdanov,J Nurminen, P Baess, L Hirvenkari… - PloS one, 2015 - journals.plos.org.
+149. `MEM-diffusion MRI framework to solve MEEG inverse problem. <http://ieeexplore.ieee.org/abstract/document/7362709/>`_. B Belaoucha, JM Lina,M Clerc… - … (EUSIPCO), 2015 23rd …, 2015 - ieeexplore.ieee.org.
+150. `An Integrated Neuroscience and Engineering Approach to Classifying Human Brain-States. <http://oai.dtic.mil/oai/oai?verb=getRecord&metadataPrefix=html&identifier=AD1001845>`_. AK Lee, M Wronkiewicz - 2015 - DTIC Document.
+151. `Medidas espectrales y de conectividad funcional con magnetoencefalografía: fiabilidad y aplicaciones a deterioro cognitivo leve. <http://eprints.ucm.es/33593/>`_. P Garcés López - 2015 - eprints.ucm.es.
+152. `Mind the noise covariance when localizing brain sources with M/EEG. <http://ieeexplore.ieee.org/abstract/document/7270835/>`_. D Engemann, D Strohmeier,E Larson… - Pattern Recognition  …, 2015 - ieeexplore.ieee.org.
+153. `Modulation of the~ 20‐Hz motor‐cortex rhythm to passive movement and tactile stimulation. <http://onlinelibrary.wiley.com/doi/10.1002/brb3.328/full>`_. E Parkkonen, K Laaksonen,H Piitulainen… - Brain and  …, 2015 - Wiley Online Library.
+154. `Neuroimaging, neural population models for. <http://link.springer.com/10.1007/978-1-4614-6675-8_70>`_. I Bojak,M Breakspear- Encyclopedia of Computational Neuroscience, 2015 - Springer.
+155. `Neuromagnetic Decomposition of Social Interaction. <http://kups.ub.uni-koeln.de/6262/1/thesis_engemann_da.pdf>`_. DA Engemann- 2015 - kups.ub.uni-koeln.de.
+156. `Auditory Conflict Resolution Correlates with Medial–Lateral Frontal Theta/Alpha Phase Synchrony. <http://journals.plos.org/plosone/article?id=10.1371/journal.pone.0110989>`_. S Huang, S Rossi, M Hämäläinen, J Ahveninen - PloS one, 2014 - journals.plos.org.
+157. `A finite-element reciprocity solution for EEG forward modeling with realistic individual head models. <http://www.sciencedirect.com/science/article/pii/S1053811914007307>`_. E Ziegler, SL Chellappa, G Gaggioni, JQM Ly… - NeuroImage, 2014 - Elsevier.
+158. `Blind denoising with random greedy pursuits. <http://ieeexplore.ieee.org/abstract/document/6847117/>`_. M Moussallam,A Gramfort,L Daudet… - IEEE Signal  …, 2014 - ieeexplore.ieee.org.
+159. `Improving spatial localization in MEG inverse imaging by leveraging intersubject anatomical differences. <http://journal.frontiersin.org/article/10.3389/fnins.2014.00330/full>`_. E Larson,RK Maddox,AKC Lee- Frontiers in neuroscience, 2014 - journal.frontiersin.org.
+160. `Integrating neuroinformatics tools in TheVirtualBrain. <http://journal.frontiersin.org/article/10.3389/fninf.2014.00036/abstract>`_. MM Woodman, L Pezard, L Domide… - Frontiers in  …, 2014 - journal.frontiersin.org.
+161. `Infants' brain responses to speech suggest analysis by synthesis. <http://www.pnas.org/content/111/31/11238.short>`_. PK Kuhl, RR Ramírez, A Bosseler… - Proceedings of the  …, 2014 - National Acad Sciences.
+162. `Spatiotemporal signatures of lexical–semantic prediction. <http://cercor.oxfordjournals.org/content/early/2014/10/14/cercor.bhu219.short>`_. EF Lau,K Weber,A Gramfort,MS Hämäläinen… - Cerebral  …, 2014 - Oxford Univ Press.
+163. `Two distinct dynamic modes subtend the detection of unexpected sounds. <http://journals.plos.org/plosone/article?id=10.1371/journal.pone.0085791>`_. JR King,A Gramfort,A Schurger,L Naccache… - PloS one, 2014 - journals.plos.org.
+164. `Protocoles d'interaction cerveau-machine pour améliorer la performance d'attention visuo-spatiale chez l'homme. <https://tel.archives-ouvertes.fr/tel-01077931/>`_. R Trachel - 2014 - tel.archives-ouvertes.fr.
+165. `Online distributed source localization from EEG/MEG data. <http://www.computingonline.net/index.php/computing/article/view/617>`_. C Pieloth,TR Knosche, B Maess… - International Journal of  …, 2014 - computingonline.net.
+166. `Vector ℓ 0 latent-space principal component analysis. <http://ieeexplore.ieee.org/abstract/document/6854399/>`_. M Luessi, MS Hamalainen… - Acoustics, Speech and  …, 2014 - ieeexplore.ieee.org.
+167. `MoBILAB: an open source toolbox for analysis and visualization of mobile brain/body imaging data. <https://books.google.co.in/books?hl=en&lr=&id=DpogBQAAQBAJ&oi=fnd&pg=PA50&ots=rldZ-8D7se&sig=djxEdCnDgrBRYDHZ8p5t06MyU6A>`_. A Ojeda,N Bigdely-Shamlo,S Makeig- Front. Hum. Neurosci, 2014 - books.google.com.
+168. `Brain network connectivity during language comprehension: Interacting linguistic and perceptual subsystems. <http://cercor.oxfordjournals.org/content/early/2014/12/01/cercor.bhu283.short>`_. E Fonteneau,M Bozic,WD Marslen-Wilson- Cerebral Cortex, 2014 - Oxford Univ Press.
+169. `Voxel-wise resting-state MEG source magnitude imaging study reveals neurocircuitry abnormality in active-duty service members and veterans with PTSD. <http://www.sciencedirect.com/science/article/pii/S2213158214001132>`_. MX Huang, KA Yurgil, A Robb, A Angeles… - NeuroImage: Clinical, 2014 - Elsevier.
+170. `Localization of MEG human brain responses to retinotopic visual stimuli with contrasting source reconstruction approaches. <http://journal.frontiersin.org/article/10.3389/fnins.2014.00127>`_. N Cicmil,H Bridge,AJ Parker… - Frontiers in  …, 2014 - journal.frontiersin.org.
+171. `The connectome visualization utility: Software for visualization of human brain networks. <http://journals.plos.org/plosone/article?id=10.1371/journal.pone.0113838>`_. RA LaPlante,L Douw,W Tang,SM Stufflebeam- PloS one, 2014 - journals.plos.org.
+172. `Supramodal processing optimizes visual perceptual learning and plasticity. <http://www.sciencedirect.com/science/article/pii/S1053811914001165>`_. N Zilber,P Ciuciu,A Gramfort, L Azizi… - Neuroimage, 2014 - Elsevier.
+173. `Improved MEG/EEG source localization with reweighted mixed-norms. <http://ieeexplore.ieee.org/abstract/document/6858545/>`_. D Strohmeier,J Haueisen… - Pattern Recognition in  …, 2014 - ieeexplore.ieee.org.
+174. `Functional roles of 10 Hz alpha-band power modulating engagement and disengagement of cortical networks in a complex visual motion task. <http://journals.plos.org/plosone/article?id=10.1371/journal.pone.0107715>`_. KD Rana,LM Vaina- PloS one, 2014 - journals.plos.org.
+175. `Estimating Learning Effects: A Short-Time Fourier Transform Regression Model for MEG Source Localization. <http://link.springer.com/chapter/10.1007/978-3-319-45174-9_8>`_. Y Yang,MJ Tarr,RE Kass- …  on Machine Learning and Interpretation in  …, 2014 - Springer.
+176. `Encoding of event timing in the phase of neural oscillations. <http://www.sciencedirect.com/science/article/pii/S1053811914001013>`_. A Kösem,A Gramfort,V van Wassenhove- NeuroImage, 2014 - Elsevier.
+177. `Encoding cortical dynamics in sparse features. <https://books.google.co.in/books?hl=en&lr=&id=THImCwAAQBAJ&oi=fnd&pg=PA78&ots=ymvuskEDW4&sig=wtdn4GDCzKbfujPvVH2P7yqBmGw>`_. S Khan,J Lefèvre,S Baillet… - Frontiers in human  …, 2014 - books.google.com.
+178. `ERF and scale-free analyses of source-reconstructed MEG brain signals during a multisensory learning paradigm. <http://www.theses.fr/2014PA112040>`_. N Zilber - 2014 - theses.fr.
+179. `Whole brain functional connectivity using phase locking measures of resting state magnetoencephalography. <http://journal.frontiersin.org/article/10.3389/fnins.2014.00141/full>`_. BT Schmidt,AS Ghuman… - Frontiers in  …, 2014 - journal.frontiersin.org.
+180. `Decoding perceptual thresholds from MEG/EEG. <http://ieeexplore.ieee.org/abstract/document/6858510/>`_. Y Bekhti, N Zilber,F Pedregosa… - Pattern Recognition  …, 2014 - ieeexplore.ieee.org.
+181. `Covariance shrinkage for autocorrelated data. <http://papers.nips.cc/paper/5399-covariance-shrinkage-for-autocorrelated-data>`_. D Bartz,KR Müller- Advances in neural information processing  …, 2014 - papers.nips.cc.
+182. `Cortical oscillations as temporal reference frames for perception. <https://tel.archives-ouvertes.fr/tel-01069219/>`_. A Kosem- 2014 - tel.archives-ouvertes.fr.
+183. `Mapping tonotopic organization in human temporal cortex: representational similarity analysis in EMEG source space.. <http://europepmc.org/articles/pmc4228977>`_. L Su, I Zulfiqar, F Jamshed,E Fonteneau… - Frontiers in  …, 2013 - europepmc.org.
+184. `MEG and EEG data analysis with MNE-Python. <http://journal.frontiersin.org/article/10.3389/fnins.2013.00267/abstract>`_. A Gramfort,M Luessi,E Larson… - Frontiers in  …, 2013 - journal.frontiersin.org.
+185. `Interoperability of Free Software Packages to Analyze Functional Human Brain Data. <http://www.synesisjournal.com/vol4_g/Sander_2013_G85-89.pdf>`_. T Sander-Thömmes, A Schlögl - 2010 - synesisjournal.com.
+186. `The effect of alpha binaural beat on frontal esd alpha asymmetry on different gender. <http://www.arpnjournals.org/jeas/research_papers/rp_2016/jeas_0416_4042.pdf>`_. H Norhazman, N Mohamad Zaini,MN Taib… - 2006 - arpnjournals.org.
+187. `Brain Beats: Tempo extraction from EEG data. <https://www.audiolabs-erlangen.de/content/05-fau/professor/00-mueller/03-publications/2016_StoberPM_BeatEEG_ISMIR.pdf>`_. S Stober, T Prätzlich, M Meinard - Proceedings of the International  … - audiolabs-erlangen.de.
+188. `The Temporal Structure of Memory Retrieval1. <http://act-r.psy.cmu.edu/wordpress/wp-content/uploads/2016/11/meg21.pdf>`_. JR Anderson,JP Borst, JM Fincham,A Ghuman… - act-r.psy.cmu.edu.
+189. `Règles de sélection de variables pour accélerer la localisation de sources en MEG et EEG sous contrainte de parcimonie. <http://www.josephsalmon.eu/papers/gretsi2015.pdf>`_. O FERCOQ,A GRAMFORT,J SALMON- josephsalmon.eu.
+190. `Neuroplasticity in Human Alcoholism: Studies of Extended Abstinence with Potential Treatment Implications George Fein1, 2 and Valerie A. Cardenas1  …. <http://www.nbresearch.com/PDF/2014/Neuroplasticity%20in%20Human%20Alcoholism-%20Studies%20of%20Extended%20Abstinence%20with%20Potential%20Treatment%20Implications_Fein%20G,%20Cardenas%20V.pdf>`_. G Fein, AMP Center - nbresearch.com.
diff --git a/doc/conf.py b/doc/conf.py
index 8f93729..ce312da 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -12,11 +12,15 @@
 # All configuration values have a default; values that are commented out
 # serve to show the default.
 
-import sys
+import inspect
 import os
+from os.path import relpath, dirname
+import sys
 from datetime import date
-import sphinx_gallery
+import sphinx_gallery  # noqa
 import sphinx_bootstrap_theme
+from numpydoc import numpydoc, docscrape  # noqa
+import mne
 
 # If extensions (or modules to document with autodoc) are in another directory,
 # add these directories to sys.path here. If the directory is relative to the
@@ -25,7 +29,6 @@ curdir = os.path.dirname(__file__)
 sys.path.append(os.path.abspath(os.path.join(curdir, '..', 'mne')))
 sys.path.append(os.path.abspath(os.path.join(curdir, 'sphinxext')))
 
-import mne
 if not os.path.isdir('_images'):
     os.mkdir('_images')
 
@@ -34,26 +37,26 @@ if not os.path.isdir('_images'):
 # If your documentation needs a minimal Sphinx version, state it here.
 #needs_sphinx = '1.0'
 
+# XXX This hack defines what extra methods numpydoc will document
+docscrape.ClassDoc.extra_public_methods = mne.utils._doc_special_members
+
 # Add any Sphinx extension module names here, as strings. They can be
 # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
-from numpydoc import numpydoc, docscrape
-docscrape.ClassDoc.extra_public_methods = mne.utils._doc_special_members
 
 extensions = [
     'sphinx.ext.autodoc',
     'sphinx.ext.autosummary',
+    'sphinx.ext.coverage',
     'sphinx.ext.doctest',
     'sphinx.ext.intersphinx',
-    'sphinx.ext.todo',
-    'sphinx.ext.coverage',
+    'sphinx.ext.linkcode',
     'sphinx.ext.mathjax',
+    'sphinx.ext.todo',
     'sphinx_gallery.gen_gallery',
+    'numpydoc',
+    'gen_commands',
 ]
 
-extensions += ['numpydoc'] 
-extensions += ['gen_commands']  # auto generate the doc for the python commands
-# extensions += ['flow_diagram]  # generate flow chart in cookbook
-
 autosummary_generate = True
 autodoc_default_flags = ['inherited-members']
 
@@ -75,6 +78,10 @@ td = date.today()
 copyright = u'2012-%s, MNE Developers. Last updated on %s' % (td.year,
                                                               td.isoformat())
 
+nitpicky = True
+needs_sphinx = '1.5'
+suppress_warnings = ['image.nonlocal_uri']  # we intentionally link outside
+
 # The version info for the project you're documenting, acts as replacement for
 # |version| and |release|, also used in various other places throughout the
 # built documents.
@@ -118,7 +125,7 @@ exclude_patterns = ['source/generated']
 #show_authors = False
 
 # The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
+pygments_style = 'sphinx'  # friendly, manni, murphy, tango
 
 # A list of ignored prefixes for module index sorting.
 modindex_common_prefix = ['mne.']
@@ -137,18 +144,19 @@ html_theme = 'bootstrap'
 # further.  For a list of options available for each theme, see the
 # documentation.
 html_theme_options = {
-    'navbar_title': ' ',
-    'source_link_position': "footer",
-    'bootswatch_theme': "flatly",
-    'navbar_sidebarrel': False,
-    'bootstrap_version': "3",
+    'navbar_title': ' ',  # we replace this with an image
+    'source_link_position': "nav",  # default
+    'bootswatch_theme': "flatly",  # yeti paper lumen
+    'navbar_sidebarrel': False,  # Render the next/prev links in navbar?
+    'navbar_pagenav': False,
+    'navbar_class': "navbar",
+    'bootstrap_version': "3",  # default
     'navbar_links': [
-        ("Get started", "getting_started"),
-        ("Tutorials", "tutorials"),
-        ("Gallery", "auto_examples/index"),
+        ("Install", "getting_started"),
+        ("Documentation", "documentation"),
         ("API", "python_reference"),
-        ("Manual", "manual/index"),
-        ("FAQ", "faq"),
+        ("Examples", "auto_examples/index"),
+        ("Contribute", "contributing"),
     ],
     }
 
@@ -254,7 +262,7 @@ latex_logo = "_static/logo.png"
 
 # For "manual" documents, if this is true, then toplevel headings are parts,
 # not chapters.
-latex_use_parts = True
+latex_toplevel_sectioning = 'part'
 
 # Additional stuff for the LaTeX preamble.
 # latex_preamble = ''
@@ -269,16 +277,22 @@ trim_doctests_flags = True
 
 # Example configuration for intersphinx: refer to the Python standard library.
 intersphinx_mapping = {
-    'python': ('http://docs.python.org/', None),
-    'numpy': ('http://docs.scipy.org/doc/numpy-dev/', None),
-    'scipy': ('http://scipy.github.io/devdocs/', None),
+    'python': ('http://docs.python.org', None),
+    'numpy': ('http://docs.scipy.org/doc/numpy-dev', None),
+    'scipy': ('http://scipy.github.io/devdocs', None),
+    'matplotlib': ('http://matplotlib.org', None),
+    'sklearn': ('http://scikit-learn.org/stable', None),
+    'mayavi': ('http://docs.enthought.com/mayavi/mayavi', None),
+    'nibabel': ('http://nipy.org/nibabel', None),
+    'nilearn': ('http://nilearn.github.io', None),
+    'surfer': ('https://pysurfer.github.io/', None),
 }
 
 examples_dirs = ['../examples', '../tutorials']
 gallery_dirs = ['auto_examples', 'auto_tutorials']
 
 try:
-    from mayavi import mlab
+    mlab = mne.utils._import_mlab()
     find_mayavi_figures = True
     # Do not pop up any mayavi windows while running the
     # examples. These are very annoying since they steal the focus.
@@ -290,15 +304,74 @@ sphinx_gallery_conf = {
     'doc_module': ('mne',),
     'reference_url': {
         'mne': None,
-        'matplotlib': 'http://matplotlib.org',
-        'numpy': 'http://docs.scipy.org/doc/numpy-1.10.1',
-        'scipy': 'http://docs.scipy.org/doc/scipy-0.17.0/reference',
-        'mayavi': 'http://docs.enthought.com/mayavi/mayavi'},
+        },
     'examples_dirs': examples_dirs,
     'gallery_dirs': gallery_dirs,
     'find_mayavi_figures': find_mayavi_figures,
     'default_thumb_file': os.path.join('_static', 'mne_helmet.png'),
-    'mod_example_dir': 'generated',
-    }
+    'backreferences_dir': 'generated',
+    'plot_gallery': 'True',  # Avoid annoying Unicode/bool default warning
+    'download_section_examples': False,
+    'thumbnail_size': (160, 112),
+    'min_reported_time': 1.,
+    'abort_on_example_error': False,
+}
 
 numpydoc_class_members_toctree = False
+
+
+# -----------------------------------------------------------------------------
+# Source code links (adapted from SciPy (doc/source/conf.py))
+# -----------------------------------------------------------------------------
+
+def linkcode_resolve(domain, info):
+    """
+    Determine the URL corresponding to Python object
+    """
+    if domain != 'py':
+        return None
+
+    modname = info['module']
+    fullname = info['fullname']
+
+    submod = sys.modules.get(modname)
+    if submod is None:
+        return None
+
+    obj = submod
+    for part in fullname.split('.'):
+        try:
+            obj = getattr(obj, part)
+        except:
+            return None
+
+    try:
+        fn = inspect.getsourcefile(obj)
+    except:
+        fn = None
+    if not fn:
+        try:
+            fn = inspect.getsourcefile(sys.modules[obj.__module__])
+        except:
+            fn = None
+    if not fn:
+        return None
+
+    try:
+        source, lineno = inspect.getsourcelines(obj)
+    except:
+        lineno = None
+
+    if lineno:
+        linespec = "#L%d-L%d" % (lineno, lineno + len(source) - 1)
+    else:
+        linespec = ""
+
+    fn = relpath(fn, start=dirname(mne.__file__))
+
+    if 'dev' in mne.__version__:
+        kind = 'master'
+    else:
+        kind = 'maint/%s' % ('.'.join(mne.__version__.split('.')[:2]))
+    return "http://github.com/mne-tools/mne-python/blob/%s/mne/%s%s" % (  # noqa
+       kind, fn, linespec)
diff --git a/doc/contributing.rst b/doc/configure_git.rst
similarity index 58%
copy from doc/contributing.rst
copy to doc/configure_git.rst
index 47383c3..02c3f1a 100644
--- a/doc/contributing.rst
+++ b/doc/configure_git.rst
@@ -1,209 +1,13 @@
-.. _contributing:
+:orphan:
 
-Contribute to MNE
-=================
-
-.. contents:: Contents
-   :local:
-   :depth: 1
-
-.. We want to thank all MNE Software users at the Martinos Center and
-.. in other institutions for their collaboration during the creation
-.. of this software as well as for useful comments on the software
-.. and its documentation.
-
-We are open to all types of contributions, from bugfixes to functionality
-enhancements. mne-python_ is meant to be maintained by a community of labs,
-and as such, we seek enhancements that will likely benefit a large proportion
-of the users who use the package.
-
-*Before starting new code*, we highly recommend opening an issue on
-`mne-python GitHub`_ to discuss potential changes. Getting on the same
-page as the maintainers about changes or enhancements before too much
-coding is done saves everyone time and effort!
-
-What you will need
-------------------
-
-#. A good python editor: Atom_ and `Sublime Text`_ are modern general-purpose
-   text editors and are available on all three major platforms. Both provide
-   plugins that facilitate editing python code and help avoid bugs and style
-   errors. See for example linterflake8_ for Atom_.
-   The Spyder_ IDE is especially suitable for those migrating from Matlab.
-   EPD_ and Anaconda_ both ship Spyder and all its dependencies.
-   As always, Vim or Emacs will suffice as well.
-
-#. Basic scientific tools in python: numpy_, scipy_, matplotlib_
-
-#. Development related tools: nosetests_, coverage_, nose-timer_, mayavi_, sphinx_,
-   pep8_, and pyflakes_
-
-#. Other useful packages: pysurfer_, nitime_, pandas_, PIL_, PyDICOM_,
-   joblib_, nibabel_, h5py_, and scikit-learn_
-
-#. `MNE command line utilities`_ and FreeSurfer_ are optional but will allow you
-   to make the best out of MNE. Yet they will require a Unix (Linux or Mac OS)
-   system. If you are on Windows, you can install these applications inside a
-   Unix virtual machine.
-
-#. Documentation building packages ``numpydoc``, ``sphinx_bootstrap_theme`` and
-   ``sphinx_gallery``.
-
-General code guidelines
------------------------
-
-* We highly recommend using a code editor that uses both `pep8`_ and
-  `pyflakes`_, such as `Spyder`_. Standard python style guidelines are
-  followed, with very few exceptions.
-
-  You can also manually check pyflakes and pep8 warnings as:
-
-  .. code-block:: bash
-
-     $ pip install pyflakes
-     $ pip install pep8
-     $ pyflakes path/to/module.py
-     $ pep8 path/to/module.py
-
-  AutoPEP8 can then help you fix some of the easy redundant errors:
-
-  .. code-block:: bash
-
-     $ pip install autopep8
-     $ autopep8 path/to/pep8.py
-
-* mne-python adheres to the same docstring formatting as seen on
-  `numpy style`_.
-  New public functions should have all variables defined. The test suite
-  has some functionality that checks docstrings, but docstrings should
-  still be checked for clarity, uniformity, and completeness.
-
-* New functionality should be covered by appropriate tests, e.g. a method in
-  ``mne/evoked.py`` should have a corresponding test in
-  ``mne/tests/test_evoked.py``. You can use the ``coverage`` module in
-  conjunction with ``nosetests`` (nose can automatically determine the code
-  coverage if ``coverage`` is installed) to see how well new code is covered.
-  The ambition is to achieve around 85% coverage with tests.
-
-* After changes have been made, **ensure all tests pass**. This can be done
-  by running the following from the ``mne-python`` root directory:
-
-  .. code-block:: bash
-
-     $ make
-
-  To run individual tests, you can also run any of the following:
-
-  .. code-block:: bash
-
-     $ make clean
-     $ make inplace
-     $ make test-doc
-     $ make inplace
-     $ nosetests
-
-  To explicitly download and extract the mne-python testing dataset (~320 MB)
-  run:
-
-  .. code-block:: bash
-
-     make testing_data
-
-  Alternatively:
-
-  .. code-block:: bash
-
-     $ python -c "import mne; mne.datasets.testing.data_path(verbose=True)"
-
-  downloads the test data as well. Having a complete testing dataset is
-  necessary for running the tests. To run the examples you'll need
-  the `mne-python sample dataset`_ which is automatically downloaded
-  when running an example for the first time.
-
-  You can also run ``nosetests -x`` to have nose stop as soon as a failed
-  test is found, or run e.g., ``nosetests mne/tests/test_event.py`` to run
-  a specific test. In addition, one can run individual tests from python::
-
-     >>> from mne.utils import run_tests_if_main
-     >>> run_tests_if_main()
-
-  For more details see troubleshooting_.
-  
-* Update relevant documentation. Update :doc:`whats_new.rst <whats_new>` for new features and :doc:`python_reference.rst <python_reference>` for new classes and standalone functions. :doc:`whats_new.rst <whats_new>` is organized in chronological order with the last feature at the end of the document.
-
-Checking and building documentation
------------------------------------
-
-All changes to the codebase must be properly documented.
-To ensure that documentation is rendered correctly, the best bet is to
-follow the existing examples for class and function docstrings,
-and examples and tutorials.
-
-Our documentation (including docstring in code) uses ReStructuredText format,
-see `Sphinx documentation`_ to learn more about editing them. Our code
-follows the `NumPy docstring standard`_.
-
-To test documentation locally, you will need to install (e.g., via ``pip``):
-
-  * sphinx
-  * sphinx-gallery
-  * sphinx_bootstrap_theme
-  * numpydoc
-
-Then to build the documentation locally, within the ``mne/doc`` directory do:
-
-.. code-block:: bash
-
-    $ make html-noplot
-
-This will build the docs without building all the examples, which can save
-some time. If you are working on examples or tutorials, you can build
-specific examples with e.g.:
-
-.. code-block:: bash
-
-   $ PATTERN=plot_background_filtering.py make html_dev-pattern
-
-Consult the `sphinx gallery documentation`_ for more details.
-
-MNE-Python specific coding guidelines
--------------------------------------
-
-* Please, ideally address one and only one issue per pull request (PR).
-* Avoid unnecessary cosmetic changes if they are not the goal of the PR, this will help keep the diff clean and facilitate reviewing.
-* Use underscores to separate words in non class names: n_samples rather than nsamples.
-* Use CamelCase for class names.
-* Use relative imports for references inside mne-python.
-* Use nested imports (i.e., within a function or method instead of at the top of a file) for ``matplotlib``, ``sklearn``, and ``pandas``.
-* Use ``RdBu_r`` colormap for signed data and ``Reds`` for unsigned data in visualization functions and examples.
-* All visualization functions must accept a ``show`` parameter and return a ``fig`` handle.
-* Efforts to improve test timing without decreasing coverage is well appreciated. To see the top-30 tests in order of decreasing timing, run the following command:
-
-  .. code-block:: bash
-
-     $ nosetests --with-timer --timer-top-n 30
-
-* Instance methods that update the state of the object should return self.
-* Use single quotes whenever possible.
-* Prefer generator or list comprehensions over ``filter``, ``map`` and other functional idioms.
-* Use explicit functional constructors for builtin containers to improve readability. E.g. ``list()``, ``dict()``.
-* Avoid nested functions if not necessary and use private functions instead.
-* When adding visualization methods, add public functions to the mne.viz package and use these in the corresponding method.
-* If not otherwise required, methods should deal with state while functions should return copies. There are a few justified exceptions though, e.g. ``equalize_channels``, for memory reasons for example.
-* Update the whats_new.rst file at the end, otherwise merge conflicts are guaranteed to occur.
-* Avoid ``**kwargs`` and ``*args`` in function signatures, they are not user friendly (inspection).
-* Avoid single character variable names if you can. They are not readable and often they don't comply with the builtin debugger.
-* Add at least some brief comment to a private function to help us guess what it does. For complex private functions please write a full documentation.
-
-Profiling in Python
--------------------
+.. include:: links.inc
 
-To learn more about profiling python codes please see `the scikit learn profiling site <http://scikit-learn.org/stable/developers/performance.html#performance-howto>`_.
+.. _using_github:
 
-Configuring git
----------------
+Using GitHub to make a Pull Request
+===================================
 
-Any contributions to the core mne-python package, whether bug fixes,
+Any contributions to MNE, whether bug fixes,
 improvements to the documentation, or new functionality, can be done via
 *pull requests* on GitHub. The workflow for this is described here.
 [Many thanks to Astropy_ for providing clear instructions that we have
@@ -212,7 +16,7 @@ adapted for our use here!]
 The only absolutely necessary configuration step is identifying yourself and
 your contact info:
 
-.. code-block:: bash
+.. code-block:: console
 
    $ git config --global user.name "Your Name"
    $ git config --global user.email you at yourdomain.example.com
@@ -227,7 +31,7 @@ However, you can also directly go to the `GitHub help pages
 <https://help.github.com/>`_ which offer a great introduction to git and
 GitHub.
 
-In the present document, we refer to the MNE-Python ``master`` branch, as the
+In the present document, we refer to the ``mne-python/master`` branch, as the
 *trunk*.
 
 .. _forking:
@@ -237,7 +41,7 @@ Creating a fork
 
 You need to do this only once for each package you want to contribute to. The
 instructions here are very similar to the instructions at
-https://help.github.com/fork-a-repo/ |emdash| please see that page for more
+https://help.github.com/fork-a-repo/ -- please see that page for more
 details. We're repeating some of it here just to give the specifics for the
 mne-python_ project, and to suggest some default names.
 
@@ -248,14 +52,13 @@ Set up and configure a GitHub account
 
 If you don't have a GitHub account, go to the GitHub page, and make one.
 
-You then need to configure your account to allow write access |emdash| see
+You then need to configure your account to allow write access -- see
 the *Generating SSH keys* help on `GitHub Help`_.
 
 Create your own fork of a repository
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-Now you should fork the core ``mne-python`` repository (although you could
-in principle also fork a different one, such as ``mne-matlab```):
+Now you should fork the core ``mne-python.git`` repository:
 
 #. Log into your GitHub account.
 
@@ -273,7 +76,7 @@ Setting up the fork and the working directory
 
 Briefly, this is done using:
 
-.. code-block:: bash
+.. code-block:: console
 
    $ git clone git at github.com:your-user-name/mne-python.git
    $ cd mne-python
@@ -283,19 +86,19 @@ These steps can be broken out to be more explicit as:
 
 #. Clone your fork to the local computer:
 
-   .. code-block:: bash
+   .. code-block:: console
 
       $ git clone git at github.com:your-user-name/mne-python.git
 
 #. Change directory to your new repo:
 
-   .. code-block:: bash
+   .. code-block:: console
 
       $ cd mne-python
 
    Then type:
 
-   .. code-block:: bash
+   .. code-block:: console
 
       $ git branch -a
 
@@ -312,7 +115,7 @@ These steps can be broken out to be more explicit as:
    Now you want to connect to the mne-python repository, so you can
    merge in changes from the trunk:
 
-   .. code-block:: bash
+   .. code-block:: console
 
       $ cd mne-python
       $ git remote add upstream git://github.com/mne-tools/mne-python.git
@@ -335,30 +138,27 @@ These steps can be broken out to be more explicit as:
 
    Your fork is now set up correctly.
 
-#. Install mne with editing permissions to the installed folder:
+#. Install mne with editing permissions using the installed folder:
 
-   To be able to conveniently edit your files after installing mne-python,
+   To be able to conveniently edit your files after installing mne,
    install using the following setting:
 
-   .. code-block:: bash
+   .. code-block:: console
 
       $ python setup.py develop --user
 
    To make changes in the code, edit the relevant files and restart the
    ipython kernel for changes to take effect.
 
-#. Ensure unit tests pass and html files can be compiled
+#. Ensure unit tests pass
 
-   Make sure before starting to code that all unit tests pass and the
-   html files in the ``doc/`` directory can be built without errors. To build
-   the html files, first go the ``doc/`` directory and then type:
+   Make sure before starting to code that all unit tests pass with `pytest`_:
 
-   .. code-block:: bash
+   .. code-block:: console
 
-      $ make html
+      $ make test
 
-   Once it is compiled for the first time, subsequent compiles will only
-   recompile what has changed. That's it! You are now ready to hack away.
+   That's it! You are now ready to hack away.
 
 Workflow summary
 ----------------
@@ -372,7 +172,7 @@ sections.
 * When you are starting a new set of changes, fetch any changes from the
   trunk, and start a new *feature branch* from that.
 
-* Make a new branch for each separable set of changes |emdash| "one task, one
+* Make a new branch for each separable set of changes -- "one task, one
   branch" (`ipython git workflow`_).
 
 * Name your branch for the purpose of the changes - e.g.
@@ -407,7 +207,7 @@ Updating the mirror of trunk
 
 From time to time you should fetch the upstream (trunk) changes from GitHub:
 
-.. code-block:: bash
+.. code-block:: console
 
    $ git fetch upstream
 
@@ -432,7 +232,7 @@ Choose an informative name for the branch to remind yourself and the rest of
 us what the changes in the branch are for. For example ``add-ability-to-fly``,
 or ``buxfix-for-issue-42``.
 
-.. code-block:: bash
+.. code-block:: console
 
    # Update the mirror of trunk
    $ git fetch upstream
@@ -447,14 +247,14 @@ github repo. Generally (if you followed the instructions in these pages, and
 by default), git will have a link to your GitHub repo, called ``origin``. You
 push up to your own repo on GitHub with:
 
-.. code-block:: bash
+.. code-block:: console
 
    $ git push origin my-new-feature
 
 In git > 1.7 you can ensure that the link is correctly set by using the
 ``--set-upstream`` option:
 
-.. code-block:: bash
+.. code-block:: console
 
    $ git push --set-upstream origin my-new-feature
 
@@ -469,7 +269,7 @@ The editing workflow
 Overview
 ^^^^^^^^
 
-.. code-block:: bash
+.. code-block:: console
 
    $ git add my_new_file
    $ git commit -am 'FIX: some message'
@@ -559,87 +359,9 @@ When you are ready to ask for someone to review your code and consider a merge:
 
 
 If you are uncertain about what would or would not be appropriate to contribute
-to mne-python, don't hesitate to either send a pull request, or open an issue
+to MNE, don't hesitate to either send a pull request, or open an issue
 on the mne-python_ GitHub site to discuss potential changes.
 
-Some other things you might want to do
---------------------------------------
-
-Delete a branch on GitHub
-^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: bash
-
-   # change to the master branch (if you still have one, otherwise change to another branch)
-   $ git checkout master
-
-   # delete branch locally
-   $ git branch -D my-unwanted-branch
-
-   # delete branch on GitHub
-   $ git push origin :my-unwanted-branch
-
-(Note the colon ``:`` before ``test-branch``.  See also:
-https://help.github.com/remotes)
-
-Several people sharing a single repository
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-If you want to work on some stuff with other people, where you are all
-committing into the same repository, or even the same branch, then just
-share it via GitHub.
-
-First fork mne-python into your account, as from :ref:`forking`.
-
-Then, go to your forked repository GitHub page, say
-``http://github.com/your-user-name/mne-python``
-
-Click on the 'Admin' button, and add anyone else to the repo as a
-collaborator:
-
-   .. image:: _static/pull_button.png
-
-Now all those people can do:
-
-.. code-block:: bash
-
-   $ git clone git at githhub.com:your-user-name/mne-python.git
-
-Remember that links starting with ``git@`` use the ssh protocol and are
-read-write; links starting with ``git://`` are read-only.
-
-Your collaborators can then commit directly into that repo with the
-usual:
-
-.. code-block:: bash
-
-   $ git commit -am 'ENH: much better code'
-   $ git push origin master # pushes directly into your repo
-
-Explore your repository
-^^^^^^^^^^^^^^^^^^^^^^^
-
-To see a graphical representation of the repository branches and
-commits:
-
-.. code-block:: bash
-
-   $ gitk --all
-
-To see a linear list of commits for this branch:
-
-.. code-block:: bash
-
-   $ git log
-
-You can also look at the `network graph visualizer`_ for your GitHub
-repo.
-
-Finally the ``lg`` alias will give you a reasonable text-based graph of the
-repository.
-
-If you are making extensive changes, ``git grep`` is also very handy.
-
 .. _rebase-on-trunk:
 
 Rebasing on trunk
@@ -676,7 +398,7 @@ See `rebase without tears`_ for more detail.
 
 To do a rebase on trunk:
 
-.. code-block:: bash
+.. code-block:: console
 
     # Update the mirror of trunk
     $ git fetch upstream
@@ -693,13 +415,13 @@ To do a rebase on trunk:
 In this situation, where you are already on branch ``cool-feature``, the last
 command can be written more succinctly as:
 
-.. code-block:: bash
+.. code-block:: console
 
     $ git rebase upstream/master
 
 When all looks good you can delete your backup branch:
 
-.. code-block:: bash
+.. code-block:: console
 
    $ git branch -D tmp
 
@@ -717,7 +439,7 @@ push the branch; a normal push would give an error. If the branch you rebased is
 called ``cool-feature`` and your GitHub fork is available as the remote called ``origin``,
 you use this command to force-push:
 
-.. code-block:: bash
+.. code-block:: console
 
    $ git push -f origin cool-feature
 
@@ -737,20 +459,20 @@ straightforward to recover from such mistakes.
 
 If you mess up during a rebase:
 
-.. code-block:: bash
+.. code-block:: console
 
    $ git rebase --abort
 
 If you notice you messed up after the rebase:
 
-.. code-block:: bash
+.. code-block:: console
 
    # Reset branch back to the saved point
    $ git reset --hard tmp
 
 If you forgot to make a backup branch:
 
-.. code-block:: bash
+.. code-block:: console
 
    # Look at the reflog of the branch
    $ git reflog show cool-feature
@@ -782,7 +504,7 @@ This can be done via *interactive rebasing*.
 
 Suppose that the commit history looks like this:
 
-.. code-block:: bash
+.. code-block:: console
 
     $ git log --oneline
     eadc391 Fix some remaining bugs
@@ -801,7 +523,7 @@ want to make the following changes:
 
 We do as follows:
 
-.. code-block:: bash
+.. code-block:: console
 
     # make a backup of the current state
     $ git branch tmp HEAD
@@ -863,14 +585,14 @@ Fetching a pull request
 To fetch a pull request on the main repository to your local working
 directory as a new branch, just do:
 
-.. code-block:: bash
+.. code-block:: console
 
    $ git fetch upstream pull/<pull request number>/head:<local-branch>
 
 As an example, to pull the realtime pull request which has a url
 ``https://github.com/mne-tools/mne-python/pull/615/``, do:
 
-.. code-block:: bash
+.. code-block:: console
 
    $ git fetch upstream pull/615/head:realtime
 
@@ -885,40 +607,3 @@ The builds when the pull request is in `WIP` state can be safely skipped. The im
   FIX: some changes [ci skip]
 
 This will help prevent clogging up Travis and Appveyor and also save the environment.
-
-.. _troubleshooting:
-
-Troubleshooting
----------------
-
-Listed below are miscellaneous issues that you might face:
-
-Missing files in examples or unit tests
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-If the unit tests fail due to missing files, you may need to run
-`mne-scripts`_ on the sample dataset. Go to ``bash`` if you are using some
-other shell. Then, execute all three shell scripts in the
-``sample-data/`` directory within ``mne-scripts/``.
-
-Cannot import class from a new \*.py file
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-You need to update the corresponding ``__init__.py`` file and then
-restart the ipython kernel.
-
-ICE default IO error handler doing an exit()
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-If the make test command fails with the error
-``ICE default IO error handler doing an exit()``, try backing up or removing
-.ICEauthority:
-
-.. code-block:: bash
-
-   $ mv ~/.ICEauthority ~/.ICEauthority.bak
-
-.. include:: links.inc
-.. _Sphinx documentation: http://sphinx-doc.org/rest.html
-.. _sphinx gallery documentation: http://sphinx-gallery.readthedocs.org/en/latest/advanced_configuration.html
-.. _NumPy docstring standard: https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMENT.rst.txt
diff --git a/doc/contributing.rst b/doc/contributing.rst
index 47383c3..3407cb6 100644
--- a/doc/contributing.rst
+++ b/doc/contributing.rst
@@ -1,18 +1,17 @@
-.. _contributing:
+:orphan:
 
-Contribute to MNE
-=================
+.. include:: links.inc
+
+.. _contribute_to_mne:
+
+How to contribute to MNE
+========================
 
 .. contents:: Contents
    :local:
    :depth: 1
 
-.. We want to thank all MNE Software users at the Martinos Center and
-.. in other institutions for their collaboration during the creation
-.. of this software as well as for useful comments on the software
-.. and its documentation.
-
-We are open to all types of contributions, from bugfixes to functionality
+We are open to many types of contributions, from bugfixes to functionality
 enhancements. mne-python_ is meant to be maintained by a community of labs,
 and as such, we seek enhancements that will likely benefit a large proportion
 of the users who use the package.
@@ -22,114 +21,91 @@ of the users who use the package.
 page as the maintainers about changes or enhancements before too much
 coding is done saves everyone time and effort!
 
-What you will need
-------------------
-
-#. A good python editor: Atom_ and `Sublime Text`_ are modern general-purpose
-   text editors and are available on all three major platforms. Both provide
-   plugins that facilitate editing python code and help avoid bugs and style
-   errors. See for example linterflake8_ for Atom_.
-   The Spyder_ IDE is especially suitable for those migrating from Matlab.
-   EPD_ and Anaconda_ both ship Spyder and all its dependencies.
-   As always, Vim or Emacs will suffice as well.
-
-#. Basic scientific tools in python: numpy_, scipy_, matplotlib_
-
-#. Development related tools: nosetests_, coverage_, nose-timer_, mayavi_, sphinx_,
-   pep8_, and pyflakes_
-
-#. Other useful packages: pysurfer_, nitime_, pandas_, PIL_, PyDICOM_,
-   joblib_, nibabel_, h5py_, and scikit-learn_
-
-#. `MNE command line utilities`_ and FreeSurfer_ are optional but will allow you
-   to make the best out of MNE. Yet they will require a Unix (Linux or Mac OS)
-   system. If you are on Windows, you can install these applications inside a
-   Unix virtual machine.
-
-#. Documentation building packages ``numpydoc``, ``sphinx_bootstrap_theme`` and
-   ``sphinx_gallery``.
-
-General code guidelines
------------------------
-
-* We highly recommend using a code editor that uses both `pep8`_ and
-  `pyflakes`_, such as `Spyder`_. Standard python style guidelines are
-  followed, with very few exceptions.
-
-  You can also manually check pyflakes and pep8 warnings as:
-
-  .. code-block:: bash
+Code guidelines
+---------------
 
-     $ pip install pyflakes
-     $ pip install pep8
-     $ pyflakes path/to/module.py
-     $ pep8 path/to/module.py
+* Standard python style guidelines set by pep8_ and pyflakes_ are followed
+  with very few exceptions. We recommend using an editor that calls out
+  style violations automatcally, such as Spyder_. From the MNE code root, you
+  can check for violations using flake8_ with:
 
-  AutoPEP8 can then help you fix some of the easy redundant errors:
+  .. code-block:: console
 
-  .. code-block:: bash
+     $ make flake
 
-     $ pip install autopep8
-     $ autopep8 path/to/pep8.py
+* Use `numpy style`_ for docstrings. Follow existing examples for simplest
+  guidance.
 
-* mne-python adheres to the same docstring formatting as seen on
-  `numpy style`_.
-  New public functions should have all variables defined. The test suite
-  has some functionality that checks docstrings, but docstrings should
-  still be checked for clarity, uniformity, and completeness.
+* New functionality must be covered by tests. For example, a
+  :class:`mne.Evoked` method in ``mne/evoked.py`` should have a corresponding
+  test in ``mne/tests/test_evoked.py``.
 
-* New functionality should be covered by appropriate tests, e.g. a method in
-  ``mne/evoked.py`` should have a corresponding test in
-  ``mne/tests/test_evoked.py``. You can use the ``coverage`` module in
-  conjunction with ``nosetests`` (nose can automatically determine the code
-  coverage if ``coverage`` is installed) to see how well new code is covered.
-  The ambition is to achieve around 85% coverage with tests.
+* Changes must be accompanied by updated documentation, including
+  :doc:`doc/whats_new.rst <whats_new>` and
+  :doc:`doc/python_reference.rst <python_reference>`.
 
-* After changes have been made, **ensure all tests pass**. This can be done
-  by running the following from the ``mne-python`` root directory:
+* After making changes, **ensure all tests pass**. This can be done
+  by running:
 
-  .. code-block:: bash
+  .. code-block:: console
 
-     $ make
+     $ make test
 
-  To run individual tests, you can also run any of the following:
+  To run individual tests, you can also run e.g.:
 
-  .. code-block:: bash
+  .. code-block:: console
 
-     $ make clean
-     $ make inplace
-     $ make test-doc
-     $ make inplace
-     $ nosetests
+     $ pytest mne/tests/test_evoked.py:test_io_evoked -x --verbose
 
-  To explicitly download and extract the mne-python testing dataset (~320 MB)
-  run:
+  Make sure you have the testing dataset, which you can get by doing::
 
-  .. code-block:: bash
+     >>> mne.datasets.testing.data_path(verbose=True)  # doctest: +SKIP
 
-     make testing_data
+MNE-specific coding guidelines
+------------------------------
 
-  Alternatively:
+These are guidelines that are generally followed:
 
-  .. code-block:: bash
+Pull requests
+^^^^^^^^^^^^^
+* Address one issue per pull request (PR).
+* Avoid unnecessary cosmetic changes in PRs.
+* Minimize test timing while maximizing coverage. Use ``pytest --durations=20`` on modified tests.
+* Update the ``doc/whats_new.rst`` file last, just before merge to avoid merge conflicts.
 
-     $ python -c "import mne; mne.datasets.testing.data_path(verbose=True)"
+Naming
+^^^^^^
+* Classes should be named using CamelCase.
+* Functions and instances/variables should be snake_case (n_samples rather than nsamples).
+* Avoid single-character variable names.
 
-  downloads the test data as well. Having a complete testing dataset is
-  necessary for running the tests. To run the examples you'll need
-  the `mne-python sample dataset`_ which is automatically downloaded
-  when running an example for the first time.
+Importing
+^^^^^^^^^
+* Import modules in this order:
+  1. builtin
+  2. standard scientific (``numpy as np``, ``scipy`` submodules)
+  3. others
+  4. mne imports (relative within the MNE module, absolute in the examples)
+* Imports for ``matplotlib`` and optional modules (``sklearn``, and ``pandas``, etc.) within the MNE module should be nested (i.e., within a function or method, not at the top of a file).
 
-  You can also run ``nosetests -x`` to have nose stop as soon as a failed
-  test is found, or run e.g., ``nosetests mne/tests/test_event.py`` to run
-  a specific test. In addition, one can run individual tests from python::
+Vizualization
+^^^^^^^^^^^^^
+* Add public functions to the :mod:`mne.viz` package and use these in the corresponding methods.
+* All visualization functions must accept a ``show`` parameter and return a ``fig`` handle.
+* Use ``RdBu_r`` colormap for signed data with a meaningful middle (zero-point) and ``Reds`` otherwise in visualization functions and examples.
 
-     >>> from mne.utils import run_tests_if_main
-     >>> run_tests_if_main()
+Return types
+^^^^^^^^^^^^
+* Methods should modify inplace and return ``self``, functions should return copies (where applicable).
 
-  For more details see troubleshooting_.
-  
-* Update relevant documentation. Update :doc:`whats_new.rst <whats_new>` for new features and :doc:`python_reference.rst <python_reference>` for new classes and standalone functions. :doc:`whats_new.rst <whats_new>` is organized in chronological order with the last feature at the end of the document.
+Style
+^^^^^
+* Use single quotes whenever possible.
+* Prefer generator or list comprehensions over ``filter``, ``map`` and other functional idioms.
+* Use explicit functional constructors for builtin containers to improve readability (e.g., ``list()``, ``dict``).
+* Avoid nested functions or class methods if possible -- use private functions instead.
+* Avoid ``**kwargs`` and ``*args`` in function signatures.
+* Add brief docstrings to simple private functions and complete docstrings for complex ones.
 
 Checking and building documentation
 -----------------------------------
@@ -143,782 +119,57 @@ Our documentation (including docstring in code) uses ReStructuredText format,
 see `Sphinx documentation`_ to learn more about editing them. Our code
 follows the `NumPy docstring standard`_.
 
-To test documentation locally, you will need to install (e.g., via ``pip``):
-
-  * sphinx
-  * sphinx-gallery
-  * sphinx_bootstrap_theme
-  * numpydoc
+Documentation is automatically built remotely during pull requests. If
+you want to also test documentation locally, you will need to install
+``sphinx sphinx-gallery sphinx_bootstrap_theme numpydoc``, and then within
+the ``mne/doc`` directory do:
 
-Then to build the documentation locally, within the ``mne/doc`` directory do:
+.. code-block:: console
 
-.. code-block:: bash
+   $ make html_dev-noplot
 
-    $ make html-noplot
+If you are working on examples or tutorials, you can build specific examples
+with:
 
-This will build the docs without building all the examples, which can save
-some time. If you are working on examples or tutorials, you can build
-specific examples with e.g.:
-
-.. code-block:: bash
+.. code-block:: console
 
    $ PATTERN=plot_background_filtering.py make html_dev-pattern
 
 Consult the `sphinx gallery documentation`_ for more details.
 
-MNE-Python specific coding guidelines
--------------------------------------
-
-* Please, ideally address one and only one issue per pull request (PR).
-* Avoid unnecessary cosmetic changes if they are not the goal of the PR, this will help keep the diff clean and facilitate reviewing.
-* Use underscores to separate words in non class names: n_samples rather than nsamples.
-* Use CamelCase for class names.
-* Use relative imports for references inside mne-python.
-* Use nested imports (i.e., within a function or method instead of at the top of a file) for ``matplotlib``, ``sklearn``, and ``pandas``.
-* Use ``RdBu_r`` colormap for signed data and ``Reds`` for unsigned data in visualization functions and examples.
-* All visualization functions must accept a ``show`` parameter and return a ``fig`` handle.
-* Efforts to improve test timing without decreasing coverage is well appreciated. To see the top-30 tests in order of decreasing timing, run the following command:
-
-  .. code-block:: bash
-
-     $ nosetests --with-timer --timer-top-n 30
-
-* Instance methods that update the state of the object should return self.
-* Use single quotes whenever possible.
-* Prefer generator or list comprehensions over ``filter``, ``map`` and other functional idioms.
-* Use explicit functional constructors for builtin containers to improve readability. E.g. ``list()``, ``dict()``.
-* Avoid nested functions if not necessary and use private functions instead.
-* When adding visualization methods, add public functions to the mne.viz package and use these in the corresponding method.
-* If not otherwise required, methods should deal with state while functions should return copies. There are a few justified exceptions though, e.g. ``equalize_channels``, for memory reasons for example.
-* Update the whats_new.rst file at the end, otherwise merge conflicts are guaranteed to occur.
-* Avoid ``**kwargs`` and ``*args`` in function signatures, they are not user friendly (inspection).
-* Avoid single character variable names if you can. They are not readable and often they don't comply with the builtin debugger.
-* Add at least some brief comment to a private function to help us guess what it does. For complex private functions please write a full documentation.
-
-Profiling in Python
--------------------
-
-To learn more about profiling python codes please see `the scikit learn profiling site <http://scikit-learn.org/stable/developers/performance.html#performance-howto>`_.
-
-Configuring git
----------------
-
-Any contributions to the core mne-python package, whether bug fixes,
-improvements to the documentation, or new functionality, can be done via
-*pull requests* on GitHub. The workflow for this is described here.
-[Many thanks to Astropy_ for providing clear instructions that we have
-adapted for our use here!]
-
-The only absolutely necessary configuration step is identifying yourself and
-your contact info:
-
-.. code-block:: bash
-
-   $ git config --global user.name "Your Name"
-   $ git config --global user.email you at yourdomain.example.com
-
-If you are going to :ref:`setup-github` eventually, this email address should
-be the same as the one used to sign up for a GitHub account. For more
-information about configuring your git installation, see :ref:`customizing-git`.
-
-The following sections cover the installation of the git software, the basic
-configuration, and links to resources to learn more about using git.
-However, you can also directly go to the `GitHub help pages
-<https://help.github.com/>`_ which offer a great introduction to git and
-GitHub.
-
-In the present document, we refer to the MNE-Python ``master`` branch, as the
-*trunk*.
-
-.. _forking:
-
-Creating a fork
-^^^^^^^^^^^^^^^
-
-You need to do this only once for each package you want to contribute to. The
-instructions here are very similar to the instructions at
-https://help.github.com/fork-a-repo/ |emdash| please see that page for more
-details. We're repeating some of it here just to give the specifics for the
-mne-python_ project, and to suggest some default names.
-
-.. _setup-github:
-
-Set up and configure a GitHub account
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-If you don't have a GitHub account, go to the GitHub page, and make one.
-
-You then need to configure your account to allow write access |emdash| see
-the *Generating SSH keys* help on `GitHub Help`_.
-
-Create your own fork of a repository
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Now you should fork the core ``mne-python`` repository (although you could
-in principle also fork a different one, such as ``mne-matlab```):
-
-#. Log into your GitHub account.
-
-#. Go to the `mne-python GitHub`_ home.
-
-#. Click on the *fork* button:
-
-   .. image:: _static/forking_button.png
-
-   Now, after a short pause and some 'Hardcore forking action', you should
-   find yourself at the home page for your own forked copy of mne-python_.
-
-Setting up the fork and the working directory
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Briefly, this is done using:
-
-.. code-block:: bash
-
-   $ git clone git at github.com:your-user-name/mne-python.git
-   $ cd mne-python
-   $ git remote add upstream git://github.com/mne-tools/mne-python.git
-
-These steps can be broken out to be more explicit as:
-
-#. Clone your fork to the local computer:
-
-   .. code-block:: bash
-
-      $ git clone git at github.com:your-user-name/mne-python.git
-
-#. Change directory to your new repo:
-
-   .. code-block:: bash
-
-      $ cd mne-python
-
-   Then type:
-
-   .. code-block:: bash
-
-      $ git branch -a
-
-   to show you all branches.  You'll get something like::
-
-    * master
-    remotes/origin/master
-
-   This tells you that you are currently on the ``master`` branch, and
-   that you also have a ``remote`` connection to ``origin/master``.
-   What remote repository is ``remote/origin``? Try ``git remote -v`` to
-   see the URLs for the remote.  They will point to your GitHub fork.
-
-   Now you want to connect to the mne-python repository, so you can
-   merge in changes from the trunk:
-
-   .. code-block:: bash
-
-      $ cd mne-python
-      $ git remote add upstream git://github.com/mne-tools/mne-python.git
-
-   ``upstream`` here is just the arbitrary name we're using to refer to the
-   main mne-python_ repository.
-
-   Note that we've used ``git://`` for the URL rather than ``git@``. The
-   ``git://`` URL is read only. This means we that we can't accidentally (or
-   deliberately) write to the upstream repo, and we are only going to use it
-   to merge into our own code.
-
-   Just for your own satisfaction, show yourself that you now have a new
-   'remote', with ``git remote -v show``, giving you something like::
-
-       upstream   git://github.com/mne-tools/mne-python.git (fetch)
-       upstream   git://github.com/mne-tools/mne-python.git (push)
-       origin     git at github.com:your-user-name/mne-python.git (fetch)
-       origin     git at github.com:your-user-name/mne-python.git (push)
-
-   Your fork is now set up correctly.
-
-#. Install mne with editing permissions to the installed folder:
-
-   To be able to conveniently edit your files after installing mne-python,
-   install using the following setting:
-
-   .. code-block:: bash
-
-      $ python setup.py develop --user
-
-   To make changes in the code, edit the relevant files and restart the
-   ipython kernel for changes to take effect.
-
-#. Ensure unit tests pass and html files can be compiled
-
-   Make sure before starting to code that all unit tests pass and the
-   html files in the ``doc/`` directory can be built without errors. To build
-   the html files, first go the ``doc/`` directory and then type:
-
-   .. code-block:: bash
-
-      $ make html
-
-   Once it is compiled for the first time, subsequent compiles will only
-   recompile what has changed. That's it! You are now ready to hack away.
-
-Workflow summary
-----------------
-
-This section gives a summary of the workflow once you have successfully forked
-the repository, and details are given for each of these steps in the following
-sections.
-
-* Don't use your ``master`` branch for anything.  Consider deleting it.
-
-* When you are starting a new set of changes, fetch any changes from the
-  trunk, and start a new *feature branch* from that.
-
-* Make a new branch for each separable set of changes |emdash| "one task, one
-  branch" (`ipython git workflow`_).
-
-* Name your branch for the purpose of the changes - e.g.
-  ``bugfix-for-issue-14`` or ``refactor-database-code``.
-
-* If you can possibly avoid it, avoid merging trunk or any other branches into
-  your feature branch while you are working.
-
-* If you do find yourself merging from the trunk, consider :ref:`rebase-on-trunk`
-
-* **Ensure all tests still pass**. Make `travis`_ happy.
-
-* Ask for code review!
-
-This way of working helps to keep work well organized, with readable history.
-This in turn makes it easier for project maintainers (that might be you) to
-see what you've done, and why you did it.
-
-See `linux git workflow`_ and `ipython git workflow`_ for some explanation.
-
-Deleting your master branch
-^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-It may sound strange, but deleting your own ``master`` branch can help reduce
-confusion about which branch you are on.  See `deleting master on github`_ for
-details.
-
-.. _update-mirror-trunk:
-
-Updating the mirror of trunk
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-From time to time you should fetch the upstream (trunk) changes from GitHub:
-
-.. code-block:: bash
-
-   $ git fetch upstream
-
-This will pull down any commits you don't have, and set the remote branches to
-point to the right commit. For example, 'trunk' is the branch referred to by
-(remote/branchname) ``upstream/master`` - and if there have been commits since
-you last checked, ``upstream/master`` will change after you do the fetch.
-
-.. _make-feature-branch:
-
-Making a new feature branch
-^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-When you are ready to make some changes to the code, you should start a new
-branch. Branches that are for a collection of related edits are often called
-'feature branches'.
-
-Making an new branch for each set of related changes will make it easier for
-someone reviewing your branch to see what you are doing.
-
-Choose an informative name for the branch to remind yourself and the rest of
-us what the changes in the branch are for. For example ``add-ability-to-fly``,
-or ``buxfix-for-issue-42``.
-
-.. code-block:: bash
-
-   # Update the mirror of trunk
-   $ git fetch upstream
-
-   # Make new feature branch starting at current trunk
-   $ git branch my-new-feature upstream/master
-   $ git checkout my-new-feature
-
-Generally, you will want to keep your feature branches on your public GitHub_
-fork. To do this, you `git push`_ this new branch up to your
-github repo. Generally (if you followed the instructions in these pages, and
-by default), git will have a link to your GitHub repo, called ``origin``. You
-push up to your own repo on GitHub with:
-
-.. code-block:: bash
-
-   $ git push origin my-new-feature
-
-In git > 1.7 you can ensure that the link is correctly set by using the
-``--set-upstream`` option:
-
-.. code-block:: bash
-
-   $ git push --set-upstream origin my-new-feature
-
-From now on git will know that ``my-new-feature`` is related to the
-``my-new-feature`` branch in the GitHub repo.
-
-.. _edit-flow:
-
-The editing workflow
---------------------
-
-Overview
-^^^^^^^^
-
-.. code-block:: bash
-
-   $ git add my_new_file
-   $ git commit -am 'FIX: some message'
-   $ git push
-
-In more detail
-^^^^^^^^^^^^^^
-
-#. Make some changes
-
-#. See which files have changed with ``git status`` (see `git status`_).
-   You'll see a listing like this one::
-
-     # On branch ny-new-feature
-     # Changed but not updated:
-     #   (use "git add <file>..." to update what will be committed)
-     #   (use "git checkout -- <file>..." to discard changes in working directory)
-     #
-     #    modified:   README
-     #
-     # Untracked files:
-     #   (use "git add <file>..." to include in what will be committed)
-     #
-     #    INSTALL
-     no changes added to commit (use "git add" and/or "git commit -a")
-
-#. Check what the actual changes are with ``git diff`` (`git diff`_).
-
-#. Add any new files to version control ``git add new_file_name`` (see
-   `git add`_).
+Deprecating
+-----------
+If you need to deprecate a function or a class, use the ``@deprecated`` decorator::
 
-#. Add any modified files that you want to commit using
-   ``git add modified_file_name``  (see `git add`_).
+    from mne.utils import deprecated
 
-#. Once you are ready to commit, check with ``git status`` which files are
-   about to be committed::
+    @deprecated('my_function will be deprecated in 0.XX, please use my_new_function instead.')
+    def my_function():
+       return 'foo'
 
-    # Changes to be committed:
-    #   (use "git reset HEAD <file>..." to unstage)
-    #
-    #    modified:   README
+If you need to deprecate a parameter, use the mne warning function.
+For example to rename a parameter from `old_param` to `new_param` you can
+use something like this::
 
-   Then use ``git commit -m 'A commit message'``. The ``m`` flag just
-   signals that you're going to type a message on the command line. The `git
-   commit`_ manual page might also be useful.
+    from mne.utils import warn
 
-   It is also good practice to prefix commits with the type of change, such as
-   ``FIX:``, ``STY:``, or ``ENH:`` for fixes, style changes, or enhancements.
+    def my_function(new_param, old_param=None):
+        if old_param is not None:
+             warn('old_param is deprecated and will be replaced by new_param in 0.XX.',
+                  DeprecationWarning)
+             new_param = old_param
+        # Do what you have to do with new_param
+        return 'foo'
 
-#. To push the changes up to your forked repo on GitHub, do a ``git
-   push`` (see `git push`_).
 
-Asking for your changes to be reviewed or merged
-------------------------------------------------
 
-When you are ready to ask for someone to review your code and consider a merge:
-
-#. Go to the URL of your forked repo, say
-   ``https://github.com/your-user-name/mne-python``.
-
-#. Use the 'Switch Branches' dropdown menu near the top left of the page to
-   select the branch with your changes:
-
-   .. image:: _static/branch_dropdown.png
-
-#. Click on the 'Pull request' button:
-
-   .. image:: _static/pull_button.png
-
-   Enter a title for the set of changes, and some explanation of what you've
-   done. Say if there is anything you'd like particular attention for - like a
-   complicated change or some code you are not happy with.
-
-   If you don't think your request is ready to be merged, prefix ``WIP:`` to
-   the title of the pull request, and note it also in your pull request
-   message. This is still a good way of getting some preliminary code review.
-   Submitting a pull request early on in feature development can save a great
-   deal of time for you, as the code maintainers may have "suggestions" about
-   how the code should be written (features, style, etc.) that are easier to
-   implement from the start.
-
-#. Finally, make `travis`_ happy. Ensure that builds in all four jobs pass. To make code python3 compatible, refer to ``externals/six.py``. Use virtual environments to test code on different python versions. Please remember that `travis`_ only runs a subset of the tests and is thus not a substitute for running the entire test suite locally.
-
-#. For the code to be mergeable, please rebase w.r.t master branch.
-
-#. Once, you are ready, prefix ``MRG:`` to the title of the pull request to indicate that you are ready for the pull request to be merged.
-
-
-If you are uncertain about what would or would not be appropriate to contribute
-to mne-python, don't hesitate to either send a pull request, or open an issue
-on the mne-python_ GitHub site to discuss potential changes.
-
-Some other things you might want to do
---------------------------------------
-
-Delete a branch on GitHub
-^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: bash
-
-   # change to the master branch (if you still have one, otherwise change to another branch)
-   $ git checkout master
-
-   # delete branch locally
-   $ git branch -D my-unwanted-branch
-
-   # delete branch on GitHub
-   $ git push origin :my-unwanted-branch
-
-(Note the colon ``:`` before ``test-branch``.  See also:
-https://help.github.com/remotes)
-
-Several people sharing a single repository
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-If you want to work on some stuff with other people, where you are all
-committing into the same repository, or even the same branch, then just
-share it via GitHub.
-
-First fork mne-python into your account, as from :ref:`forking`.
-
-Then, go to your forked repository GitHub page, say
-``http://github.com/your-user-name/mne-python``
-
-Click on the 'Admin' button, and add anyone else to the repo as a
-collaborator:
-
-   .. image:: _static/pull_button.png
-
-Now all those people can do:
-
-.. code-block:: bash
-
-   $ git clone git at githhub.com:your-user-name/mne-python.git
-
-Remember that links starting with ``git@`` use the ssh protocol and are
-read-write; links starting with ``git://`` are read-only.
-
-Your collaborators can then commit directly into that repo with the
-usual:
-
-.. code-block:: bash
-
-   $ git commit -am 'ENH: much better code'
-   $ git push origin master # pushes directly into your repo
-
-Explore your repository
-^^^^^^^^^^^^^^^^^^^^^^^
-
-To see a graphical representation of the repository branches and
-commits:
-
-.. code-block:: bash
-
-   $ gitk --all
-
-To see a linear list of commits for this branch:
-
-.. code-block:: bash
-
-   $ git log
-
-You can also look at the `network graph visualizer`_ for your GitHub
-repo.
-
-Finally the ``lg`` alias will give you a reasonable text-based graph of the
-repository.
-
-If you are making extensive changes, ``git grep`` is also very handy.
-
-.. _rebase-on-trunk:
-
-Rebasing on trunk
-^^^^^^^^^^^^^^^^^
-
-Let's say you thought of some work you'd like to do. You
-:ref:`update-mirror-trunk` and :ref:`make-feature-branch` called
-``cool-feature``. At this stage trunk is at some commit, let's call it E. Now
-you make some new commits on your ``cool-feature`` branch, let's call them A,
-B, C. Maybe your changes take a while, or you come back to them after a while.
-In the meantime, trunk has progressed from commit E to commit (say) G::
-
-          A---B---C cool-feature
-         /
-    D---E---F---G trunk
-
-At this stage you consider merging trunk into your feature branch, and you
-remember that this here page sternly advises you not to do that, because the
-history will get messy. Most of the time you can just ask for a review, and
-not worry that trunk has got a little ahead. But sometimes, the changes in
-trunk might affect your changes, and you need to harmonize them. In this
-situation you may prefer to do a rebase.
-
-Rebase takes your changes (A, B, C) and replays them as if they had been made
-to the current state of ``trunk``. In other words, in this case, it takes the
-changes represented by A, B, C and replays them on top of G. After the rebase,
-your history will look like this::
-
-                  A'--B'--C' cool-feature
-                 /
-    D---E---F---G trunk
-
-See `rebase without tears`_ for more detail.
-
-To do a rebase on trunk:
-
-.. code-block:: bash
-
-    # Update the mirror of trunk
-    $ git fetch upstream
-
-    # Go to the feature branch
-    $ git checkout cool-feature
-
-    # Make a backup in case you mess up
-    $ git branch tmp cool-feature
-
-    # Rebase cool-feature onto trunk
-    $ git rebase --onto upstream/master upstream/master cool-feature
-
-In this situation, where you are already on branch ``cool-feature``, the last
-command can be written more succinctly as:
-
-.. code-block:: bash
-
-    $ git rebase upstream/master
-
-When all looks good you can delete your backup branch:
-
-.. code-block:: bash
-
-   $ git branch -D tmp
-
-If it doesn't look good you may need to have a look at
-:ref:`recovering-from-mess-up`.
-
-If you have made changes to files that have also changed in trunk, this may
-generate merge conflicts that you need to resolve - see the `git rebase`_ man
-page for some instructions at the end of the "Description" section. There is
-some related help on merging in the git user manual - see `resolving a
-merge`_.
-
-If your feature branch is already on GitHub and you rebase, you will have to force
-push the branch; a normal push would give an error. If the branch you rebased is
-called ``cool-feature`` and your GitHub fork is available as the remote called ``origin``,
-you use this command to force-push:
-
-.. code-block:: bash
-
-   $ git push -f origin cool-feature
-
-Note that this will overwrite the branch on GitHub, i.e. this is one of the few ways
-you can actually lose commits with git.
-Also note that it is never allowed to force push to the main mne-python repo (typically
-called ``upstream``), because this would re-write commit history and thus cause problems
-for all others.
-
-.. _recovering-from-mess-up:
-
-Recovering from mess-ups
-^^^^^^^^^^^^^^^^^^^^^^^^
-
-Sometimes, you mess up merges or rebases. Luckily, in git it is relatively
-straightforward to recover from such mistakes.
-
-If you mess up during a rebase:
-
-.. code-block:: bash
-
-   $ git rebase --abort
-
-If you notice you messed up after the rebase:
-
-.. code-block:: bash
-
-   # Reset branch back to the saved point
-   $ git reset --hard tmp
-
-If you forgot to make a backup branch:
-
-.. code-block:: bash
-
-   # Look at the reflog of the branch
-   $ git reflog show cool-feature
-
-   8630830 cool-feature@{0}: commit: BUG: io: close file handles immediately
-   278dd2a cool-feature@{1}: rebase finished: refs/heads/my-feature-branch onto 11ee694744f2552d
-   26aa21a cool-feature@{2}: commit: BUG: lib: make seek_gzip_factory not leak gzip obj
-   ...
-
-   # Reset the branch to where it was before the botched rebase
-   $ git reset --hard cool-feature@{2}
-
-Otherwise, googling the issue may be helpful (especially links to Stack
-Overflow).
-
-.. _rewriting-commit-history:
-
-Rewriting commit history
-^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. note::
-
-   Do this only for your own feature branches.
-
-There's an embarrassing typo in a commit you made? Or perhaps the you
-made several false starts you would like the posterity not to see.
-
-This can be done via *interactive rebasing*.
-
-Suppose that the commit history looks like this:
-
-.. code-block:: bash
-
-    $ git log --oneline
-    eadc391 Fix some remaining bugs
-    a815645 Modify it so that it works
-    2dec1ac Fix a few bugs + disable
-    13d7934 First implementation
-    6ad92e5 * masked is now an instance of a new object, MaskedConstant
-    29001ed Add pre-nep for a copule of structured_array_extensions.
-    ...
-
-and ``6ad92e5`` is the last commit in the ``cool-feature`` branch. Suppose we
-want to make the following changes:
-
-* Rewrite the commit message for ``13d7934`` to something more sensible.
-* Combine the commits ``2dec1ac``, ``a815645``, ``eadc391`` into a single one.
-
-We do as follows:
-
-.. code-block:: bash
-
-    # make a backup of the current state
-    $ git branch tmp HEAD
-    # interactive rebase
-    $ git rebase -i 6ad92e5
-
-This will open an editor with the following text in it::
-
-    pick 13d7934 First implementation
-    pick 2dec1ac Fix a few bugs + disable
-    pick a815645 Modify it so that it works
-    pick eadc391 Fix some remaining bugs
-
-    # Rebase 6ad92e5..eadc391 onto 6ad92e5
-    #
-    # Commands:
-    #  p, pick = use commit
-    #  r, reword = use commit, but edit the commit message
-    #  e, edit = use commit, but stop for amending
-    #  s, squash = use commit, but meld into previous commit
-    #  f, fixup = like "squash", but discard this commit's log message
-    #
-    # If you remove a line here THAT COMMIT WILL BE LOST.
-    # However, if you remove everything, the rebase will be aborted.
-    #
-
-To achieve what we want, we will make the following changes to it::
-
-    r 13d7934 First implementation
-    pick 2dec1ac Fix a few bugs + disable
-    f a815645 Modify it so that it works
-    f eadc391 Fix some remaining bugs
-
-This means that (i) we want to edit the commit message for ``13d7934``, and
-(ii) collapse the last three commits into one. Now we save and quit the
-editor.
-
-Git will then immediately bring up an editor for editing the commit message.
-After revising it, we get the output::
-
-    [detached HEAD 721fc64] FOO: First implementation
-     2 files changed, 199 insertions(+), 66 deletions(-)
-    [detached HEAD 0f22701] Fix a few bugs + disable
-     1 files changed, 79 insertions(+), 61 deletions(-)
-    Successfully rebased and updated refs/heads/my-feature-branch.
-
-and the history looks now like this::
-
-     0f22701 Fix a few bugs + disable
-     721fc64 ENH: Sophisticated feature
-     6ad92e5 * masked is now an instance of a new object, MaskedConstant
-
-If it went wrong, recovery is again possible as explained :ref:`above
-<recovering-from-mess-up>`.
-
-Fetching a pull request
-^^^^^^^^^^^^^^^^^^^^^^^
-
-To fetch a pull request on the main repository to your local working
-directory as a new branch, just do:
-
-.. code-block:: bash
-
-   $ git fetch upstream pull/<pull request number>/head:<local-branch>
-
-As an example, to pull the realtime pull request which has a url
-``https://github.com/mne-tools/mne-python/pull/615/``, do:
-
-.. code-block:: bash
-
-   $ git fetch upstream pull/615/head:realtime
-
-If you want to fetch a pull request to your own fork, replace
-``upstream`` with ``origin``. That's it!
-
-Skipping a build
-^^^^^^^^^^^^^^^^
-
-The builds when the pull request is in `WIP` state can be safely skipped. The important thing is to ensure that the builds pass when the PR is ready to be merged. To skip a Travis build, add ``[ci skip]`` to the commit message::
-
-  FIX: some changes [ci skip]
-
-This will help prevent clogging up Travis and Appveyor and also save the environment.
+Profiling
+---------
+To learn more about profiling python codes please see `the scikit learn profiling site <http://scikit-learn.org/stable/developers/performance.html#performance-howto>`_.
 
 .. _troubleshooting:
 
-Troubleshooting
----------------
-
-Listed below are miscellaneous issues that you might face:
-
-Missing files in examples or unit tests
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-If the unit tests fail due to missing files, you may need to run
-`mne-scripts`_ on the sample dataset. Go to ``bash`` if you are using some
-other shell. Then, execute all three shell scripts in the
-``sample-data/`` directory within ``mne-scripts/``.
-
-Cannot import class from a new \*.py file
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-You need to update the corresponding ``__init__.py`` file and then
-restart the ipython kernel.
-
-ICE default IO error handler doing an exit()
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-If the make test command fails with the error
-``ICE default IO error handler doing an exit()``, try backing up or removing
-.ICEauthority:
-
-.. code-block:: bash
-
-   $ mv ~/.ICEauthority ~/.ICEauthority.bak
-
-.. include:: links.inc
-.. _Sphinx documentation: http://sphinx-doc.org/rest.html
-.. _sphinx gallery documentation: http://sphinx-gallery.readthedocs.org/en/latest/advanced_configuration.html
-.. _NumPy docstring standard: https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMENT.rst.txt
+Submitting changes
+------------------
+Changes to code can be submitted using a standard GitHub Pull Request, as
+documented in :ref:`using_github`.
diff --git a/doc/customizing_git.rst b/doc/customizing_git.rst
index e3ed1ed..34ca26c 100644
--- a/doc/customizing_git.rst
+++ b/doc/customizing_git.rst
@@ -1,3 +1,7 @@
+:orphan:
+
+.. include:: links.inc
+
 .. _customizing-git:
 
 =================
diff --git a/doc/documentation.rst b/doc/documentation.rst
new file mode 100644
index 0000000..7461a27
--- /dev/null
+++ b/doc/documentation.rst
@@ -0,0 +1,710 @@
+:orphan:
+
+.. _documentation:
+
+Documentation
+=============
+
+.. raw:: html
+
+    <style class='text/css'>
+    .panel-title a {
+        display: block;
+        padding: 5px;
+        text-decoration: none;
+    }
+
+    .plus {
+        float: right;
+        color: #212121;
+    }
+
+    .panel {
+        margin-bottom: 3px;
+    }
+
+    .example_details {
+        padding-left: 20px;
+        margin-bottom: 10px;
+    }
+    </style>
+
+    <script type="text/javascript">
+    $(document).ready(function () {
+        if(location.hash != null && location.hash != ""){
+            $('.collapse').removeClass('in');
+            $(location.hash + '.collapse').addClass('in');
+        }
+    });
+    </script>
+
+This is where you can learn about all the things you can do with MNE. It contains **background information** and **tutorials** for taking a deep-dive into the techniques that MNE-python covers. You'll find practical information on how to use these methods with your data, and in many cases some high-level concepts underlying these methods.
+
+There are also **examples**, which contain a short use-case to highlight MNE-functionality and provide inspiration for the many things you can do with this package. You can also find a gallery of these examples in the `examples gallery <auto_examples/index.html>`_.
+
+**See the links below for an introduction to MNE-python, or click one of the sections on this page to see more.**
+
+.. raw:: html
+
+    <div class="panel-group">
+      <div class="panel panel-default">
+        <div class="panel-heading">
+          <h4 class="panel-title">
+            <a data-toggle="collapse" href="#collapse_intro">Introduction to MNE and Python</a>
+          </h4>
+        </div>
+
+        <div id="collapse_intro" class="panel-collapse collapse in">
+          <div class="panel-body">
+
+**Getting started**
+
+.. toctree::
+    :maxdepth: 1
+
+    getting_started.rst
+    advanced_setup.rst
+    auto_tutorials/plot_python_intro.rst
+
+**MNE basics**
+
+.. toctree::
+    :maxdepth: 1
+
+    tutorials/philosophy.rst
+    manual/cookbook.rst
+    whats_new.rst
+    python_reference.rst
+    auto_examples/index.rst
+    generated/commands.rst
+    auto_tutorials/plot_configuration.rst
+    cited.rst
+    faq.rst
+
+**More help**
+
+- :ref:`Cite MNE <cite>`
+- `Mailing list <MNE mailing list>`_ for analysis talk
+- `GitHub issues <https://github.com/mne-tools/mne-python/issues/>`_ for
+  requests and bug reports
+- `Gitter <https://gitter.im/mne-tools/mne-python>`_ to chat with devs
+
+.. raw:: html
+
+          </div>
+        </div>
+      </div>
+
+
+      <div class="panel panel-default">
+        <div class="panel-heading">
+          <h4 class="panel-title">
+            <a data-toggle="collapse" href="#collapse_data">Data structures and containers</a>
+          </h4>
+        </div>
+
+        <div id="collapse_data" class="panel-collapse collapse">
+          <div class="panel-body">
+
+.. toctree::
+    :maxdepth: 1
+
+    auto_tutorials/plot_object_raw.rst
+    auto_tutorials/plot_object_epochs.rst
+    auto_tutorials/plot_object_evoked.rst
+    auto_tutorials/plot_info.rst
+
+
+.. raw:: html
+
+          </div>
+        </div>
+      </div>
+
+
+      <div class="panel panel-default">
+        <div class="panel-heading">
+          <h4 class="panel-title">
+            <a data-toggle="collapse" href="#collapse_io">Data I/O and datasets</a>
+          </h4>
+        </div>
+        <div id="collapse_io" class="panel-collapse collapse">
+          <div class="panel-body">
+
+**Getting your data into MNE**
+
+.. toctree::
+    :maxdepth: 1
+
+    manual/io.rst
+    auto_tutorials/plot_creating_data_structures.rst
+    auto_tutorials/plot_modifying_data_inplace.rst
+    auto_tutorials/plot_ecog.rst
+    manual/memory.rst
+
+.. raw:: html
+
+  <details class="example_details">
+  <summary><strong>Examples</strong></summary>
+
+.. toctree::
+    :maxdepth: 1
+
+    auto_examples/io/plot_elekta_epochs.rst
+    auto_examples/io/plot_objects_from_arrays.rst
+    auto_examples/io/plot_read_and_write_raw_data.rst
+    auto_examples/io/plot_read_epochs.rst
+    auto_examples/io/plot_read_events.rst
+    auto_examples/io/plot_read_evoked.rst
+    auto_examples/io/plot_read_noise_covariance_matrix.rst
+
+.. raw:: html
+
+    </details>
+
+**Working with public datasets**
+
+.. toctree::
+    :maxdepth: 1
+
+    manual/datasets_index.rst
+    auto_tutorials/plot_brainstorm_auditory.rst
+    auto_tutorials/plot_brainstorm_phantom_ctf.rst
+    auto_tutorials/plot_brainstorm_phantom_elekta.rst
+    auto_examples/datasets/plot_brainstorm_data.rst
+    auto_examples/datasets/plot_megsim_data.rst
+    auto_examples/datasets/plot_megsim_data_single_trial.rst
+    auto_examples/datasets/plot_spm_faces_dataset.rst
+
+.. raw:: html
+
+          </div>
+        </div>
+      </div>
+
+
+      <div class="panel panel-default">
+        <div class="panel-heading">
+          <h4 class="panel-title">
+            <a id="preprocessing" class="anchor-doc"></a>
+            <a data-toggle="collapse" href="#collapse_preprocessing">Preprocessing (filtering, SSP, ICA, Maxwell filtering, ...)</a>
+          </h4>
+        </div>
+        <div id="collapse_preprocessing" class="panel-collapse collapse">
+          <div class="panel-body">
+
+**Background**
+
+.. toctree::
+    :maxdepth: 1
+
+    auto_tutorials/plot_background_filtering.rst
+    manual/preprocessing/ssp.rst
+    manual/preprocessing/ica.rst
+    manual/preprocessing/maxwell.rst
+    manual/channel_interpolation.rst
+
+**Preprocessing your data**
+
+.. toctree::
+    :maxdepth: 1
+
+    auto_tutorials/plot_artifacts_detection.rst
+    auto_tutorials/plot_artifacts_correction_filtering.rst
+    auto_tutorials/plot_artifacts_correction_rejection.rst
+    auto_tutorials/plot_artifacts_correction_ssp.rst
+    auto_tutorials/plot_artifacts_correction_ica.rst
+    auto_tutorials/plot_artifacts_correction_maxwell_filtering.rst
+
+.. raw:: html
+
+  <details class="example_details">
+  <summary><strong>Examples</strong></summary>
+
+.. toctree::
+    :maxdepth: 1
+
+    auto_examples/preprocessing/plot_define_target_events.rst
+    auto_examples/preprocessing/plot_eog_artifact_histogram.rst
+    auto_examples/preprocessing/plot_find_ecg_artifacts.rst
+    auto_examples/preprocessing/plot_find_eog_artifacts.rst
+    auto_examples/preprocessing/plot_head_positions.rst
+    auto_examples/preprocessing/plot_interpolate_bad_channels.rst
+    auto_examples/preprocessing/plot_movement_compensation.rst
+    auto_examples/preprocessing/plot_rereference_eeg.rst
+    auto_examples/preprocessing/plot_resample.rst
+    auto_examples/preprocessing/plot_run_ica.rst
+    auto_examples/preprocessing/plot_shift_evoked.rst
+    auto_examples/preprocessing/plot_virtual_evoked.rst
+    auto_examples/preprocessing/plot_xdawn_denoising.rst
+
+.. raw:: html
+
+    </details>
+
+.. raw:: html
+
+          </div>
+        </div>
+      </div>
+
+
+      <div class="panel panel-default">
+        <div class="panel-heading">
+          <h4 class="panel-title">
+            <a id="visualization" class="anchor-doc"></a>
+            <a data-toggle="collapse" href="#collapse_visualization">Visualization</a>
+          </h4>
+        </div>
+        <div id="collapse_visualization" class="panel-collapse collapse">
+          <div class="panel-body">
+
+.. toctree::
+    :maxdepth: 1
+
+    auto_tutorials/plot_visualize_raw.rst
+    auto_tutorials/plot_visualize_epochs.rst
+    auto_tutorials/plot_visualize_evoked.rst
+    tutorials/report.rst
+
+.. raw:: html
+
+  <details class="example_details">
+  <summary><strong>Examples</strong></summary>
+
+.. toctree::
+    :maxdepth: 1
+
+    auto_examples/visualization/make_report.rst
+    auto_examples/visualization/plot_3d_to_2d.rst
+    auto_examples/visualization/plot_channel_epochs_image.rst
+    auto_examples/visualization/plot_eeg_on_scalp.rst
+    auto_examples/visualization/plot_evoked_topomap.rst
+    auto_examples/visualization/plot_evoked_whitening.rst
+    auto_examples/visualization/plot_meg_sensors.rst
+    auto_examples/visualization/plot_parcellation.rst
+    auto_examples/visualization/plot_sensor_noise_level.rst
+    auto_examples/visualization/plot_ssp_projs_sensitivity_map.rst
+    auto_examples/visualization/plot_topo_compare_conditions.rst
+    auto_examples/visualization/plot_topo_customized.rst
+    auto_examples/visualization/plot_xhemi.rst
+
+.. raw:: html
+
+    </details>
+.. raw:: html
+
+          </div>
+        </div>
+      </div>
+
+
+      <div class="panel panel-default">
+        <div class="panel-heading">
+          <h4 class="panel-title">
+            <a id="time-freq" class="anchor-doc"></a>
+            <a data-toggle="collapse" href="#collapse_tf">Time- and frequency-domain analyses</a>
+          </h4>
+        </div>
+        <div id="collapse_tf" class="panel-collapse collapse">
+          <div class="panel-body">
+
+**Tutorials**
+
+.. toctree::
+    :maxdepth: 1
+
+    auto_tutorials/plot_introduction.rst
+    auto_tutorials/plot_epoching_and_averaging.rst
+    auto_tutorials/plot_eeg_erp.rst
+    auto_tutorials/plot_sensors_time_frequency.rst
+    manual/time_frequency.rst
+
+.. raw:: html
+
+  <details class="example_details">
+  <summary><strong>Examples</strong></summary>
+
+.. toctree::
+    :maxdepth: 1
+
+    auto_examples/time_frequency/plot_compute_raw_data_spectrum.rst
+    auto_examples/time_frequency/plot_compute_source_psd_epochs.rst
+    auto_examples/time_frequency/plot_source_label_time_frequency.rst
+    auto_examples/time_frequency/plot_source_power_spectrum.rst
+    auto_examples/time_frequency/plot_source_space_time_frequency.rst
+    auto_examples/time_frequency/plot_temporal_whitening.rst
+    auto_examples/time_frequency/plot_time_frequency_global_field_power.rst
+    auto_examples/time_frequency/plot_time_frequency_simulated.rst
+
+.. raw:: html
+
+  </details>
+
+.. raw:: html
+
+          </div>
+        </div>
+      </div>
+
+
+      <div class="panel panel-default">
+        <div class="panel-heading">
+          <h4 class="panel-title">
+            <a id="source-analysis" class="anchor-doc"></a>
+            <a data-toggle="collapse" href="#collapse_source">Source-level analysis</a>
+          </h4>
+        </div>
+        <div id="collapse_source" class="panel-collapse collapse">
+          <div class="panel-body">
+
+**Background**
+
+.. toctree::
+    :maxdepth: 1
+
+    manual/source_localization/forward.rst
+    manual/source_localization/inverse.rst
+    manual/source_localization/morph.rst
+
+**Getting data to source space**
+
+.. toctree::
+    :maxdepth: 1
+
+    auto_tutorials/plot_source_alignment.rst
+    auto_tutorials/plot_forward.rst
+    auto_tutorials/plot_compute_covariance.rst
+    auto_tutorials/plot_mne_dspm_source_localization.rst
+    auto_tutorials/plot_dipole_fit.rst
+    auto_tutorials/plot_point_spread.rst
+    auto_tutorials/plot_dipole_orientations.rst
+
+
+.. raw:: html
+
+  <details class="example_details">
+  <summary><strong>Forward examples</strong></summary>
+
+.. toctree::
+    :maxdepth: 1
+
+    auto_examples/forward/plot_decimate_head_surface.rst
+    auto_examples/forward/plot_forward_sensitivity_maps.rst
+    auto_examples/forward/plot_left_cerebellum_volume_source.rst
+    auto_examples/forward/plot_source_space_morphing.rst
+
+.. raw:: html
+
+    </details>
+
+.. raw:: html
+
+  <details class="example_details">
+  <summary><strong>Inverse examples</strong></summary>
+
+.. toctree::
+    :maxdepth: 1
+
+    auto_examples/inverse/plot_compute_mne_inverse_epochs_in_label.rst
+    auto_examples/inverse/plot_compute_mne_inverse_raw_in_label.rst
+    auto_examples/inverse/plot_compute_mne_inverse_volume.rst
+    auto_examples/inverse/plot_covariance_whitening_dspm.rst
+    auto_examples/inverse/plot_custom_inverse_solver.rst
+    auto_examples/inverse/plot_dics_beamformer.rst
+    auto_examples/inverse/plot_dics_source_power.rst
+    auto_examples/inverse/plot_gamma_map_inverse.rst
+    auto_examples/inverse/plot_label_activation_from_stc.rst
+    auto_examples/inverse/plot_label_from_stc.rst
+    auto_examples/inverse/plot_label_source_activations.rst
+    auto_examples/inverse/plot_lcmv_beamformer.rst
+    auto_examples/inverse/plot_lcmv_beamformer_volume.rst
+    auto_examples/inverse/plot_mixed_source_space_inverse.rst
+    auto_examples/inverse/plot_mixed_norm_inverse.rst
+    auto_examples/inverse/plot_mne_crosstalk_function.rst
+    auto_examples/inverse/plot_mne_point_spread_function.rst
+    auto_examples/inverse/plot_morph_data.rst
+    auto_examples/inverse/plot_rap_music.rst
+    auto_examples/inverse/plot_read_stc.rst
+    auto_examples/inverse/plot_read_inverse.rst
+    auto_examples/inverse/plot_read_source_space.rst
+    auto_examples/inverse/plot_snr_estimate.rst
+    auto_examples/inverse/plot_tf_dics.rst
+    auto_examples/inverse/plot_tf_lcmv.rst
+    auto_examples/inverse/plot_time_frequency_mixed_norm_inverse.rst
+    auto_examples/inverse/plot_vector_mne_solution.rst
+
+.. raw:: html
+
+    </details>
+
+.. raw:: html
+
+  <details class="example_details">
+  <summary><strong>Simulation examples</strong></summary>
+
+.. toctree::
+    :maxdepth: 1
+
+    auto_examples/simulation/plot_simulate_evoked_data.rst
+    auto_examples/simulation/plot_simulate_raw_data.rst
+
+.. raw:: html
+
+    </details>
+
+.. raw:: html
+
+          </div>
+        </div>
+      </div>
+
+
+      <div class="panel panel-default">
+        <div class="panel-heading">
+          <h4 class="panel-title">
+            <a data-toggle="collapse" href="#collapse_statistics">Statistics in sensor- and source-space</a>
+          </h4>
+        </div>
+        <div id="collapse_statistics" class="panel-collapse collapse">
+          <div class="panel-body">
+
+
+**Background**
+
+.. toctree::
+    :maxdepth: 1
+
+    manual/statistics.rst
+
+**Sensor Space**
+
+.. toctree::
+    :maxdepth: 1
+
+    auto_tutorials/plot_stats_cluster_methods.rst
+    auto_tutorials/plot_stats_spatio_temporal_cluster_sensors.rst
+    auto_tutorials/plot_stats_cluster_1samp_test_time_frequency.rst
+    auto_tutorials/plot_stats_cluster_time_frequency.rst
+
+.. raw:: html
+
+  <details class="example_details">
+  <summary><strong>Examples</strong></summary>
+
+.. toctree::
+    :maxdepth: 1
+
+    auto_examples/stats/plot_fdr_stats_evoked.rst
+    auto_examples/stats/plot_cluster_stats_evoked.rst
+    auto_examples/stats/plot_sensor_permutation_test.rst
+    auto_examples/stats/plot_sensor_regression.rst
+    auto_examples/stats/plot_linear_regression_raw.rst
+
+.. raw:: html
+
+    </details>
+
+**Source Space**
+
+.. toctree::
+    :maxdepth: 1
+
+    auto_tutorials/plot_stats_cluster_time_frequency_repeated_measures_anova.rst
+    auto_tutorials/plot_stats_cluster_spatio_temporal_2samp.rst
+    auto_tutorials/plot_stats_cluster_spatio_temporal_repeated_measures_anova.rst
+    auto_tutorials/plot_stats_cluster_spatio_temporal.rst
+
+.. raw:: html
+
+          </div>
+        </div>
+      </div>
+
+
+      <div class="panel panel-default">
+        <div class="panel-heading">
+          <h4 class="panel-title">
+            <a id="machine-learning" class="anchor-doc"></a>
+            <a data-toggle="collapse" href="#collapse_ml">Machine learning (decoding, encoding, MVPA)</a>
+          </h4>
+        </div>
+        <div id="collapse_ml" class="panel-collapse collapse">
+          <div class="panel-body">
+
+**Decoding**
+
+.. toctree::
+    :maxdepth: 1
+
+    manual/decoding.rst
+    auto_tutorials/plot_sensors_decoding.rst
+
+.. raw:: html
+
+  <details class="example_details">
+  <summary><strong>Examples</strong></summary>
+
+.. toctree::
+    :maxdepth: 1
+
+    auto_examples/decoding/decoding_rsa.rst
+    auto_examples/decoding/plot_decoding_csp_eeg.rst
+    auto_examples/decoding/plot_decoding_csp_space.rst
+    auto_examples/decoding/plot_decoding_csp_timefreq.rst
+    auto_examples/decoding/plot_decoding_spatio_temporal_source.rst
+    auto_examples/decoding/plot_decoding_spoc_CMC.rst
+    auto_examples/decoding/plot_decoding_time_generalization_conditions.rst
+    auto_examples/decoding/plot_decoding_unsupervised_spatial_filter.rst
+    auto_examples/decoding/plot_decoding_xdawn_eeg.rst
+    auto_examples/decoding/plot_ems_filtering.rst
+    auto_examples/decoding/plot_linear_model_patterns.rst
+
+.. raw:: html
+
+    </details>
+
+**Encoding**
+
+.. toctree::
+    :maxdepth: 1
+
+    auto_tutorials/plot_receptive_field.rst
+
+.. raw:: html
+
+  <details class="example_details">
+  <summary><strong>Examples</strong></summary>
+
+.. toctree::
+    :maxdepth: 1
+
+    auto_examples/decoding/plot_receptive_field.rst
+
+.. raw:: html
+
+    </details>
+
+.. raw:: html
+
+          </div>
+        </div>
+      </div>
+
+
+      <div class="panel panel-default">
+        <div class="panel-heading">
+          <h4 class="panel-title">
+            <a id="connectivity" class="anchor-doc"></a>
+            <a data-toggle="collapse" href="#collapse_connectivity">Connectivity</a>
+          </h4>
+        </div>
+        <div id="collapse_connectivity" class="panel-collapse collapse">
+          <div class="panel-body">
+
+**Examples**
+
+.. toctree::
+    :maxdepth: 1
+
+    auto_examples/connectivity/plot_cwt_sensor_connectivity.rst
+    auto_examples/connectivity/plot_mixed_source_space_connectivity.rst
+    auto_examples/connectivity/plot_mne_inverse_coherence_epochs.rst
+    auto_examples/connectivity/plot_mne_inverse_connectivity_spectrum.rst
+    auto_examples/connectivity/plot_mne_inverse_label_connectivity.rst
+    auto_examples/connectivity/plot_mne_inverse_psi_visual.rst
+    auto_examples/connectivity/plot_sensor_connectivity.rst
+
+.. raw:: html
+
+          </div>
+        </div>
+      </div>
+
+
+      <div class="panel panel-default">
+        <div class="panel-heading">
+          <h4 class="panel-title">
+            <a data-toggle="collapse" href="#collapse_realtime">Realtime</a>
+          </h4>
+        </div>
+        <div id="collapse_realtime" class="panel-collapse collapse">
+          <div class="panel-body">
+
+**Examples**
+
+.. toctree::
+    :maxdepth: 1
+
+    auto_examples/realtime/ftclient_rt_average.rst
+    auto_examples/realtime/ftclient_rt_compute_psd.rst
+    auto_examples/realtime/plot_compute_rt_average.rst
+    auto_examples/realtime/plot_compute_rt_decoder.rst
+    auto_examples/realtime/rt_feedback_client.rst
+    auto_examples/realtime/rt_feedback_server.rst
+
+.. raw:: html
+
+          </div>
+        </div>
+      </div>
+
+      <div class="panel panel-default">
+        <div class="panel-heading">
+          <h4 class="panel-title">
+            <a data-toggle="collapse" href="#collapse_c">MNE-C and MNE-MATLAB</a>
+          </h4>
+        </div>
+        <div id="collapse_c" class="panel-collapse collapse">
+          <div class="panel-body">
+
+**MNE-C**
+
+.. toctree::
+    :maxdepth: 1
+
+    tutorials/command_line.rst
+    manual/c_reference.rst
+    manual/gui/analyze.rst
+    manual/gui/browse.rst
+    manual/appendix/bem_model.rst
+    manual/appendix/c_misc.rst
+
+**MNE-MATLAB**
+
+.. toctree::
+    :maxdepth: 1
+
+    manual/matlab.rst
+
+
+.. raw:: html
+
+          </div>
+        </div>
+      </div>
+
+
+      <div class="panel panel-default">
+        <div class="panel-heading">
+          <h4 class="panel-title">
+            <a data-toggle="collapse" href="#collapse_contributing">Contributing</a>
+          </h4>
+        </div>
+
+        <div id="collapse_contributing" class="panel-collapse collapse">
+          <div class="panel-body">
+
+.. toctree::
+    :maxdepth: 1
+
+    contributing.rst
+    configure_git.rst
+    customizing_git.rst
+
+.. raw:: html
+
+          </div>
+        </div>
+      </div>
+    </div>
diff --git a/doc/faq.rst b/doc/faq.rst
index 469e669..53a793b 100644
--- a/doc/faq.rst
+++ b/doc/faq.rst
@@ -1,3 +1,5 @@
+:orphan:
+
 .. include:: links.inc
 
 .. _faq:
@@ -17,7 +19,31 @@ Help! I can't get Python and MNE-Python working!
 ------------------------------------------------
 
 Check out our section on how to get Anaconda up and running over at the
-:ref:`getting started page <install_interpreter>`.
+:ref:`getting started page <install_python_and_mne_python>`.
+
+I still can't get it to work!
+-----------------------------
+For analysis talk, join the `MNE mailing list`_. File specific feature
+requests or bug reports `on GitHub <https://github.com/mne-tools/mne-python/issues/>`_.
+You can also chat with developers `on Gitter <https://gitter.im/mne-tools/mne-python>`_.
+
+.. _cite:
+
+How do I cite MNE?
+------------------
+If you use the implementations provided by the MNE software in your research,
+you should cite:
+
+    - A. Gramfort, M. Luessi, E. Larson, D. Engemann, D. Strohmeier, C. Brodbeck, L. Parkkonen, M. Hämäläinen, `MNE software for processing MEG and EEG data <http://www.ncbi.nlm.nih.gov/pubmed/24161808>`_, NeuroImage, Volume 86, 1 February 2014, Pages 446-460, ISSN 1053-8119, `[DOI] <http://dx.doi.org/10.1016/j.neuroimage.2013.10.027>`__
+
+If you use the Python code you should cite as well:
+
+    - A. Gramfort, M. Luessi, E. Larson, D. Engemann, D. Strohmeier, C. Brodbeck, R. Goj, M. Jas, T. Brooks, L. Parkkonen, M. Hämäläinen, `MEG and EEG data analysis with MNE-Python <http://journal.frontiersin.org/article/10.3389/fnins.2013.00267/abstract>`_, Frontiers in Neuroscience, Volume 7, 2013, ISSN 1662-453X, `[DOI] <http://dx.doi.org/10.3389/fnins.2013.00267>`__
+
+To cite specific versions of the software, you can use the DOIs provided by
+`Zenodo <https://zenodo.org/search?ln=en&p=mne-python>`_.
+
+You should as well cite the related method papers, some of which are listed in :ref:`ch_reading`.
 
 I'm not sure how to do *X* analysis step with my *Y* data...
 ------------------------------------------------------------
@@ -55,7 +81,6 @@ reporting a bug, which should look something like this::
     nibabel:       Not found
     nitime:        Not found
     mayavi:        Not found
-    nose:          1.3.7
     pandas:        Not found
     pycuda:        Not found
     skcuda:        Not found
@@ -74,7 +99,7 @@ able to read your data in the not-too-distant future. For details, see:
 
 MNE-Python is designed to provide its own file saving formats
 (often based on the FIF standard) for its objects usually via a
-``save`` method or ``write_*`` method, e.g. :func:`mne.Raw.save`,
+``save`` method or ``write_*`` method, e.g. :func:`mne.io.Raw.save`,
 :func:`mne.Epochs.save`, :func:`mne.write_evokeds`,
 :func:`mne.SourceEstimate.save`. If you have some data that you
 want to save but can't figure out how, shoot an email to the
@@ -87,6 +112,54 @@ which is based on the
 h5py_, to save data in a fast, future-compatible, standard format.
 
 
+I downloaded a dataset once, but MNE-Python is asking to download it again. Why?
+--------------------------------------------------------------------------------
+The default location for the MNE-sample data is ``~/mne_data``.
+If you downloaded data and an example asks you whether to download it again,
+make sure the data reside in the examples directory and that you run the
+script from its current directory:
+
+.. code-block:: console
+
+  $ cd examples/preprocessing
+
+Then in Python you can do::
+
+  In [1]: %run plot_find_ecg_artifacts.py
+
+See :ref:`datasets` for a list of all available datasets and some advanced
+configuration options, e.g. to specify a custom location for storing the
+datasets.
+
+.. _faq_cpu:
+
+A function uses multiple CPU cores even though I didn't tell it to. Why?
+------------------------------------------------------------------------
+Ordinarily in MNE-python the ``parallel`` module is used to deploy multiple
+cores via the ``n_jobs`` variable. However, functions like
+:func:`mne.preprocessing.maxwell_filter` that use :mod:`scipy.linalg` do not have an
+``n_jobs`` flag but may still use multiple cores. This is because :mod:`scipy.linalg`
+is built with linear algebra libraries that natively support multithreading:
+
+* `OpenBLAS <http://www.openblas.net/>`_
+* `Intel Math Kernel Library (MKL) <https://software.intel.com/en-us/intel-mkl>`_,
+  which uses `OpenMP <http://www.openmp.org/>`_
+
+To control how many cores are used for linear-algebra-heavy functions like
+:func:`mne.preprocessing.maxwell_filter`, you can set the
+``OMP_NUM_THREADS`` or ``OPENBLAS_NUM_THREADS`` environment variable to the
+desired number of cores for MKL or OpenBLAS, respectively. This can be done
+before running Python, or inside Python you can achieve the same effect by,
+e.g.::
+
+    >>> import os
+    >>> num_cpu = '4' # Set as a string
+    >>> os.environ['OMP_NUM_THREADS'] = num_cpu
+
+This must be done *before* running linear algebra functions; subsequent
+changes in the same Python session will have no effect.
+
+
 Resampling and decimating data
 ==============================
 
diff --git a/doc/getting_started.rst b/doc/getting_started.rst
index 06d0648..646ca3d 100644
--- a/doc/getting_started.rst
+++ b/doc/getting_started.rst
@@ -1,123 +1,77 @@
+:orphan:
+
 .. include:: links.inc
 
-.. _getting_started:
+.. _installation:
+
+Installation
+============
 
-Getting started
-===============
+To get started with MNE, visit the installation instructions for
+the :ref:`MNE<install_python_and_mne_python>`. You can optionally also
+install :ref:`MNE-C <install_mne_c>`:
 
-.. _introduction_to_mne:
+.. container:: row
 
-**MNE** is an academic software package that aims to provide data analysis
-pipelines encompassing all phases of M/EEG data processing.
+  .. container:: panel panel-default halfpad
 
-MNE started as tool written in C by Matti Hämäläinen while at MGH in Boston.
-MNE was then extended with the Python programming language to implement
-nearly all MNE-C’s functionality, offer transparent scripting, and
-:ref:`extend MNE-C’s functionality considerably <what_can_you_do>`.
+    .. container:: panel-heading nosize
 
-A basic :ref:`ch_matlab` is also available mostly
-to allow reading and write MNE files. The sister :ref:`mne_cpp` project
-aims to provide modular and open-source tools for acquisition,
-visualization, and analysis.
+      MNE python module
 
-.. note:: This package is based on the FIF file format from Neuromag. But, it
-          can read and convert CTF, BTI/4D, KIT and various EEG formats to
-          FIF (see :ref:`IO functions <ch_convert>`).
+    .. container:: panel-body nosize
 
-          If you have been using MNE-C, there is no need to convert your fif
-          files to a new system or database -- MNE-Python works nicely with
-          the historical fif files.
+      .. toctree::
+        :maxdepth: 2
 
-Installation
-------------
+        install_mne_python
 
-To get started with MNE, visit the installation instructions for
-:ref:`MNE-Python <install_python_and_mne_python>` and
-:ref:`MNE-C <install_mne_c>`:
-
-.. container:: span box
-
-  .. raw:: html
-
-    <h3>MNE-Python</h3>
-
-  .. toctree::
-    :maxdepth: 2
-
-    install_mne_python
-
-.. container:: span box
-
-  .. raw:: html
-
-    <h3>MNE-C</h3>
-
-  .. toctree::
-    :maxdepth: 2
-
-    install_mne_c
-
-
-.. _what_can_you_do:
-
-What can you do with MNE using Python?
---------------------------------------
-
-   - **Raw data visualization** to visualize recordings
-     (see :ref:`general_examples` for more).
-   - **Epoching**: Define epochs, baseline correction, handle conditions etc.
-   - **Averaging** to get Evoked data.
-   - **Compute SSP projectors** to remove ECG and EOG artifacts.
-   - **Compute ICA** to remove artifacts or select latent sources.
-   - **Maxwell filtering** to remove environmental noise.
-   - **Boundary Element Modeling**: single and three-layer BEM model
-     creation and solution computation.
-   - **Forward modeling**: BEM computation and mesh creation
-     (see :ref:`ch_forward`).
-   - **Linear inverse solvers** (dSPM, sLORETA, MNE, LCMV, DICS).
-   - **Sparse inverse solvers** (L1/L2 mixed norm MxNE, Gamma Map,
-     Time-Frequency MxNE, RAP-MUSIC).
-   - **Connectivity estimation** in sensor and source space.
-   - **Visualization of sensor and source space data**
-   - **Time-frequency** analysis with Morlet wavelets (induced power,
-     intertrial coherence, phase lock value) also in the source space.
-   - **Spectrum estimation** using multi-taper method.
-   - **Mixed Source Models** combining cortical and subcortical structures.
-   - **Dipole Fitting**
-   - **Decoding** multivariate pattern analysis of M/EEG topographies.
-   - **Compute contrasts** between conditions, between sensors, across
-     subjects etc.
-   - **Non-parametric statistics** in time, space and frequency
-     (including cluster-level).
-   - **Scripting** (batch and parallel computing)
-
-
-Is that all you can do with MNE-Python?
----------------------------------------
-
-Short answer is **No**! You can also do:
-
-    - detect heart beat QRS component
-    - detect eye blinks and EOG artifacts
-    - compute SSP projections to remove ECG or EOG artifacts
-    - compute Independent Component Analysis (ICA) to remove artifacts or
-      select latent sources
-    - estimate noise covariance matrix from Raw and Epochs
-    - visualize cross-trial response dynamics using epochs images
-    - compute forward solutions
-    - estimate power in the source space
-    - estimate connectivity in sensor and source space
-    - morph stc from one brain to another for group studies
-    - compute mass univariate statistics base on custom contrasts
-    - visualize source estimates
-    - export raw, epochs, and evoked data to other python data analysis
-      libraries e.g. pandas
-    - Raw movement compensation as you would do with Elekta Maxfilter™
-    - and many more things ...
-
-
-What you're not supposed to do with MNE-Python
-----------------------------------------------
-
-    - **Brain and head surface segmentation** for use with BEM
-      models -- use Freesurfer_.
+  .. container:: panel panel-default halfpad
+
+    .. container:: panel-heading nosize
+
+      MNE-C
+
+    .. container:: panel-body nosize
+
+      .. toctree::
+        :maxdepth: 2
+
+        install_mne_c
+
+.. container:: row
+
+  .. container:: panel panel-default
+
+    .. container:: panel-heading nosize
+
+      Historical notes
+
+    .. container:: panel-body nosize
+
+      MNE started as tool written in C by Matti Hämäläinen while at MGH in
+      Boston.
+
+      - :ref:`MNE-C <c_reference>` is Matti's C code. Historically, MNE was
+        a software package for computing cortically constrained Minimum Norm
+        Estimates from MEG and EEG data.
+
+      - The MNE python module was built in the Python programming language to
+        reimplement all MNE-C’s functionality, offer transparent scripting,
+        and extend MNE-C’s functionality considerably (see left). Thus it is
+        the primary focus of this documentation.
+
+      - :ref:`ch_matlab` is available mostly to allow reading and writing
+        FIF files.
+
+      - :ref:`mne_cpp`  aims to provide modular and open-source tools for
+        real-time acquisition, visualization, and analysis. It provides
+        a :ref:`separate website <mne_cpp>` for documentation and releases.
+
+      The MNE tools are based on the FIF file format from Neuromag.
+      However, MNE can read native CTF, BTI/4D, KIT and various
+      EEG formats (see :ref:`IO functions <ch_convert>`).
+
+      If you have been using MNE-C, there is no need to convert your fif
+      files to a new system or database -- MNE works nicely with
+      the historical fif files.
diff --git a/doc/git_links.inc b/doc/git_links.inc
index 48fdb44..7e61b92 100644
--- a/doc/git_links.inc
+++ b/doc/git_links.inc
@@ -62,14 +62,12 @@
 .. _pep8: https://pypi.python.org/pypi/pep8
 .. _pyflakes: https://pypi.python.org/pypi/pyflakes
 .. _coverage: https://pypi.python.org/pypi/coverage
-.. _nose-timer: https://pypi.python.org/pypi/nose-timer
-.. _nosetests: https://nose.readthedocs.org/en/latest/
 .. _mayavi: http://mayavi.sourceforge.net/
 .. _nitime: http://nipy.org/nitime/
 .. _joblib: https://pypi.python.org/pypi/joblib
 .. _scikit-learn: http://scikit-learn.org/stable/
 .. _pysurfer: http://pysurfer.github.io/
-.. _pyDICOM: https://pypi.python.org/pypi/pydicom/ 
+.. _pyDICOM: https://pypi.python.org/pypi/pydicom/
 .. _matplotlib: http://matplotlib.org/
 .. _sphinx: http://sphinx-doc.org/
 .. _pandas: http://pandas.pydata.org/
@@ -84,6 +82,9 @@
 
 .. _FreeSurfer: http://surfer.nmr.mgh.harvard.edu/fswiki/DownloadAndInstall/
 
-.. |emdash| unicode:: U+02014
+.. _Sphinx documentation: http://sphinx-doc.org/rest.html
+.. _sphinx gallery documentation: http://sphinx-gallery.readthedocs.org/en/latest/advanced_configuration.html
+.. _NumPy docstring standard: https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMENT.rst.txt
 
 .. vim: ft=rst
+
diff --git a/doc/index.rst b/doc/index.rst
index 88966cf..ec7f0d8 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -2,151 +2,112 @@
 
 .. include:: links.inc
 
-.. raw:: html
-
-    <div class="container"><div class="row">
-    <div class="col-md-8"><div style="text-align: center; height: 270px">
-    <span style="display: inline-block; height: 100%; vertical-align: middle"></span>
-    <a href="index.html"><img src="_static/mne_logo.png" border="0" alt="MNE" style="vertical-align: middle"></a>
-    </div></div>
-    <div class="col-md-4"><div style="float: left">
-    <a href="index.html"><img src="_static/institutions.png"" border="0" alt="Institutions"/></a>
-    </div></div>
-    </div></div>
-
-.. raw:: html
-
-   <div class="container-fluid">
-   <div class="row">
-   <div class="col-md-8">
-   <br>
-
-MNE is a community-driven software package designed for **processing
-electroencephalography (EEG) and magnetoencephalography (MEG) data**
-providing comprehensive tools and workflows for
-(:ref:`among other things <what_can_you_do>`):
-
-1. Preprocessing and denoising
-2. Source estimation
-3. Time–frequency analysis
-4. Statistical testing
-5. Estimation of functional connectivity
-6. Applying machine learning algorithms
-7. Visualization of sensor- and source-space data
-
-MNE includes a comprehensive Python_ package supplemented by tools compiled
-from C code for the LINUX and Mac OSX operating systems, as well as a MATLAB toolbox.
-
-**From raw data to source estimates in about 30 lines of code** (Try it :ref:`by installing it <getting_started>` or `in an experimental online demo <http://mybinder.org/repo/mne-tools/mne-binder/notebooks/plot_introduction.ipynb>`_!):
-
-.. code:: python
-
-    >>> import mne  # doctest: +SKIP
-    >>> raw = mne.io.read_raw_fif('raw.fif', preload=True)  # load data  # doctest: +SKIP
-    >>> raw.info['bads'] = ['MEG 2443', 'EEG 053']  # mark bad channels  # doctest: +SKIP
-    >>> raw.filter(l_freq=None, h_freq=40.0)  # low-pass filter data  # doctest: +SKIP
-    >>> # Extract epochs and save them:
-    >>> picks = mne.pick_types(raw.info, meg=True, eeg=True, eog=True,  # doctest: +SKIP
-    >>>                        exclude='bads')  # doctest: +SKIP
-    >>> events = mne.find_events(raw)  # doctest: +SKIP
-    >>> reject = dict(grad=4000e-13, mag=4e-12, eog=150e-6)  # doctest: +SKIP
-    >>> epochs = mne.Epochs(raw, events, event_id=1, tmin=-0.2, tmax=0.5,  # doctest: +SKIP
-    >>>                     proj=True, picks=picks, baseline=(None, 0),  # doctest: +SKIP
-    >>>                     preload=True, reject=reject)  # doctest: +SKIP
-    >>> # Compute evoked response and noise covariance
-    >>> evoked = epochs.average()  # doctest: +SKIP
-    >>> cov = mne.compute_covariance(epochs, tmax=0)  # doctest: +SKIP
-    >>> evoked.plot()  # plot evoked  # doctest: +SKIP
-    >>> # Compute inverse operator:
-    >>> fwd_fname = 'sample_audvis−meg−eeg−oct−6−fwd.fif'  # doctest: +SKIP
-    >>> fwd = mne.read_forward_solution(fwd_fname, surf_ori=True)  # doctest: +SKIP
-    >>> inv = mne.minimum_norm.make_inverse_operator(raw.info, fwd,  # doctest: +SKIP
-    >>>                                              cov, loose=0.2)  # doctest: +SKIP
-    >>> # Compute inverse solution:
-    >>> stc = mne.minimum_norm.apply_inverse(evoked, inv, lambda2=1./9.,  # doctest: +SKIP
-    >>>                                      method='dSPM')  # doctest: +SKIP
-    >>> # Morph it to average brain for group study and plot it
-    >>> stc_avg = mne.morph_data('sample', 'fsaverage', stc, 5, smooth=5)  # doctest: +SKIP
-    >>> stc_avg.plot()  # doctest: +SKIP
-
-MNE development is driven by :ref:`extensive contributions from the community <whats_new>`.
-Direct financial support for the project has been provided by:
-
-- (US) National Institute of Biomedical Imaging and Bioengineering (NIBIB)
-  grants 5R01EB009048 and P41EB015896 (Center for Functional Neuroimaging
-  Technologies)
-- (US) NSF awards 0958669 and 1042134.
-- (US) NCRR *Center for Functional Neuroimaging Technologies* P41RR14075-06
-- (US) NIH grants 1R01EB009048-01, R01 EB006385-A101, 1R01 HD40712-A1, 1R01
-  NS44319-01, and 2R01 NS37462-05
-- (US) Department of Energy Award Number DE-FG02-99ER62764 to The MIND
-  Institute.
-- (FR) IDEX Paris-Saclay, ANR-11-IDEX-0003-02, via the
-  `Center for Data Science <http://www.datascience-paris-saclay.fr/>`_.
-- (FR) European Research Council (ERC) Starting Grant (ERC-YStG-263584).
-- (FR) French National Research Agency (ANR-14-NEUC-0002-01).
-- (FR) European Research Council (ERC) Starting Grant (ERC-YStG-676943).
-- Amazon Web Services - Research Grant issued to Denis A. Engemann
-
-.. raw:: html
-
-   <div class="col-md-8">
-       <script type="text/javascript" src="http://www.ohloh.net/p/586838/widgets/project_basic_stats.js"></script>
-   </div>
-
-
-.. raw:: html
-
-   </div>
-   <div class="col-md-4">
-   <h2>Documentation</h2>
-
-.. toctree::
-   :maxdepth: 1
-
-   getting_started
-   tutorials
-   auto_examples/index
-   faq
-   contributing
-
-.. toctree::
-   :maxdepth: 1
-
-   python_reference
-   manual/index
-   whats_new
-
-.. toctree::
-   :maxdepth: 1
-
-   cite
-   references
-   cited
-
-.. raw:: html
-
-   <h2>Community</h2>
-
-* Analysis talk: join the `MNE mailing list`_
-
-* `Feature requests and bug reports on GitHub <https://github.com/mne-tools/mne-python/issues/>`_
-
-* `Chat with developers on Gitter <https://gitter.im/mne-tools/mne-python>`_
-
-.. raw:: html
-
-   <h2>Versions</h2>
-
-   <ul>
-      <li><a href=http://martinos.org/mne/stable>Stable</a></li>
-      <li><a href=http://martinos.org/mne/dev>Development</a></li>
-   </ul>
-
-   <div style="float: left; padding: 10px; width: 100%;">
-       <a class="twitter-timeline" href="https://twitter.com/mne_python" data-widget-id="317730454184804352">Tweets by @mne_python</a>
-   </div>
-
-   </div>
-   </div>
-   </div>
+
+.. container:: row limitedwidth table-like
+
+    .. title image and description
+    .. raw:: html
+
+      <div class="cell-like col-sm-8">
+        <img src="_static/mne_logo.png" alt="MNE" class="center-block">
+        <p class="tagline">
+        Open-source Python software for exploring, visualizing, and
+        analyzing human neurophysiological data: MEG, EEG, sEEG, ECoG,
+        and more.
+        </h4>
+      </div>
+      <div class="cell-like col-sm-4 text-right">
+        <h3><i class="fa fa-bolt"></i> Speed</h3>
+        <p>Multi-core CPU & GPU.</p>
+        <h3><i class="fa fa-eye"></i> Usability</h3>
+        <p>Clean scripting & visualization.</p>
+        <h3><i class="fa fa-sliders"></i> Flexibility</h3>
+        <p>Broad data format & analysis support.
+      </div>
+
+.. container:: row topmargin limitedwidth
+
+    .. container:: col-sm-12 topmargin
+
+      .. include:: carousel.inc
+
+.. container:: row limitedwidth
+
+    .. buttons
+    .. raw:: html
+
+        <div class="col-lg-3 col-md-4 col-sm-6 table-like bottommargin">
+        <a href="documentation.html#collapse_io" class="btn btn-primary btn-lg btn-cont">Data I/O</a>
+        </div>
+        <div class="col-lg-3 col-md-4 col-sm-6 table-like bottommargin">
+        <a href="documentation.html#collapse_preprocessing" class="btn btn-primary btn-lg btn-cont">Preprocessing</a>
+        </div>
+        <div class="col-lg-3 col-md-4 col-sm-6 table-like bottommargin">
+        <a href="documentation.html#collapse_visualization" class="btn btn-primary btn-lg btn-cont">Visualization</a>
+        </div>
+        <div class="col-lg-3 col-md-4 col-sm-6 table-like bottommargin">
+        <a href="documentation.html#collapse_source" class="btn btn-primary btn-lg btn-cont">Source estimation</a>
+        </div>
+        <div class="col-lg-3 col-md-4 col-sm-6 table-like bottommargin">
+        <a href="documentation.html#collapse_tf" class="btn btn-primary btn-lg btn-cont">Time-frequency</a>
+        </div>
+        <div class="col-lg-3 col-md-4 col-sm-6 table-like bottommargin">
+        <a href="documentation.html#collapse_connectivity" class="btn btn-primary btn-lg btn-cont">Connectivity</a>
+        </div>
+        <div class="col-lg-3 col-md-4 col-sm-6 table-like bottommargin">
+        <a href="documentation.html#collapse_ml" class="btn btn-primary btn-lg btn-cont">Machine learning</a>
+        </div>
+        <div class="col-lg-3 col-md-4 col-sm-6 table-like bottommargin">
+        <a href="documentation.html#collapse_statistics" class="btn btn-primary btn-lg btn-cont">Statistics</a>
+        </div>
+
+.. container:: row limitedwidth
+
+    .. financial support
+    .. container:: col-sm-8
+
+      .. raw:: html
+
+        <div class="list-group">
+        <div class="list-group-item">
+        <h4 class="list-group-item-heading">
+        <span class="flag-icon flag-icon-us"></span>
+        Direct financial support:
+        </h4>
+        <div class="list-group-item-text support-front ul-2col">
+
+      - **NIH** 5R01EB009048, 1R01EB009048, R01EB006385, 1R01HD40712,
+        1R01NS44319, 2R01NS37462, P41EB015896
+      - **NSF** 0958669, 1042134
+      - **NCRR** P41RR14075-06
+      - **DoE** DE-FG02-99ER62764 (MIND)
+      - **Amazon** - AWS Research Grants
+
+      .. raw:: html
+
+        </div>
+        </div>
+        </div>
+
+    .. container:: col-sm-4
+
+      .. raw:: html
+
+        <div class="list-group">
+        <div class="list-group-item">
+        <h4 class="list-group-item-heading">
+        <span class="flag-icon flag-icon-fr"></span>
+        Direct financial support:
+        </h4>
+        <div class="list-group-item-text support-front">
+
+      - **IDEX** Paris-Saclay, ANR-11-IDEX-0003-02
+        `CDS <http://www.datascience-paris-saclay.fr/>`_
+      - **ERC** ERC-YStG-263584, ERC-YStG-676943
+      - **ANR** ANR-14-NEUC-0002-01
+
+      .. raw:: html
+
+        </div>
+        </div>
+        </div>
diff --git a/doc/install_mne_c.rst b/doc/install_mne_c.rst
index cb2c35a..08e06f2 100644
--- a/doc/install_mne_c.rst
+++ b/doc/install_mne_c.rst
@@ -5,52 +5,37 @@
 Install MNE-C
 -------------
 
-Some advanced functionality is provided by the MNE-C command-line tools.
-It is not strictly necessary to have the MNE-C tools installed to use
-MNE-Python, but it can be helpful.
-
-The MNE Unix commands can be downloaded at:
-
-* `Download MNE <http://www.nmr.mgh.harvard.edu/martinos/userInfo/data/MNE_register/index.php>`_
-
-:ref:`c_reference` gives an overview of the command line
-tools provided with MNE.
+The MNE-C commands can be downloaded
+`here <http://www.nmr.mgh.harvard.edu/martinos/userInfo/data/MNE_register/index.php>`_.
+The :ref:`c_reference` gives an overview of the MNE-C tools.
 
 .. contents:: Contents
    :local:
    :depth: 1
 
-System requirements
-###################
+System requirements and installation
+####################################
 
-The MNE Unix commands runs on Mac OSX and LINUX operating systems.
-The hardware and software requirements are:
+The MNE-C runs on Mac OSX and LINUX, and requires:
 
 - Mac OSX version 10.5 (Leopard) or later.
-
 - LINUX kernel 2.6.9 or later
-
 - On both LINUX and Mac OSX 32-bit and 64-bit Intel platforms
   are supported. PowerPC version on Mac OSX can be provided upon request.
-
 - At least 2 GB of memory, 4 GB or more recommended.
-
 - Disk space required for the MNE software: 80 MB
-
 - Additional open source software on Mac OSX, see :ref:`BABDBCJE`.
 
-Installation
-############
-
 The MNE software is distributed as a compressed tar archive
 (Mac OSX and LINUX) or a Mac OSX disk image (dmg).
 
 The file names follow the convention:
 
-MNE-* <*version*>*- <*rev*> -* <*Operating
-system*>*-* <*Processor*>*.* <*ext*>*
+::
 
-The present version number is 2.7.0. The <*rev*> field
+  MNE-* <*version*>*- <*rev*> -* <*Operating system*>*-* <*Processor*>*.* <*ext*>*
+
+The present version number is 2.7.4. The <*rev*> field
 is the SVN revision number at the time this package was created.
 The <*Operating system*> field
 is either Linux or MacOSX. The <*processor*> field
@@ -63,13 +48,13 @@ Installing from a compressed tar archive
 
 Go to the directory where you want the software to be installed:
 
-.. code-block:: bash
+.. code-block:: console
 
     $ cd <dir>
 
 Unpack the tar archive:
 
-.. code-block:: bash
+.. code-block:: console
 
     $ tar zxvf <software package>
 
@@ -95,21 +80,21 @@ Installing from a Mac OSX disk image
 
 .. _user_environment:
 
-Setting up MNE Unix commands environment
-########################################
+Setting up MNE-C environment
+############################
 
 The system-dependent location of the MNE Software will be
 here referred to by the environment variable MNE_ROOT. There are
 two scripts for setting up user environment so that the software
 can be used conveniently:
 
-.. code-block:: bash
+.. code-block:: console
 
     $ $MNE_ROOT/bin/mne_setup_sh
 
 and
 
-.. code-block:: bash
+.. code-block:: console
 
     $ $MNE_ROOT/bin/mne_setup
 
@@ -118,14 +103,14 @@ the scripts set environment variables they should be 'sourced' to
 the present shell. You can find which type of a shell you are using
 by saying
 
-.. code-block:: bash
+.. code-block:: console
 
     $ echo $SHELL
 
-If the output indicates a POSIX shell (bash or sh) you should issue
+If the output indicates a POSIX shell (``bash`` or ``sh``) you should issue
 the three commands:
 
-.. code-block:: bash
+.. code-block:: console
 
     $ export MNE_ROOT=<MNE>
     $ export MATLAB_ROOT=<Matlab>
@@ -184,8 +169,11 @@ listed in :ref:`CIHDGFAA`.
 
 .. _BABDBCJE:
 
+Additional options
+##################
+
 Additional software
-###################
+^^^^^^^^^^^^^^^^^^^
 
 MNE uses the `Netpbm package <http://netpbm.sourceforge.net/>`_
 to create image files in formats other than tif and rgb from
@@ -209,7 +197,7 @@ the X11 included with the operating system is sufficient.
 .. _CIHIIBDA:
 
 Testing the performance of your OpenGL graphics
-###############################################
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 The graphics performance of mne_analyze depends
 on your graphics software and hardware configuration. You get the
@@ -240,7 +228,7 @@ key (usually control-c) is pressed on the terminal window.
 mne_opengl_test is located
 in the ``bin`` directory and is thus started as:
 
-.. code-block:: bash
+.. code-block:: console
 
     $ $MNE_ROOT/bin/mne_opengl_test
 
@@ -248,11 +236,3 @@ On the fastest graphics cards, the time per revolution is
 well below 1 second. If this time longer than 10 seconds either
 the graphics hardware acceleration is not in effect or you need
 a faster graphics adapter.
-
-Obtain FreeSurfer
-#################
-
-The MNE software relies on the FreeSurfer software for cortical
-surface reconstruction and other MRI-related tasks. Please consult the
-FreeSurfer_ home page.
-
diff --git a/doc/install_mne_python.rst b/doc/install_mne_python.rst
index a92332a..b57d154 100644
--- a/doc/install_mne_python.rst
+++ b/doc/install_mne_python.rst
@@ -5,273 +5,73 @@
 Install Python and MNE-Python
 -----------------------------
 
-To use MNE-Python, you need two things:
-
-1. A working Python interpreter and dependencies
-
-2. The MNE-Python package installed to the Python distribution
-
-Step-by-step instructions to accomplish this are given below.
-
 .. contents:: Steps
    :local:
-   :depth: 1
-
-.. note:: Users who work at a facility with a site-wide install of
-          MNE-Python (e.g. Martinos center) are encouraged to contact
-          their technical staff about how to access and use MNE-Python,
-          as the instructions might differ.
-
-.. _install_interpreter:
-
-1. Install a Python interpreter and dependencies
-################################################
-
-There are multiple options available for getting a suitable Python interpreter
-running on your system. However, for a fast and up to date scientific Python
-environment that resolves all dependencies, we highly recommend following
-installation instructions for the Anaconda Python distribution. Go here
-to download, and follow the installation instructions:
-
-* https://www.continuum.io/downloads
-* http://docs.continuum.io/anaconda/install
-
-.. note:: Intel's `Math Kernel Library <https://software.intel.com/en-us/intel-mkl>`_
-          speeds up many linear algebra comptutations, some by an order of
-          magnitude. It is included by default in Anaconda, which makes it
-          preferable to some other potentially viable Python interpreter
-          options (e.g., using ``brew`` in OSX or ``sudo apt-get install``
-          on Ubuntu to get a usable Python interpreter). 
-
-If everything is set up correctly, you should be able to check the version
-of ``conda`` that is installed (your version number will probably be newer)
-and which ``python`` will be called when you run ``python``:
-
-.. code-block:: bash
-
-    $ conda --version
-    conda 3.19.1
-    $ which python
-    /home/agramfort/anaconda/bin/python
-
-If your installation doesn't look something like this,
-*something went wrong* and you should try to fix it. We recommend looking
-through the Anaconda documentation a bit, and Googling for Anaconda
-install tips (StackExchange results are often helpful).
-
-Once Anaconda works properly, you can do this to resolve
-the MNE-Python dependencies:
-
-.. code-block:: bash
-
-    $ conda install scipy matplotlib scikit-learn
-
-To test that everything works properly, open up IPython:
-
-.. code-block:: bash
-
-    $ ipython --matplotlib=qt
-
-Now that you have a working Python environment you can install MNE-Python.
-
-If you want to have an environment with a clean MATLAB-like interface,
-consider using Spyder_, which can easily be installed with Anaconda
-as:
-
-.. code-block:: bash
-
-    $ conda install spyder
-
-.. _install_mne_python:
-
-2. Install the MNE-Python package
-#################################
-
-There are a many options for installing MNE-Python, but two of the most
-useful and common are:
-
-1. **Use the stable release version of MNE-Python.** It can be installed as:
-
-   .. code-block:: bash
-
-       $ pip install mne --upgrade
-
-   MNE-Python tends to release about once every six months, and this
-   command can be used to update the install after each release.
-
-.. _installing_master:
-
-2. **Use the development master version of MNE-Python.** If you want to
-   be able to update your MNE-Python version between releases (e.g., for
-   bugfixes or new features), this will set you up for frequent updates:
-
-   .. code-block:: bash
-
-       $ git clone git://github.com/mne-tools/mne-python.git
-       $ cd mne-python
-       $ python setup.py develop
-
-   A cool feature of ``python setup.py develop`` is that any changes made to
-   the files (e.g., by updating to latest ``master``) will be reflected in
-   ``mne`` as soon as you restart your Python interpreter. So to update to
-   the latest version of the ``master`` development branch, you can do:
-
-   .. code-block:: bash
-
-       $ git pull origin master
-
-   and your MNE-Python will be updated to have the latest changes.
-   If you plan to contribute to MNE-Python, you should follow a variant
-   of this approach outlined in the
-   :ref:`contribution instructions <contributing>`. 
-
-3. Check your installation
-##########################
-
-To check that everything went fine, in ipython, type::
-
-    >>> import mne
-
-If you get a new prompt with no error messages, you should be good to go!
-
-A good place to start is on our :ref:`tutorials` page or with our
-:ref:`general_examples`.
-
-Along the way, make frequent use of :ref:`api_reference` and
-:ref:`manual` to understand the capabilities of MNE.
-
-4. Optional advanced setup
-##########################
-
-.. _CUDA:
-
-CUDA
-^^^^
-
-We have developed specialized routines to make use of
-`NVIDIA CUDA GPU processing <http://www.nvidia.com/object/cuda_home_new.html>`_
-to speed up some operations (e.g. FIR filtering) by up to 10x. 
-If you want to use NVIDIA CUDA, you should install:
-
-1. `the NVIDIA toolkit on your system <https://developer.nvidia.com/cuda-downloads>`_
-2. `PyCUDA <http://wiki.tiker.net/PyCuda/Installation/>`_
-3. `skcuda <https://github.com/lebedov/scikits.cuda>`_
-
-For example, on Ubuntu 15.10, a combination of system packages and ``git``
-packages can be used to install the CUDA stack:
-
-.. code-block:: bash
-
-    # install system packages for CUDA
-    $ sudo apt-get install nvidia-cuda-dev nvidia-modprobe
-    # install PyCUDA
-    $ git clone http://git.tiker.net/trees/pycuda.git
-    $ cd pycuda
-    $ ./configure.py --cuda-enable-gl
-    $ git submodule update --init
-    $ make -j 4
-    $ python setup.py install
-    # install skcuda
-    $ cd ..
-    $ git clone https://github.com/lebedov/scikit-cuda.git
-    $ cd scikit-cuda
-    $ python setup.py install
-
-To initialize mne-python cuda support, after installing these dependencies
-and running their associated unit tests (to ensure your installation is correct)
-you can run:
-
-.. code-block:: bash
-
-    $ MNE_USE_CUDA=true MNE_LOGGING_LEVEL=info python -c "import mne; mne.cuda.init_cuda()"
-    Enabling CUDA with 1.55 GB available memory
-
-If you have everything installed correctly, you should see an INFO-level log
-message telling you your CUDA hardware's available memory. To have CUDA
-initialized on startup, you can do::
-
-    >>> mne.utils.set_config('MNE_USE_CUDA', 'true') # doctest: +SKIP
-
-You can test if MNE CUDA support is working by running the associated test:
-
-.. code-block:: bash
-
-    $ nosetests mne/tests/test_filter.py
-
-If ``MNE_USE_CUDA=true`` and all tests pass with none skipped, then
-MNE-Python CUDA support works.
-
-IPython (and notebooks)
-^^^^^^^^^^^^^^^^^^^^^^^
-
-In IPython, we strongly recommend using the Qt matplotlib backend for
-fast and correct rendering:
-
-.. code-block:: bash
-
-    $ ipython --matplotlib=qt
-
-On Linux, for example, QT is the only matplotlib backend for which 3D rendering
-will work correctly. On Mac OS X for other backends certain matplotlib
-functions might not work as expected.
-
-To take full advantage of MNE-Python's visualization capacities in combination
-with IPython notebooks and inline displaying, please explicitly add the
-following magic method invocation to your notebook or configure your notebook
-runtime accordingly::
-
-    In [1]: %matplotlib inline
-
-If you use another Python setup and you encounter some difficulties please
-report them on the MNE mailing list or on github to get assistance.
+   :depth: 2
 
-Mayavi and PySurfer
-^^^^^^^^^^^^^^^^^^^
+1. Install a Python interpreter
+###############################
 
-Mayavi is currently only available for Python2.7.
-The easiest way to install `mayavi` is to do the following with Anaconda:
+* For a fast and up to date scientific Python environment, **we recommend the
+  Anaconda Python 2.7 distribution**. Get it for Windows, OSX, and Linux
+  `here <http://docs.continuum.io/anaconda/install>`_.
 
-.. code-block:: bash
+  .. note :: Python has two major versions currently available, 2.7+ and 3.3+.
+             Currently 3D visualization is only officially supported on 2.7.
 
-    $ conda install mayavi
+* Once everything is set up, check the installation:
 
-For other methods of installation, please consult the
-`Mayavi documentation <http://docs.enthought.com/mayavi/mayavi/installation.html>`_.
+  .. code-block:: console
 
-The PySurfer package, which is used for visualizing cortical source estimates,
-uses Mayavi and can be installed using:
+      $ conda --version
+      conda 4.2.14
+      $ python --version
+      Python 2.7.12 :: Continuum Analytics, Inc.
 
-.. code-block:: bash
+  If your installation doesn't look something like this, **something went wrong**.
+  Try looking through the Anaconda documentation or Googling for Anaconda install
+  tips (StackExchange results are often helpful).
 
-    $ pip install PySurfer
+2. Install dependencies and MNE
+###############################
 
-Some users may need to configure PySurfer before they can make full use of
-our visualization capabilities. Please refer to the
-`PySurfer installation page <https://pysurfer.github.io/install.html>`_
-for up to date information.
+* From the command line, install the MNE dependencies to the root Anaconda environment:
 
-Troubleshooting
-###############
+  .. raw:: html
 
-If you run into trouble when visualizing source estimates (or anything else
-using mayavi), you can try setting ETS_TOOLKIT environment variable::
+      <div class="row container">
+        <div class="col-sm-7 container">
 
-    >>> import os
-    >>> os.environ['ETS_TOOLKIT'] = 'qt4'
-    >>> os.environ['QT_API'] = 'pyqt'
+  .. code-block:: console
 
-This will tell Traits that we will use Qt with PyQt bindings.
+      $ conda install scipy matplotlib scikit-learn mayavi jupyter spyder
+      $ pip install PySurfer mne
 
-If you get an error saying::
+  .. raw:: html
 
-    ValueError: API 'QDate' has already been set to version 1
+         </div>
+         <div class="col-sm-4 container">
+          <div class="panel panel-success">
+            <div class="panel-heading"><h1 class="panel-title"><a data-toggle="collapse" href="#collapse_conda"><strong>Experimental</strong> Python 3.6 alternative ▼</a></h1></div>
+            <div id="collapse_conda" class="panel-body panel-collapse collapse">
+              <p>Try the conda environment available
+              <a class="reference external" href="https://raw.githubusercontent.com/mne-tools/mne-python/master/environment.yml">here</a>:
+              </p>
+              <div class="highlight-console">
+                <div class="highlight">
+                  <pre><span></span><span class="gp">$</span> curl -O https://raw.githubusercontent.com/mne-tools/mne-python/master/environment.yml<br><span class="gp">$</span> conda env create -f environment.yml<br><span class="gp">$</span> source activate mne</pre>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
 
-you have run into a conflict with Traits. You can work around this by telling
-the interpreter to use QtGui and QtCore from pyface::
+* To check that everything went fine, in Python, type::
 
-    >>> from pyface.qt import QtGui, QtCore
+      >>> import mne
 
-This line should be added before any imports from mne-python.
+  If you get a new prompt with no error messages, you should be good to go!
 
-For more information, see
-http://docs.enthought.com/mayavi/mayavi/building_applications.html.
+* For advanced topics like how to get NVIDIA :ref:`CUDA` support or if you're
+  having trouble, visit :ref:`advanced_setup`.
diff --git a/doc/known_projects.inc b/doc/known_projects.inc
index 3d040ac..88951f4 100644
--- a/doc/known_projects.inc
+++ b/doc/known_projects.inc
@@ -49,3 +49,9 @@
 .. Astropy
 .. _Astropy: http://www.astropy.org
 .. _`Astropy GitHub`: http://github.com/astropy/astropy
+
+.. Pytest
+.. _pytest: https://docs.pytest.org/
+
+.. Flake8
+.. _Flake8: http://flake8.pycqa.org/
diff --git a/doc/manual/appendix/c_release_notes.rst b/doc/manual/appendix/c_release_notes.rst
index 53865cf..977b2ce 100644
--- a/doc/manual/appendix/c_release_notes.rst
+++ b/doc/manual/appendix/c_release_notes.rst
@@ -701,7 +701,7 @@ MNE software and setup for individual users:
 - The setup scripts have changed.
 
 The installation and user-level effects of the new software
-organization are discussed in :ref:`getting_started`.
+organization are discussed in :ref:`install_mne_c`.
 
 In addition, several minor bugs have been fixed in the source
 code. Most relevant changes visible to the user are listed below.
diff --git a/doc/manual/channel_interpolation.rst b/doc/manual/channel_interpolation.rst
index 19c942a..a7a6431 100644
--- a/doc/manual/channel_interpolation.rst
+++ b/doc/manual/channel_interpolation.rst
@@ -5,8 +5,8 @@
 
 .. _channel_interpolation:
 
-Repairing bad channels
-######################
+Bad channel repair via interpolation
+####################################
 
 Spherical spline interpolation (EEG)
 ====================================
@@ -58,7 +58,7 @@ where :math:`G_{ds} \in R^{M \times N}` computes :math:`g_{m}(\boldsymbol{r_i},
 
 To interpolate bad channels, one can simply do:
 
-	>>> evoked.interpolate_bads(reset_bads=False)
+	>>> evoked.interpolate_bads(reset_bads=False)  # doctest: +SKIP
 
 and the bad channel will be fixed
 
diff --git a/doc/manual/cookbook.rst b/doc/manual/cookbook.rst
index fab76e1..1d40688 100644
--- a/doc/manual/cookbook.rst
+++ b/doc/manual/cookbook.rst
@@ -1,8 +1,8 @@
 .. _cookbook:
 
-========
-Cookbook
-========
+==========================
+The typical M/EEG workflow
+==========================
 
 .. contents:: Contents
    :local:
@@ -48,7 +48,7 @@ Sometimes some MEG or EEG channels are not functioning properly
 for various reasons. These channels should be excluded from
 analysis by marking them bad as::
 
-    >>> raw.info['bads'] = ['MEG2443']
+    >>> raw.info['bads'] = ['MEG2443']  # doctest: +SKIP
 
 Especially if a channel does not show
 a signal at all (flat) it is important to exclude it from the
@@ -122,17 +122,17 @@ Epoching of raw data is done using events, which define a ``t=0`` for your
 data chunks. Event times stamped to the acquisition software can be extracted
 using :func:`mne.find_events`::
 
-    >>> events = mne.find_events(raw)
+    >>> events = mne.find_events(raw)  # doctest: +SKIP
 
 The ``events`` array can then be modified, extended, or changed if necessary.
 If the original trigger codes and trigger times are correct for the analysis
 of interest, :class:`mne.Epochs` for the first event type (``1``) can be
 constructed using::
 
-    >>> reject = dict(grad=4000e-13, mag=4e-12, eog=150e-6)
-    >>> epochs = mne.Epochs(raw, events, event_id=1, tmin=-0.2, tmax=0.5,
-    >>>                     proj=True, picks=picks, baseline=(None, 0),
-    >>>                     preload=True, reject=reject)
+    >>> reject = dict(grad=4000e-13, mag=4e-12, eog=150e-6)  # doctest: +SKIP
+    >>> epochs = mne.Epochs(raw, events, event_id=1, tmin=-0.2, tmax=0.5,  # doctest: +SKIP
+    >>>                     proj=True, picks=picks, baseline=(None, 0),  # doctest: +SKIP
+    >>>                     preload=True, reject=reject)  # doctest: +SKIP
 
 .. note:: The rejection thresholds (set with argument ``reject``) are defined
           in T / m for gradiometers, T for magnetometers and V for EEG and EOG
@@ -151,7 +151,7 @@ information.
 Once the :class:`mne.Epochs` are constructed, they can be averaged to obtain
 :class:`mne.Evoked` data as::
 
-    >>> evoked = epochs.average()
+    >>> evoked = epochs.average()  # doctest: +SKIP
 
 
 Source localization
@@ -210,8 +210,8 @@ has been completed as described in :ref:`CHDBBCEJ`.
 For example, to create the reconstruction geometry for ``subject='sample'``
 with a ~5-mm spacing between the grid points, say::
 
-    >>> src = setup_source_space('sample', spacing='oct6')
-    >>> write_source_spaces('sample-oct6-src.fif', src)
+    >>> src = setup_source_space('sample', spacing='oct6')  # doctest: +SKIP
+    >>> write_source_spaces('sample-oct6-src.fif', src)  # doctest: +SKIP
 
 This creates the source spaces and writes them to disk.
 
@@ -259,8 +259,8 @@ Setting up the boundary-element model
 This stage sets up the subject-dependent data for computing
 the forward solutions:"
 
-    >>> model = make_bem_model('sample')
-    >>> write_bem_surfaces('sample-5120-5120-5120-bem.fif', model)
+    >>> model = make_bem_model('sample')  # doctest: +SKIP
+    >>> write_bem_surfaces('sample-5120-5120-5120-bem.fif', model)  # doctest: +SKIP
 
 Where ``surfaces`` is a list of BEM surfaces that have each been read using
 :func:`mne.read_surface`. This step also checks that the input surfaces
@@ -290,17 +290,18 @@ segmentation.
 Using this model, the BEM solution can be computed using
 :func:`mne.make_bem_solution` as::
 
-    >>> bem_sol = make_bem_solution(model)
-    >>> write_bem_solution('sample-5120-5120-5120-bem-sol.fif', bem_sol)
+    >>> bem_sol = make_bem_solution(model)  # doctest: +SKIP
+    >>> write_bem_solution('sample-5120-5120-5120-bem-sol.fif', bem_sol)  # doctest: +SKIP
 
 After the BEM is set up it is advisable to check that the
-BEM model meshes are correctly positioned using *e.g.*, :class:`mne.Report`.
+BEM model meshes are correctly positioned using *e.g.*
+:func:`mne.viz.plot_alignment` or :class:`mne.report.Report`.
 
 .. note:: Up to this point all processing stages depend on the
           anatomical (geometrical) information only and thus remain
           identical across different MEG studies.
 
-.. note:: If you use custom head models you might need to set the ``ico=None`` 
+.. note:: If you use custom head models you might need to set the ``ico=None``
           parameter to ``None`` and skip subsampling of the surface.
 
 
@@ -343,7 +344,7 @@ potentials at the measurement sensors and electrodes due to dipole
 sources located on the cortex, can be calculated with help of
 :func:`mne.make_forward_solution` as::
 
-    >>> fwd = make_forward_solution(raw.info, fname_trans, src, bem_sol)
+    >>> fwd = make_forward_solution(raw.info, fname_trans, src, bem_sol)  # doctest: +SKIP
 
 .. _BABDEEEB:
 
@@ -364,14 +365,14 @@ ways:
   This is the recommended approach for evoked responses, *e.g.* using
   :func:`mne.compute_covariance`::
 
-      >>> cov = mne.compute_covariance(epochs, method='auto')
+      >>> cov = mne.compute_covariance(epochs, method='auto')  # doctest: +SKIP
 
 - Employ empty room data (collected without the subject) to
   calculate the full noise covariance matrix. This is recommended
   for analyzing ongoing spontaneous activity. This can be done using
   :func:`mne.compute_raw_covariance` as::
 
-      >>> cov = mne.compute_raw_covariance(raw_erm)
+      >>> cov = mne.compute_raw_covariance(raw_erm)  # doctest: +SKIP
 
 - Employ a section of continuous raw data collected in the presence
   of the subject to calculate the full noise covariance matrix. This
@@ -401,7 +402,7 @@ please consult :ref:`CBBDJFBJ`.
 This computation stage can be done by using
 :func:`mne.minimum_norm.make_inverse_operator` as::
 
-    >>> inv = mne.minimum_norm.make_inverse_operator(raw.info, fwd, cov, loose=0.2)
+    >>> inv = mne.minimum_norm.make_inverse_operator(raw.info, fwd, cov, loose=0.2)  # doctest: +SKIP
 
 Creating source estimates
 -------------------------
@@ -410,11 +411,11 @@ Once all the preprocessing steps described above have been
 completed, the inverse operator computed can be applied to the MEG
 and EEG data as::
 
-    >>> stc = mne.minimum_norm.apply_inverse(evoked, inv, lambda2=1. / 9.)
+    >>> stc = mne.minimum_norm.apply_inverse(evoked, inv, lambda2=1. / 9.)  # doctest: +SKIP
 
 And the results can be viewed as::
 
-    >>> stc.plot()
+    >>> stc.plot()  # doctest: +SKIP
 
 The interactive analysis tool :ref:`mne_analyze` can also
 be used to explore the data and to produce quantitative analysis
@@ -427,6 +428,6 @@ Group analyses
 Group analysis is facilitated by morphing source estimates, which can be
 done *e.g.*, to ``subject='fsaverage'`` as::
 
-    >>> stc_fsaverage = stc.morph('fsaverage')
+    >>> stc_fsaverage = stc.morph('fsaverage')  # doctest: +SKIP
 
 See :ref:`ch_morph` for more information.
diff --git a/doc/manual/datasets_index.rst b/doc/manual/datasets_index.rst
index 31484ed..f55a6d9 100644
--- a/doc/manual/datasets_index.rst
+++ b/doc/manual/datasets_index.rst
@@ -12,17 +12,16 @@ use the ``data_path`` (fetches full dataset) or the ``load_data`` (fetches datas
 
 Sample
 ======
-:ref:`ch_sample_data` is recorded using a 306-channel Neuromag vectorview system. 
+:func:`mne.datasets.sample.data_path`
+
+:ref:`ch_sample_data` is recorded using a 306-channel Neuromag vectorview system.
 
 In this experiment, checkerboard patterns were presented to the subject
 into the left and right visual field, interspersed by tones to the
 left or right ear. The interval between the stimuli was 750 ms. Occasionally
 a smiley face was presented at the center of the visual field.
 The subject was asked to press a key with the right index finger
-as soon as possible after the appearance of the face. To fetch this dataset, do::
-
-    from mne.datasets import sample
-    data_path = sample.data_path()  # returns the folder in which the data is locally stored.
+as soon as possible after the appearance of the face.
 
 Once the ``data_path`` is known, its contents can be examined using :ref:`IO functions <ch_convert>`.
 
@@ -35,37 +34,30 @@ IO for the `ctf` format as well in addition to the C converter utilities. Please
 
 Auditory
 ^^^^^^^^
-To access the data, use the following Python commands::
-    
-    from mne.datasets.brainstorm import bst_raw
-    data_path = bst_raw.data_path()
+:func:`mne.datasets.brainstorm.bst_raw.data_path`.
 
-Further details about the data can be found at the `auditory dataset tutorial`_ on the Brainstorm website.
+Details about the data can be found at the Brainstorm `auditory dataset tutorial`_.
 
 .. topic:: Examples
 
-    * :ref:`Brainstorm auditory dataset tutorial<sphx_glr_auto_examples_datasets_plot_brainstorm_data.py>`: Partially replicates the original Brainstorm tutorial.
+    * :ref:`Brainstorm auditory dataset tutorial <sphx_glr_auto_examples_datasets_plot_brainstorm_data.py>`: Partially replicates the original Brainstorm tutorial.
 
 Resting state
 ^^^^^^^^^^^^^
-To access the data, use the Python command::
-
-    from mne.datasets.brainstorm import bst_resting
-    data_path = bst_resting.data_path()
+:func:`mne.datasets.brainstorm.bst_resting.data_path`
 
-Further details can be found at the `resting state dataset tutorial`_ on the Brainstorm website.
+Details can be found at the Brainstorm `resting state dataset tutorial`_.
 
 Median nerve
 ^^^^^^^^^^^^
-To access the data, use the Python command::
+:func:`mne.datasets.brainstorm.bst_raw.data_path`
 
-    from mne.datasets.brainstorm import bst_raw
-    data_path = bst_raw.data_path()
-
-Further details can be found at the `median nerve dataset tutorial`_ on the Brainstorm website.
+Details can be found at the Brainstorm `median nerve dataset tutorial`_.
 
 MEGSIM
 ======
+:func:`mne.datasets.megsim.load_data`
+
 This dataset contains experimental and simulated MEG data. To load data from this dataset, do::
 
     from mne.io import Raw
@@ -81,10 +73,9 @@ Detailed description of the dataset can be found in the related publication [1]_
 
 SPM faces
 =========
-The `SPM faces dataset`_ contains EEG, MEG and fMRI recordings on face perception. To access this dataset, do::
+:func:`mne.datasets.spm_face.data_path`
 
-    from mne.datasets import spm_face
-    data_path = spm_face.data_path()
+The `SPM faces dataset`_ contains EEG, MEG and fMRI recordings on face perception.
 
 .. topic:: Examples
 
@@ -92,6 +83,7 @@ The `SPM faces dataset`_ contains EEG, MEG and fMRI recordings on face perceptio
 
 EEGBCI motor imagery
 ====================
+:func:`mne.datasets.eegbci.load_data`
 
 The EEGBCI dataset is documented in [2]_. The data set is available at PhysioNet [3]_.
 The dataset contains 64-channel EEG recordings from 109 subjects and 14 runs on each subject in EDF+ format.
@@ -116,6 +108,94 @@ to discuss the possibility to add more publicly available datasets.
 .. _median nerve dataset tutorial: http://neuroimage.usc.edu/brainstorm/DatasetMedianNerveCtf
 .. _SPM faces dataset: http://www.fil.ion.ucl.ac.uk/spm/data/mmfaces/
 
+Somatosensory
+=============
+:func:`mne.datasets.somato.data_path`
+
+This dataset contains somatosensory data with event-related synchronizations
+(ERS) and desynchronizations (ERD).
+
+.. topic:: Examples
+
+    * :ref:`sphx_glr_auto_tutorials_plot_sensors_time_frequency.py`
+
+Multimodal
+==========
+:func:`mne.datasets.multimodal.data_path`
+
+This dataset contains a single subject recorded at Otaniemi (Aalto University)
+with auditory, visual, and somatosensory stimuli.
+
+.. topic:: Examples
+
+    * :ref:`sphx_glr_auto_examples_io_plot_elekta_epochs.py`
+
+
+High frequency SEF
+==================
+:func:`mne.datasets.hf_sef.data_path()`
+
+This dataset contains somatosensory evoked fields (median nerve stimulation)
+with thousands of epochs. It was recorded with an Elekta TRIUX MEG device at
+a sampling frequency of 3 kHz. The dataset is suitable for investigating
+high-frequency somatosensory responses. Data from two subjects are included
+with MRI images in DICOM format and FreeSurfer reconstructions.
+
+
+Visual 92 object categories
+===========================
+:func:`mne.datasets.visual_92_categories.data_path`.
+
+This dataset is recorded using a 306-channel Neuromag vectorview system.
+
+Experiment consisted in the visual presentation of 92 images of human, animal
+and inanimate objects either natural or artificial [4]_. Given the high number
+of conditions this dataset is well adapted to an approach based on
+Representational Similarity Analysis (RSA).
+
+.. topic:: Examples
+
+    * :ref:`Representational Similarity Analysis (RSA) <sphx_glr_auto_examples_decoding_decoding_rsa.py>`: Partially replicates the results from Cichy et al. (2014).
+
+
+mTRF Dataset
+============
+:func:`mne.datasets.mtrf.data_path`.
+
+This dataset contains 128 channel EEG as well as natural speech stimulus features,
+which is also available `here <https://sourceforge.net/projects/aespa/files/>`_.
+
+The experiment consisted of subjects listening to natural speech.
+The dataset contains several feature representations of the speech stimulus,
+suitable for using to fit continuous regression models of neural activity.
+More details and a description of the package can be found in [5]_.
+
+.. topic:: Examples
+
+    * :ref:`Receptive Field Estimation and Prediction <sphx_glr_auto_examples_decoding_plot_receptive_field.py>`: Partially replicates the results from Crosse et al. (2016).
+
+Miscellaneous Datasets
+======================
+These datasets are used for specific purposes in the documentation and in
+general are not useful for separate analyses.
+
+ECoG Dataset
+^^^^^^^^^^^^
+:func:`mne.datasets.misc.data_path`. Data exists at ``/ecog/sample_ecog.mat``.
+
+This dataset contains a sample Electrocorticography (ECoG) dataset. It includes
+a single grid of electrodes placed over the temporal lobe during an auditory
+listening task. This dataset is primarily used to demonstrate visualization
+functions in MNE and does not contain useful metadata for analysis.
+
+.. topic:: Examples
+
+    * :ref:`How to convert 3D electrode positions to a 2D image.
+      <sphx_glr_auto_examples_visualization_plot_3d_to_2d.py>`: Demonstrates
+      how to project a 3D electrode location onto a 2D image, a common procedure
+      in electrocorticography.
+
+
 References
 ==========
 
@@ -123,4 +203,8 @@ References
 
 .. [2] Schalk, G., McFarland, D.J., Hinterberger, T., Birbaumer, N., Wolpaw, J.R. (2004) BCI2000: A General-Purpose Brain-Computer Interface (BCI) System. IEEE TBME 51(6):1034-1043
 
-.. [3] Goldberger AL, Amaral LAN, Glass L, Hausdorff JM, Ivanov PCh, Mark RG, Mietus JE, Moody GB, Peng C-K, Stanley HE. (2000) PhysioBank, PhysioToolkit, and PhysioNet: Components of a New Research Resource for Complex Physiologic Signals. Circulation 101(23):e215-e220
\ No newline at end of file
+.. [3] Goldberger AL, Amaral LAN, Glass L, Hausdorff JM, Ivanov PCh, Mark RG, Mietus JE, Moody GB, Peng C-K, Stanley HE. (2000) PhysioBank, PhysioToolkit, and PhysioNet: Components of a New Research Resource for Complex Physiologic Signals. Circulation 101(23):e215-e220
+
+.. [4] Cichy, R. M., Pantazis, D., & Oliva, A. "Resolving human object recognition in space and time." Nature neuroscience (2014): 17(3), 455-462
+
+.. [5] Crosse, M. J., Di Liberto, G. M., Bednar, A., & Lalor, E. C. The Multivariate Temporal Response Function (mTRF) Toolbox: A MATLAB Toolbox for Relating Neural Signals to Continuous Stimuli. Frontiers in Human Neuroscience (2016): 10.
diff --git a/doc/manual/decoding.rst b/doc/manual/decoding.rst
index b1a0baf..db9eaa7 100644
--- a/doc/manual/decoding.rst
+++ b/doc/manual/decoding.rst
@@ -1,3 +1,4 @@
+.. include:: ../git_links.inc
 
 .. contents:: Contents
    :local:
@@ -5,8 +6,8 @@
 
 .. _decoding:
 
-Decoding
-########
+Decoding in MNE
+###############
 
 For maximal compatibility with the Scikit-learn package, we follow the same API. Each estimator implements a ``fit``, a ``transform`` and a ``fit_transform`` method. In some cases, they also implement an ``inverse_transform`` method. For more details, visit the Scikit-learn page.
 
@@ -17,15 +18,11 @@ Basic Estimators
 
 Scaler
 ^^^^^^
-This will standardize data across channels. Each channel type (mag, grad or eeg) is treated separately. During training time, the mean (`ch_mean_`) and standard deviation (`std_`) is computed in the ``fit`` method and stored as an attribute to the object. The ``transform`` method is called to transform the training set. To perform both the ``fit`` and ``transform`` operations in a single call, the ``fit_transform`` method may be used. During test time, the stored mean and standard deviat [...]
+The :class:`mne.decoding.Scaler` will standardize the data based on channel scales. In the simplest modes ``scalings=None`` or ``scalings=dict(...)``, each data channel type (e.g., mag, grad, eeg) is treated separately and scaled by a constant. This is the approach used by e.g., :func:`mne.compute_covariance` to standardize channel scales.
 
-.. note:: This is different from the ``StandarScaler`` estimator offered by Scikit-Learn. The ``StandardScaler`` standardizes each feature, whereas the ``Scaler`` object standardizes by channel type.
+If ``scalings='mean'`` or ``scalings='median'``, each channel is scaled using empirical measures. Each channel is scaled independently by the mean and standand deviation, or median and interquartile range, respectively, across all epochs and time points during :class:`mne.decoding.Scaler.fit` (during training). The :meth:`mne.decoding.Scaler.transform` method is called to transform data (training or test set) by scaling all time points and epochs on a channel-by-channel basis. To perform [...]
 
-EpochsVectorizer
-^^^^^^^^^^^^^^^^
-Scikit-learn API enforces the requirement that data arrays must be 2D. A common strategy for sensor-space decoding is to tile the sensors into a single vector. This can be achieved using the function :func:`mne.decoding.EpochsVectorizer.transform`. 
-
-To recover the original 3D data, an ``inverse_transform`` can be used. The ``epochs_vectorizer`` is particularly useful when constructing a pipeline object (used mainly for parameter search and cross validation). The ``epochs_vectorizer`` is the first estimator in the pipeline enabling estimators downstream to be more advanced estimators implemented in Scikit-learn. 
+.. note:: This is different from directly applying :class:`sklearn.preprocessing.StandardScaler` or :class:`sklearn.preprocessing.RobustScaler` offered by scikit-learn_. The ``StandardScaler`` and ``RobustScaler`` scale each *classification feature*, e.g. each time point for each channel, with mean and standard deviation computed across epochs, whereas ``Scaler`` scales each *channel* using mean and standard deviation computed across all of its time points and epochs.
 
 Vectorizer
 ^^^^^^^^^^
@@ -54,7 +51,7 @@ This is a technique to analyze multichannel data based on recordings from two cl
 .. math::       x_{CSP}(t) = W^{T}x(t)
    :label: csp
 
-where each column of :math:`W \in R^{C\times C}` is a spatial filter and each row of :math:`x_{CSP}` is a CSP component. The matrix :math:`W` is also called the de-mixing matrix in other contexts. Let :math:`\Sigma^{+} \in R^{C\times C}` and :math:`\Sigma^{-} \in R^{C\times C}` be the estimates of the covariance matrices of the two conditions. 
+where each column of :math:`W \in R^{C\times C}` is a spatial filter and each row of :math:`x_{CSP}` is a CSP component. The matrix :math:`W` is also called the de-mixing matrix in other contexts. Let :math:`\Sigma^{+} \in R^{C\times C}` and :math:`\Sigma^{-} \in R^{C\times C}` be the estimates of the covariance matrices of the two conditions.
 CSP analysis is given by the simultaneous diagonalization of the two covariance matrices
 
 .. math::       W^{T}\Sigma^{+}W = \lambda^{+}
@@ -78,6 +75,17 @@ Large entries in the diagonal matrix corresponds to a spatial filter which gives
 
     The winning entry of the Grasp-and-lift EEG competition in Kaggle uses the CSP implementation in MNE. It was featured as a `script of the week`_.
 
+
+Source Power Comodulation (SPoC)
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Source Power Comodulation (SPoC) [1]_ allows to identify the composition of orthogonal spatial filters that maximally correlate with a continuous target.
+
+SPoC can be seen as an extension of the CSP where the target is driven by a continuous variable rather than a discrete variable. Typical applications include extraction of motor patterns using EMG power or audio patterns using sound envelope.
+
+.. topic:: Examples
+
+    * :ref:`sphx_glr_auto_examples_decoding_plot_decoding_spoc_CMC.py`
+
 xDAWN
 ^^^^^
 Xdawn is a spatial filtering method designed to improve the signal to signal + noise ratio (SSNR) of the ERP responses. Xdawn was originally  designed for P300 evoked potential by enhancing the target response with respect to the non-target response. The implementation in MNE-Python is a generalization to any type of ERP.
@@ -107,8 +115,8 @@ The columns of the matrix :math:`(W^{-1})^T` are called spatial patterns. This i
 
 Plotting a pattern is as simple as doing::
 
-    >>> info = epochs.info
-    >>> model.plot_patterns(info)  # model is an instantiation of an estimator described in this section
+    >>> info = epochs.info  # doctest: +SKIP
+    >>> model.plot_patterns(info)  # model is an instantiation of an estimator described in this section  # doctest: +SKIP
 
 .. image:: ../../_images/sphx_glr_plot_linear_model_patterns_001.png
    :align: center
@@ -116,7 +124,7 @@ Plotting a pattern is as simple as doing::
 
 To plot the corresponding filter, you can do::
 
-    >>> model.plot_filters(info)
+    >>> model.plot_filters(info)  # doctest: +SKIP
 
 .. image:: ../../_images/sphx_glr_plot_linear_model_patterns_002.png
    :align: center
@@ -125,41 +133,55 @@ To plot the corresponding filter, you can do::
 Sensor-space decoding
 =====================
 
-Generalization Across Time
-^^^^^^^^^^^^^^^^^^^^^^^^^^
-Generalization Across Time (GAT) is a modern strategy to infer neuroscientific conclusions from decoding analysis of sensor-space data. An accuracy matrix is constructed where each point represents the performance of the model trained on one time window and tested on another.
-
-.. image:: ../../_images/sphx_glr_plot_decoding_time_generalization_001.png
-   :align: center
-   :width: 400px
-
-To use this functionality, simply do::
-
-    >>> gat = GeneralizationAcrossTime(predict_mode='cross-validation', n_jobs=1)
-    >>> gat.fit(epochs)
-    >>> gat.score(epochs)
-    >>> gat.plot(vmin=0.1, vmax=0.9, title="Generalization Across Time (faces vs. scrambled)")
+Decoding over time
+^^^^^^^^^^^^^^^^^^
 
-.. topic:: Examples:
+This strategy consists in fitting a multivariate predictive model on each
+time instant and evaluating its performance at the same instant on new
+epochs. The :class:`mne.decoding.SlidingEstimator` will take as input a
+pair of features :math:`X` and targets :math:`y`, where :math:`X` has
+more than 2 dimensions. For decoding over time the data :math:`X`
+is the epochs data of shape n_epochs x n_channels x n_times. As the
+last dimension of :math:`X` is the time an estimator will be fit
+on every time instant.
 
-    * :ref:`sphx_glr_auto_examples_decoding_plot_ems_filtering.py`
-    * :ref:`sphx_glr_auto_examples_decoding_plot_decoding_time_generalization_conditions.py`
+This approach is analogous to SlidingEstimator-based approaches in fMRI,
+where here we are interested in when one can discriminate experimental
+conditions and therefore figure out when the effect of interest happens.
 
-Time Decoding
-^^^^^^^^^^^^^
-In this strategy, a model trained on one time window is tested on the same time window. A moving time window will thus yield an accuracy curve similar to an ERP, but is considered more sensitive to effects in some situations. It is related to searchlight-based approaches in fMRI. This is also the diagonal of the GAT matrix.
+When working with linear models as estimators, this approach boils
+down to estimating a discriminative spatial filter for each time instant.
 
 .. image:: ../../_images/sphx_glr_plot_decoding_sensors_001.png
    :align: center
    :width: 400px
 
-To generate this plot, you need to initialize a GAT object and then use the method ``plot_diagonal``::
+To generate this plot see our tutorial :ref:`sphx_glr_auto_tutorials_plot_sensors_decoding.py`.
 
-    >>> gat.plot_diagonal()
+Temporal Generalization
+^^^^^^^^^^^^^^^^^^^^^^^
 
-.. topic:: Examples:
+Temporal Generalization is an extension of the decoding over time approach.
+It consists in evaluating whether the model estimated at a particular
+time instant accurately predicts any other time instant. It is analogous to
+transferring a trained model to a distinct learning problem, where the problems
+correspond to decoding the patterns of brain activity recorded at distinct time
+instants.
+
+The object to for Temporal Generalization is
+:class:`mne.decoding.GeneralizingEstimator`. It expects as input :math:`X` and
+:math:`y` (similarly to :class:`mne.decoding.SlidingEstimator`) but, when generate
+predictions from each model for all time instants. The class
+:class:`mne.decoding.GeneralizingEstimator` is generic and will treat the last
+dimension as the one to be used for generalization testing. For convenience,
+here, we refer to it different tasks. If :math:`X` corresponds to epochs data
+then the last dimension is time.
 
-    * :ref:`sphx_glr_auto_examples_decoding_plot_decoding_time_generalization.py`
+.. image:: ../../_images/sphx_glr_plot_decoding_time_generalization_001.png
+   :align: center
+   :width: 400px
+
+To generate this plot see our tutorial :ref:`sphx_glr_auto_tutorials_plot_sensors_decoding.py`.
 
 Source-space decoding
 =====================
@@ -171,3 +193,8 @@ Source space decoding is also possible, but because the number of features can b
     * :ref:`sphx_glr_auto_examples_decoding_plot_decoding_spatio_temporal_source.py`
 
 .. _script of the week: http://blog.kaggle.com/2015/08/12/july-2015-scripts-of-the-week/
+
+References
+==========
+
+.. [1] Dahne, S., Meinecke, F. C., Haufe, S., Hohne, J., Tangermann, M., Muller, K. R., & Nikulin, V. V. (2014). SPoC: a novel framework for relating the amplitude of neuronal oscillations to behaviorally relevant parameters. NeuroImage, 86, 111-122.
diff --git a/doc/manual/index.rst b/doc/manual/index.rst
index dafbe1e..7a697ec 100644
--- a/doc/manual/index.rst
+++ b/doc/manual/index.rst
@@ -1,3 +1,5 @@
+:orphan:
+
 .. _manual:
 
 User Manual
@@ -11,22 +13,20 @@ and class usage information.
 
 .. contents:: Contents
    :local:
-   :depth: 1
-
-.. raw:: html
+   :depth: 2
 
-   <h2>Cookbook</h2>
-   A quick run-through of the basic steps involved in M/EEG source analysis.
+Cookbook
+--------
+A quick run-through of the basic steps involved in M/EEG source analysis.
 
 .. toctree::
    :maxdepth: 2
 
    cookbook
 
-.. raw:: html
-
-   <h2>Reading your data</h2>
-   How to get your raw data loaded in MNE.
+Reading your data
+-----------------
+How to get your raw data loaded in MNE.
 
 .. toctree::
    :maxdepth: 1
@@ -34,10 +34,9 @@ and class usage information.
    io
    memory
 
-.. raw:: html
-
-   <h2>Preprocessing</h2>
-   Dealing with artifacts and noise sources in data.
+Preprocessing
+-------------
+Dealing with artifacts and noise sources in data.
 
 .. toctree::
    :maxdepth: 1
@@ -47,10 +46,9 @@ and class usage information.
    preprocessing/ssp
    channel_interpolation
 
-.. raw:: html
-
-   <h2>Source localization</h2>
-   Projecting raw data into source (brain) space.
+Source localization
+-------------------
+Projecting raw data into source (brain) space.
 
 .. toctree::
    :maxdepth: 1
@@ -59,67 +57,53 @@ and class usage information.
    source_localization/inverse
    source_localization/morph
 
-.. raw:: html
-
-   <h2>Time-frequency analysis</h2>
-   Decomposing time-domain signals into time-frequency representations.
+Time-frequency analysis
+-----------------------
+Decomposing time-domain signals into time-frequency representations.
 
 .. toctree::
    :maxdepth: 2
 
    time_frequency
 
-.. raw:: html
-
-   <h2>Statistics</h2>
-   Using parametric and non-parametric tests with M/EEG data.
+Statistics
+----------
+Using parametric and non-parametric tests with M/EEG data.
 
 .. toctree::
    :maxdepth: 2
 
    statistics
 
-.. raw:: html
-
-   <h2>Decoding</h2>
+Decoding
+--------
+How to do decoding in MNE-Python.
 
 .. toctree::
    :maxdepth: 2
 
    decoding
 
-.. raw:: html
-
-   <h2>Datasets</h2>
-   How to use dataset fetchers for public data
+Datasets
+--------
+How to use dataset fetchers for public data
 
 .. toctree::
    :maxdepth: 2
 
    datasets_index
 
-.. raw:: html
-
-   <h2>Migrating</h2>
+Migrating
+---------
+Migrating from other software packages.
 
 .. toctree::
    :maxdepth: 1
 
    migrating
 
-.. raw:: html
-
-   <h2>Pitfalls</h2>
-
-.. toctree::
-   :maxdepth: 2
-
-   pitfalls
-
-.. raw:: html
-
-   <h2>C Tools</h2>
-
+C Tools
+-------
 Additional information about various MNE-C tools.
 
 .. toctree::
@@ -129,20 +113,17 @@ Additional information about various MNE-C tools.
    gui/analyze
    gui/browse
 
-.. raw:: html
-
-   <h2>MATLAB Tools</h2>
-   Information about the MATLAB toolbox.
+MNE-MATLAB
+----------
+Information about the MATLAB toolbox.
 
 .. toctree::
    :maxdepth: 2
 
    matlab
 
-.. raw:: html
-
-   <h2>Appendices</h2>
-
+Appendices
+----------
 More details about our implementations and software.
 
 .. toctree::
diff --git a/doc/manual/io.rst b/doc/manual/io.rst
index 1352a0a..ac62314 100644
--- a/doc/manual/io.rst
+++ b/doc/manual/io.rst
@@ -5,6 +5,13 @@
 
 .. _ch_convert:
 
+Importing data into MNE
+~~~~~~~~~~~~~~~~~~~~~~~
+
+This guide covers how to import data into MNE python. It includes instructions
+for importing from common recording equipment in MEG and EEG, as well as how
+to import raw data from numpy arrays.
+
 Importing MEG data
 ##################
 
@@ -23,7 +30,9 @@ EEG                   Brainvision                .vhdr      :func:`mne.io.read_r
 EEG                   Neuroscan CNT              .cnt       :func:`mne.io.read_raw_cnt`
 EEG                   European data format       .edf       :func:`mne.io.read_raw_edf`
 EEG                   Biosemi data format        .bdf       :func:`mne.io.read_raw_edf`
+EEG                   General data format        .gdf       :func:`mne.io.read_raw_edf`
 EEG                   EGI simple binary          .egi       :func:`mne.io.read_raw_egi`
+EEG                   EGI MFF format             .mff       :func:`mne.io.read_raw_egi`
 EEG                   EEGLAB                     .set       :func:`mne.io.read_raw_eeglab` and :func:`mne.read_epochs_eeglab`
 Electrode locations   elc, txt, csd, sfp, htps   Misc       :func:`mne.channels.read_montage`
 Electrode locations   EEGLAB loc, locs, eloc     Misc       :func:`mne.channels.read_montage`
@@ -35,6 +44,26 @@ Electrode locations   EEGLAB loc, locs, eloc     Misc       :func:`mne.channels.
     supported data formats can be read in MNE-Python directly without first
     saving it to fif.
 
+.. note::
+
+    MNE-Python performs all computation in memory using the double-precision
+    64-bit floating point format. This means that the data is typecasted into
+    `float64` format as soon as it is read into memory. The reason for this is
+    that operations such as filtering, preprocessing etc. are more accurate when
+    using the double-precision format. However, for backward compatibility, it
+    writes the `fif` files in a 32-bit format by default. This is advantageous
+    when saving data to disk as it consumes less space.
+
+    However, if the users save intermediate results to disk, they should be aware
+    that this may lead to loss in precision. The reason is that writing to disk is
+    32-bit by default and then typecasting to 64-bit does not recover the lost
+    precision. In case you would like to retain the 64-bit accuracy, there are two
+    possibilities:
+
+    * Chain the operations in memory and not save intermediate results
+    * Save intermediate results but change the ``dtype`` used for saving. However,
+      this may render the files unreadable in other software packages
+
 Elekta NeuroMag (.fif)
 ======================
 
@@ -45,14 +74,6 @@ Neuromag Raw FIF files can be loaded using :func:`mne.io.read_raw_fif`.
     with MaxFilter, they may need to be loaded with
     ``mne.io.read_raw_fif(..., allow_maxshield=True)``.
 
-.. note::
-    This file format also supports EEG data. In 0.13, an average reference
-    will be added by default on reading EEG data. To change this behavior,
-    use the argument ``add_eeg_ref=False``, which will become the default
-    in 0.14. The argument will be removed in 0.15 in favor of
-    :func:`mne.set_eeg_reference` and :meth:`mne.io.Raw.set_eeg_reference`.
-
-
 Importing 4-D Neuroimaging / BTI data
 =====================================
 
@@ -270,26 +291,37 @@ European data format (.edf)
 
 EDF and EDF+ files can be read in using :func:`mne.io.read_raw_edf`.
 
-http://www.edfplus.info/specs/edf.html
+`EDF (European Data Format) <http://www.edfplus.info/specs/edf.html>`_ and
+`EDF+ <http://www.edfplus.info/specs/edfplus.html>`_ are 16-bit formats.
 
-EDF (European Data Format) and EDF+ are 16-bit formats
-http://www.edfplus.info/specs/edfplus.html
-
-The EDF+ files may contain an annotation channel which can
-be used to store trigger information. The Time-stamped Annotation
-Lists (TALs) on the annotation  data can be converted to a trigger
-channel (STI 014) using an annotation map file which associates
-an annotation label with a number on the trigger channel.
+The EDF+ files may contain an annotation channel which can be used to store
+trigger information. The Time-stamped Annotation Lists (TALs) on the
+annotation  data can be converted to a trigger channel (STI 014) using an
+annotation map file which associates an annotation label with a number on
+the trigger channel.
 
 Biosemi data format (.bdf)
 ==========================
 
-The BDF format (http://www.biosemi.com/faq/file_format.htm) is a 24-bit variant
-of the EDF format used by the EEG systems manufactured by a company called
-BioSemi. It can also be read in using :func:`mne.io.read_raw_edf`.
+The `BDF format <http://www.biosemi.com/faq/file_format.htm>`_ is a 24-bit
+variant of the EDF format used by the EEG systems manufactured by a company
+called BioSemi. It can also be read in using :func:`mne.io.read_raw_edf`.
 
 .. warning:: The data samples in a BDF file are represented in a 3-byte (24-bit) format. Since 3-byte raw data buffers are not presently supported in the fif format these data will be changed to 4-byte integers in the conversion.
 
+General data format (.gdf)
+==========================
+
+GDF files can be read in using :func:`mne.io.read_raw_edf`.
+
+`GDF (General Data Format) <https://arxiv.org/abs/cs/0608052>`_ is a flexible
+format for biomedical signals, that overcomes some of the limitations of the
+EDF format. The original specification (GDF v1) includes a binary header,
+and uses an event table. An updated specification (GDF v2) was released in
+2011 and adds fields for additional subject-specific information (gender,
+age, etc.) and allows storing several physical units and other properties.
+Both specifications are supported in MNE.
+
 Neuroscan CNT data format (.cnt)
 ================================
 
diff --git a/doc/manual/matlab.rst b/doc/manual/matlab.rst
index 9e2e81a..6668cb0 100644
--- a/doc/manual/matlab.rst
+++ b/doc/manual/matlab.rst
@@ -1,9 +1,9 @@
 
 .. _ch_matlab:
 
-==============
-Matlab toolbox
-==============
+==================
+MNE-MATLAB toolbox
+==================
 
 .. contents:: Contents
    :local:
diff --git a/doc/manual/memory.rst b/doc/manual/memory.rst
index 16f1c03..875798e 100644
--- a/doc/manual/memory.rst
+++ b/doc/manual/memory.rst
@@ -21,7 +21,7 @@ MNE-Python can read data on-demand using the ``preload`` option provided in :ref
     raw_fname = data_path + '/MEG/sample/sample_audvis_filt-0-40_raw.fif'
     raw = io.read_raw_fif(raw_fname, preload=False)
 
-.. note:: Filtering does not work with ``preload=False``.
+.. note:: Filtering, resampling and dropping or selecting channels does not work with ``preload=False``.
 
 Epochs
 ^^^^^^
@@ -44,3 +44,11 @@ To explicitly reject artifacts with ``preload=False``, use the function :func:`m
 Loading data explicitly
 =======================
 To load the data if ``preload=False`` was initially selected, use the functions :func:`mne.io.Raw.load_data` and :func:`mne.Epochs.load_data`.
+
+Simplest way to access data
+===========================
+If you just want your raw data as a numpy array to work with it in a different framework you can use slicing syntax::
+
+    first_channel_data, times = raw[0, :]
+    channel_3_5, times_3_5 = raw[3:5, :]
+
diff --git a/doc/manual/migrating.rst b/doc/manual/migrating.rst
index 1e732bc..47d9d79 100644
--- a/doc/manual/migrating.rst
+++ b/doc/manual/migrating.rst
@@ -12,35 +12,35 @@ that the following are already defined or known: the file name ``fname``, time i
 and the conditions ``cond1`` and ``cond2``. The variables ``l_freq`` and ``h_freq`` are the frequencies (in Hz) below which
 and above which to filter out data.
 
-+-------------------+--------------------------------------------------------------+-----------------------------------------------------------------------------+
-| Processing step   | EEGLAB function                                              | MNE                                                                         |
-+===================+==============================================================+=============================================================================+
-| Get started       | | addpath(...);                                              | | import mne                                                                |
-|                   | | eeglab;                                                    | | from mne import io,     :class:`Epochs <mne.Epochs>`                      |
-|                   |                                                              | | from mne.preprocessing import     :class:`ICA <mne.preprocessing.ICA>`    |
-+-------------------+--------------------------------------------------------------+-----------------------------------------------------------------------------+
-| Import data       | EEG = pop_fileio(fname);                                     | | :func:`raw = io.read_raw_fif(fname) <mne.io.Raw>`                         |
-|                   |                                                              | | :func:`raw = io.read_raw_edf(fname) <mne.io.read_raw_edf>`                |
-|                   |                                                              | | :func:`raw = io.read_raw_eeglab(fname) <mne.io.read_raw_eeglab>`          |
-+-------------------+--------------------------------------------------------------+-----------------------------------------------------------------------------+
-| Filter data       | EEG = pop_eegfiltnew(EEG, l_freq, h_freq);                   | :func:`raw.filter(l_freq, h_freq) <mne.io.Raw.filter>`                      |
-+-------------------+--------------------------------------------------------------+-----------------------------------------------------------------------------+
-| Run ICA           | EEG = pop_runica(EEG);                                       | :func:`ica.fit(raw) <mne.preprocessing.ICA.fit>`                            |
-+-------------------+--------------------------------------------------------------+-----------------------------------------------------------------------------+
-| Epoch data        | | event_id = {'cond1', 'cond2'};                             | | :func:`events = mne.find_events(raw) <mne.find_events>`                   |
-|                   | | Epochs = pop_epochs(EEG, event_id, [tmin, tmax]);          | | :py:class:`event_id = dict(cond1=32, cond2=64) <dict>`                    |
-|                   | |                                                            | | :class:`epochs = Epochs(raw, events, event_id, tmin, tmax) <mne.Epochs>`  |
-+-------------------+--------------------------------------------------------------+-----------------------------------------------------------------------------+
-| Selecting epochs  | Epochs = pop_epochs(EEG_epochs, {cond2});                    | :class:`epochs[cond2] <mne.Epochs>`                                         |
-+-------------------+--------------------------------------------------------------+-----------------------------------------------------------------------------+
-| ERP butterfly plot| pop_timtopo(EEG_epochs, ...);                                | :func:`evoked.plot() <mne.Evoked.plot>`                                     |
-+-------------------+--------------------------------------------------------------+-----------------------------------------------------------------------------+
-| Contrast ERPs     | pop_compareerps(EEG_epochs1, EEG_epochs2);                   | :func:`(evoked1 - evoked2).plot() <mne.Evoked.__sub__>`                     |
-+-------------------+--------------------------------------------------------------+-----------------------------------------------------------------------------+
-| Save data         | EEG = pop_saveset(EEG, fname);                               | | :func:`raw.save(fname) <mne.io.Raw.save>`                                 |
-|                   |                                                              | | :func:`epochs.save(fname) <mne.Epochs.save>`                              |
-|                   |                                                              | | :func:`evoked.save(fname) <mne.Evoked.save>`                              |
-+-------------------+--------------------------------------------------------------+-----------------------------------------------------------------------------+
++-------------------+--------------------------------------------------------------+----------------------------------------------------------------------------------------------+
+| Processing step   | EEGLAB function                                              | MNE                                                                                          |
++===================+==============================================================+==============================================================================================+
+| Get started       | | addpath(...);                                              | | import mne                                                                                 |
+|                   | | eeglab;                                                    | | from mne import io,     :class:`Epochs <mne.Epochs>`                                       |
+|                   |                                                              | | from mne.preprocessing import     :class:`ICA <mne.preprocessing.ICA>`                     |
++-------------------+--------------------------------------------------------------+----------------------------------------------------------------------------------------------+
+| Import data       | EEG = pop_fileio(fname);                                     | | :func:`raw = io.read_raw_fif(fname) <mne.io.Raw>`                                          |
+|                   |                                                              | | :func:`raw = io.read_raw_edf(fname) <mne.io.read_raw_edf>`                                 |
+|                   |                                                              | | :func:`raw = io.read_raw_eeglab(fname) <mne.io.read_raw_eeglab>`                           |
++-------------------+--------------------------------------------------------------+----------------------------------------------------------------------------------------------+
+| Filter data       | EEG = pop_eegfiltnew(EEG, l_freq, h_freq);                   | :func:`raw.filter(l_freq, h_freq) <mne.io.Raw.filter>`                                       |
++-------------------+--------------------------------------------------------------+----------------------------------------------------------------------------------------------+
+| Run ICA           | EEG = pop_runica(EEG);                                       | :func:`ica.fit(raw) <mne.preprocessing.ICA.fit>`                                             |
++-------------------+--------------------------------------------------------------+----------------------------------------------------------------------------------------------+
+| Epoch data        | | event_id = {'cond1', 'cond2'};                             | | :func:`events = mne.find_events(raw) <mne.find_events>`                                    |
+|                   | | Epochs = pop_epochs(EEG, event_id, [tmin, tmax]);          | | :py:class:`event_id = dict(cond1=32, cond2=64) <dict>`                                     |
+|                   | |                                                            | | :class:`epochs = Epochs(raw, events, event_id, tmin, tmax) <mne.Epochs>`                   |
++-------------------+--------------------------------------------------------------+----------------------------------------------------------------------------------------------+
+| Selecting epochs  | Epochs = pop_epochs(EEG_epochs, {cond2});                    | :class:`epochs[cond2] <mne.Epochs>`                                                          |
++-------------------+--------------------------------------------------------------+----------------------------------------------------------------------------------------------+
+| ERP butterfly plot| pop_timtopo(EEG_epochs, ...);                                | :func:`evoked.plot() <mne.Evoked.plot>`                                                      |
++-------------------+--------------------------------------------------------------+----------------------------------------------------------------------------------------------+
+| Contrast ERPs     | pop_compareerps(EEG_epochs1, EEG_epochs2);                   | :func:`mne.combine_evoked([evoked1, -evoked2], weights='equal').plot() <mne.combine_evoked>` |
++-------------------+--------------------------------------------------------------+----------------------------------------------------------------------------------------------+
+| Save data         | EEG = pop_saveset(EEG, fname);                               | | :func:`raw.save(fname) <mne.io.Raw.save>`                                                  |
+|                   |                                                              | | :func:`epochs.save(fname) <mne.Epochs.save>`                                               |
+|                   |                                                              | | :func:`evoked.save(fname) <mne.Evoked.save>`                                               |
++-------------------+--------------------------------------------------------------+----------------------------------------------------------------------------------------------+
 
 Note that MNE has functions to read a variety of file formats, not just :func:`mne.io.Raw`. The interested user is directed to the :ref:`IO documentation <ch_convert>`.
 
diff --git a/doc/manual/pitfalls.rst b/doc/manual/pitfalls.rst
deleted file mode 100644
index c2c400a..0000000
--- a/doc/manual/pitfalls.rst
+++ /dev/null
@@ -1,30 +0,0 @@
-
-.. contents:: Contents
-   :local:
-   :depth: 2
-
-.. _pitfalls:
-
-Pitfalls
-########
-
-Float64 vs float32
-==================
-
-MNE-Python performs all computation in memory using the double-precision
-64-bit floating point format. This means that the data is typecasted into
-`float64` format as soon as it is read into memory. The reason for this is
-that operations such as filtering, preprocessing etc. are more accurate when
-using the double-precision format. However, for backward compatibility, it
-writes the `fif` files in a 32-bit format by default. This is advantageous
-when saving data to disk as it consumes less space.
-
-However, if the users save intermediate results to disk, they should be aware
-that this may lead to loss in precision. The reason is that writing to disk is
-32-bit by default and then typecasting to 64-bit does not recover the lost
-precision. In case you would like to retain the 64-bit accuracy, there are two
-possibilities:
-
-* Chain the operations in memory and not save intermediate results
-* Save intermediate results but change the ``dtype`` used for saving. However,
-  this may render the files unreadable in other software packages
diff --git a/doc/manual/preprocessing/bads.rst b/doc/manual/preprocessing/bads.rst
index f1fd803..6bc1c56 100644
--- a/doc/manual/preprocessing/bads.rst
+++ b/doc/manual/preprocessing/bads.rst
@@ -1,3 +1,5 @@
+:orphan:
+
 ============
 Bad channels
 ============
diff --git a/doc/manual/preprocessing/filter.rst b/doc/manual/preprocessing/filter.rst
index c51195d..5eef413 100644
--- a/doc/manual/preprocessing/filter.rst
+++ b/doc/manual/preprocessing/filter.rst
@@ -1,3 +1,5 @@
+:orphan:
+
 =========
 Filtering
 =========
diff --git a/doc/manual/preprocessing/overview.rst b/doc/manual/preprocessing/overview.rst
index 7a3c5c0..75a4fec 100644
--- a/doc/manual/preprocessing/overview.rst
+++ b/doc/manual/preprocessing/overview.rst
@@ -1,5 +1,5 @@
-=========
-Filtering
-=========
-
+:orphan:
 
+========
+Overview
+========
diff --git a/doc/manual/preprocessing/ssp.rst b/doc/manual/preprocessing/ssp.rst
index a5011c2..b0f2251 100644
--- a/doc/manual/preprocessing/ssp.rst
+++ b/doc/manual/preprocessing/ssp.rst
@@ -1,16 +1,16 @@
 .. _ssp:
 
-Projections
-###########
+Signal-Space Projection (SSP)
+#############################
 
 .. contents:: Contents
    :local:
    :depth: 3
 
-The Signal-Space Projection (SSP) method
-========================================
+The SSP method
+==============
 
-The Signal-Space Projection (SSP) is one approach to rejection 
+The Signal-Space Projection (SSP) is one approach to rejection
 of external disturbances in software. Unlike many other noise-cancellation
 approaches, SSP does not require additional reference sensors to record the disturbance
 fields. Instead, SSP relies on the fact that the magnetic field
@@ -141,7 +141,7 @@ Once a projector is applied on the data, it is said to be `active`.
 The proj attribute
 ------------------
 
-It is available in all the basic data containers: ``Raw``, ``Epochs`` and ``Evoked``. It is ``True`` if at least one projector is present and all of them are `active`. 
+It is available in all the basic data containers: ``Raw``, ``Epochs`` and ``Evoked``. It is ``True`` if at least one projector is present and all of them are `active`.
 
 Computing projectors
 --------------------
@@ -153,7 +153,7 @@ The general assumption these functions make is that the data passed contains
 raw, epochs or averages of the artifact. Typically this involves continues raw
 data of empty room recordings or averaged ECG or EOG artifacts.
 
-A second set of highlevel convenience functions is provided to compute projection vector for typical usecases. This includes :func:`mne.preprocessing.compute_proj_ecg` and :func:`mne.preprocessing.compute_proj_eog` for computing the ECG and EOG related artifact components, respectively. For computing the eeg reference signal, the function :func:`mne.preprocessing.ssp.make_eeg_average_ref_proj` can be used. The underlying implementation can be found in :mod:`mne.preprocessing.ssp`.
+A second set of highlevel convenience functions is provided to compute projection vector for typical usecases. This includes :func:`mne.preprocessing.compute_proj_ecg` and :func:`mne.preprocessing.compute_proj_eog` for computing the ECG and EOG related artifact components, respectively. For computing the EEG reference signal, the function :func:`mne.set_eeg_reference` can be used.
 
 .. warning:: It is best to compute projectors only on channels that will be
              used (e.g., excluding bad channels). This ensures that
@@ -167,12 +167,12 @@ Adding/removing projectors
 
 To explicitly add a ``proj``, use ``add_proj``. For example::
 
-    >>> projs = mne.read_proj('proj_a.fif')
-    >>> evoked.add_proj(projs)
+    >>> projs = mne.read_proj('proj_a.fif')  # doctest: +SKIP
+    >>> evoked.add_proj(projs)  # doctest: +SKIP
 
 If projectors are already present in the raw `fif` file, it will be added to the ``info`` dictionary automatically. To remove existing projectors, you can do::
 
-	>>> evoked.add_proj([], remove_existing=True)
+	>>> evoked.add_proj([], remove_existing=True)  # doctest: +SKIP
 
 Applying projectors
 -------------------
@@ -181,7 +181,7 @@ Projectors can be applied at any stage of the pipeline. When the ``raw`` data is
 
 To apply explicitly projs at any stage of the pipeline, use ``apply_proj``. For example::
 
-	>>> evoked.apply_proj()
+	>>> evoked.apply_proj()  # doctest: +SKIP
 
 The projectors might not be applied if data are not :ref:`preloaded <memory>`. In this case, it's the ``_projector`` attribute that indicates if a projector will be applied when the data is loaded in memory. If the data is already in memory, then the projectors applied to it are the ones marked as `active`. As soon as you've applied the projectors, it will stay active in the remaining pipeline.
 
@@ -191,11 +191,9 @@ The projectors might not be applied if data are not :ref:`preloaded <memory>`. I
 Delayed projectors
 ------------------
 
-The suggested pipeline is ``proj=True`` in epochs (it's computationally cheaper than for raw). When you use delayed SSP in ``Epochs``, projectors are applied when you call :func:`mne.Epochs.get_data` method. They are not applied to the ``evoked`` data unless you call ``apply_proj()``. The reason is that you want to reject epochs with projectors although it's not stored in the projector mode. 
+The suggested pipeline is ``proj=True`` in epochs (it's computationally cheaper than for raw). When you use delayed SSP in ``Epochs``, projectors are applied when you call :func:`mne.Epochs.get_data` method. They are not applied to the ``evoked`` data unless you call ``apply_proj()``. The reason is that you want to reject epochs with projectors although it's not stored in the projector mode.
 
 .. topic:: Examples:
 
-	* :ref:`sphx_glr_auto_examples_visualization_plot_evoked_delayed_ssp.py`: Interactive SSP
-	* :ref:`sphx_glr_auto_examples_visualization_plot_evoked_topomap_delayed_ssp.py`: Interactive SSP
-	* :ref:`sphx_glr_auto_examples_visualization_plot_ssp_projs_topomaps.py`: SSP sensitivities in sensor space
-	* :ref:`sphx_glr_auto_examples_visualization_plot_ssp_projs_sensitivity_map.py`: SSP sensitivities in source space
+    * :ref:`sphx_glr_auto_tutorials_plot_artifacts_correction_ssp.py`: SSP sensitivities in sensor space
+    * :ref:`sphx_glr_auto_examples_forward_plot_forward_sensitivity_maps.py`: SSP sensitivities in source space
diff --git a/doc/manual/sample_dataset.rst b/doc/manual/sample_dataset.rst
index 871ceb2..e4fdfa7 100644
--- a/doc/manual/sample_dataset.rst
+++ b/doc/manual/sample_dataset.rst
@@ -1,4 +1,4 @@
-
+:orphan:
 
 .. _ch_sample_data:
 
@@ -65,7 +65,7 @@ Setting up
 
 The sample dataset can be downloaded automatically by doing::
 
-    >>> mne.datasets.sample.data_path(verbose=True)
+    >>> mne.datasets.sample.data_path(verbose=True)  # doctest: +SKIP
 
 Contents of the data set
 ########################
diff --git a/doc/manual/statistics.rst b/doc/manual/statistics.rst
index 3692d69..a2cdb35 100644
--- a/doc/manual/statistics.rst
+++ b/doc/manual/statistics.rst
@@ -31,13 +31,13 @@ Models
 - :func:`mne.stats.ttest_1samp_no_p` is an optimized version of the one sample
   t-test provided by scipy. It is used by default for contrast enhancement in
   :func:`mne.stats.permutation_cluster_1samp_test` and
-  :func:`mne.stats.spatio_temporal_permutation_cluster_1samp_test`.
+  :func:`mne.stats.spatio_temporal_cluster_1samp_test`.
 
-- :func:`mne.stats.parametric.f_oneway` is an optimized version of the F-test
+- :func:`mne.stats.f_oneway` is an optimized version of the F-test
   for independent samples provided by scipy.
   It can be used in the context of non-paramteric permutation tests to
   compute various F-contrasts. It is used by default for contrast enhancement in
-  :func:`mne.stats.spatio_temporal_permutation_cluster_test` and
+  :func:`mne.stats.spatio_temporal_cluster_test` and
   :func:`mne.stats.permutation_cluster_test`.
 
 
@@ -85,9 +85,9 @@ and clustering connectivity prior, i.e., assumptions about the grouping and neig
 
 - :func:`mne.stats.permutation_cluster_test` supports F-contrasts with spatial prior.
 
-- :func:`mne.stats.spatio_temporal_permutation_cluster_1samp_test` supports paired contrasts without spatial prior.
+- :func:`mne.stats.spatio_temporal_cluster_1samp_test` supports paired contrasts without spatial prior.
 
-- :func:`mne.stats.spatio_temporal_permutation_cluster_test` supports F-contrasts without spatial prior.
+- :func:`mne.stats.spatio_temporal_cluster_test` supports F-contrasts without spatial prior.
 
 Using the TFCE option observation- instead of cluster-wise hypothesis testing can be realized.
 
diff --git a/doc/manual/visualization.rst b/doc/manual/visualization.rst
index cfefd39..27947ab 100644
--- a/doc/manual/visualization.rst
+++ b/doc/manual/visualization.rst
@@ -1,3 +1,5 @@
+:orphan:
+
 =============
 Visualization
 =============
diff --git a/doc/martinos.rst b/doc/martinos.rst
index 71d06de..a698ec5 100644
--- a/doc/martinos.rst
+++ b/doc/martinos.rst
@@ -1,3 +1,5 @@
+:orphan:
+
 .. _inside_martinos:
 
 Martinos Center setup
@@ -7,19 +9,19 @@ For people within the MGH/MIT/HMS Martinos Center, MNE is available on the netwo
 
 In a terminal do:
 
-.. code-block:: bash
+.. code-block:: console
 
     $ setenv PATH /usr/pubsw/packages/python/anaconda/bin:${PATH}
 
 If you use Bash replace the previous instruction with:
 
-.. code-block:: bash
+.. code-block:: console
 
     $ export PATH=/usr/pubsw/packages/python/anaconda/bin:${PATH}
 
 Then start the python interpreter with:
 
-.. code-block:: bash
+.. code-block:: console
 
     $ ipython
 
@@ -27,7 +29,7 @@ Then type::
 
     >>> import mne
 
-If you get a new prompt with no error messages, you should be good to go. 
+If you get a new prompt with no error messages, you should be good to go.
 
 We encourage all Martinos center Python users to subscribe to the
 `Martinos Python mailing list`_.
diff --git a/doc/mne_cpp.rst b/doc/mne_cpp.rst
index 8662c42..ad2446b 100644
--- a/doc/mne_cpp.rst
+++ b/doc/mne_cpp.rst
@@ -1,26 +1,35 @@
+:orphan:
+
 .. _mne_cpp:
 
 ======================
 MNE with CPP
 ======================
 
-MNE-CPP is a cross-platform application and algorithm C++ framework for
-MEG and EEG data analysis and acquisition. It provides a modular
-structure with many sub-libraries. These libraries can be easily
-integrated into your project to, e.g. provide full I/O support for the
-FIF-file format or files generated by the MNE suite. MNE-CPP comes with
-its own acquisition software, MNE-X, which can be used to acquire and
-process data from Elekta Neuromag MEG VectorView, BabyMEG,
-TMSI EEG and eegosports.
+MNE-CPP is a cross-platform application and algorithm C++ framework 
+for MEG/EEG data acquisition, analysis and visualization. It 
+provides a modular structure with sub-libraries. The MNE-CPP API 
+can be integrated into other stand-alone projects to, e.g. provide 
+full I/O support for the FIF-file format or files generated by the 
+MNE and Freesurfer suite. MNE-CPP’s 3D visualization library is based 
+on the Qt3D module, which provides tools for online data displaying 
+with OpenGL. 
+
+MNE-CPP ships with built-in stand-alone applications, some of which 
+are closely connected to well-known MNE-C applications. MNE Browse can 
+be used to inspect and process pre-recorded data. Among others, dipole 
+fitting and the computation of forward solutions have been ported from 
+the MNE-C library, including the same command line interfaces. With MNE 
+Scan the MNE-CPP project provides an application for acquiring and 
+processing MEG/EEG data in real-time. Supported MEG devices include 
+the Elekta Neuromag VectorView and BabyMEG system. Several EEG amplifiers 
+(TMSI Refa, BrainAmp, ANT eegosports, gUSBamp) are supported as well. 
 
 For further information please visit the MNE-CPP project pages:
 
-  * `Project Page <http://www.tu-ilmenau.de/bmti/forschung/datenanalyse-modellierung-und-inverse-verfahren/mne-cpp/>`_
-  * `Class Reference <http://www2.tu-ilmenau.de/mne-cpp/space/doc/>`_
+  * `Project Page <http://www.mne-cpp.org/>`_
   * `GitHub Sources <https://github.com/mne-tools/mne-cpp/>`_
 
 .. raw:: html
 
-    <div>
-        <script type="text/javascript" src="http://www.openhub.net/p/687714/widgets/project_basic_stats.js"></script>
-    </div>
+    <div><script type="text/javascript" src="http://www.openhub.net/p/687714/widgets/project_basic_stats.js"></script></div>
diff --git a/doc/python_reference.rst b/doc/python_reference.rst
index 2b7292f..d977f6f 100644
--- a/doc/python_reference.rst
+++ b/doc/python_reference.rst
@@ -1,12 +1,15 @@
+:orphan:
+
 .. _api_reference:
 
 ====================
 Python API Reference
 ====================
 
-This is the classes and functions reference of MNE-Python. Functions are
-grouped thematically by analysis stage. Functions and classes that are not
-below a module heading are found in the :py:mod:`mne` namespace.
+This is the reference for classes (``CamelCase`` names) and functions
+(``underscore_case`` names) of MNE-Python, grouped thematically by analysis
+stage. Functions and classes that are not
+below a module heading are found in the ``mne`` namespace.
 
 MNE-Python also provides multiple command-line scripts that can be called
 directly from a terminal, see :ref:`python_commands`.
@@ -16,8 +19,14 @@ directly from a terminal, see :ref:`python_commands`.
    :depth: 2
 
 
-Classes
-=======
+:py:mod:`mne`:
+
+.. automodule:: mne
+   :no-members:
+   :no-inherited-members:
+
+Most-used classes
+=================
 
 .. currentmodule:: mne
 
@@ -26,71 +35,9 @@ Classes
    :template: class.rst
 
    io.Raw
-   io.RawFIF
-   io.RawArray
-   Annotations
-   AcqParserFIF
    Epochs
    Evoked
-   SourceSpaces
-   Forward
-   SourceEstimate
-   VolSourceEstimate
-   MixedSourceEstimate
-   Covariance
-   Dipole
-   DipoleFixed
-   Label
-   BiHemiLabel
-   Transform
-   Report
    Info
-   Projection
-   preprocessing.ICA
-   preprocessing.Xdawn
-   decoding.CSP
-   decoding.EpochsVectorizer
-   decoding.FilterEstimator
-   decoding.GeneralizationAcrossTime
-   decoding.PSDEstimator
-   decoding.Scaler
-   decoding.TimeDecoding
-   realtime.RtEpochs
-   realtime.RtClient
-   realtime.MockRtClient
-   realtime.FieldTripClient
-   realtime.StimServer
-   realtime.StimClient
-
-Logging and Configuration
-=========================
-
-.. currentmodule:: mne
-
-.. autosummary::
-   :toctree: generated/
-   :template: function.rst
-
-   get_config_path
-   get_config
-   set_log_level
-   set_log_file
-   set_config
-   sys_info
-
-:py:mod:`mne.cuda`:
-
-.. automodule:: mne.cuda
- :no-members:
- :no-inherited-members:
-
-.. currentmodule:: mne.cuda
-
-.. autosummary::
-   :toctree: generated/
-   :template: function.rst
-
-   init_cuda
 
 Reading raw data
 ================
@@ -99,20 +46,18 @@ Reading raw data
 
 .. currentmodule:: mne.io
 
-Classes:
-
-.. autosummary::
-   :toctree: generated/
-   :template: class.rst
-
-   Raw
-
-Functions:
+.. automodule:: mne.io
+   :no-members:
+   :no-inherited-members:
 
 .. autosummary::
   :toctree: generated/
   :template: function.rst
 
+  anonymize_info
+  find_edf_events
+  read_events_eeglab
+  read_raw_artemis123
   read_raw_bti
   read_raw_cnt
   read_raw_ctf
@@ -124,10 +69,22 @@ Functions:
   read_raw_egi
   read_raw_fif
 
-.. currentmodule:: mne.io.kit
+Base class:
+
+.. autosummary::
+   :toctree: generated
+   :template: class.rst
+
+   BaseRaw
 
 :py:mod:`mne.io.kit`:
 
+.. currentmodule:: mne.io.kit
+
+.. automodule:: mne.io.kit
+   :no-members:
+   :no-inherited-members:
+
 .. autosummary::
   :toctree: generated/
   :template: function.rst
@@ -139,8 +96,6 @@ File I/O
 
 .. currentmodule:: mne
 
-Functions:
-
 .. autosummary::
    :toctree: generated
    :template: function.rst
@@ -149,6 +104,7 @@ Functions:
    get_head_surf
    get_meg_helmet_surf
    get_volume_labels_from_aseg
+   get_volume_labels_from_src
    parse_config
    read_labels_from_annot
    read_bem_solution
@@ -185,39 +141,31 @@ Functions:
    write_surface
    write_trans
    io.read_info
+   io.show_fiff
 
-Creating data objects from arrays
-=================================
-
-Classes:
-
-.. currentmodule:: mne
-
-:py:mod:`mne`:
+Base class:
 
 .. autosummary::
-   :toctree: generated/
+   :toctree: generated
    :template: class.rst
 
-   EvokedArray
-   EpochsArray
+   BaseEpochs
 
-.. currentmodule:: mne.io
+Creating data objects from arrays
+=================================
 
-:py:mod:`mne.io`:
+.. currentmodule:: mne
 
 .. autosummary::
    :toctree: generated/
    :template: class.rst
 
-   RawArray
-
-Functions:
+   EvokedArray
+   EpochsArray
+   io.RawArray
 
 .. currentmodule:: mne
 
-:py:mod:`mne`:
-
 .. autosummary::
   :toctree: generated/
   :template: function.rst
@@ -225,81 +173,48 @@ Functions:
   create_info
 
 
-Sample datasets
-===============
-
-:py:mod:`mne.datasets.sample`:
-
-.. automodule:: mne.datasets.sample
- :no-members:
- :no-inherited-members:
-
-.. currentmodule:: mne.datasets.sample
-
-.. autosummary::
-   :toctree: generated/
-   :template: function.rst
-
-   data_path
-
-:py:mod:`mne.datasets.spm_face`:
-
-.. automodule:: mne.datasets.spm_face
- :no-members:
- :no-inherited-members:
-
-.. currentmodule:: mne.datasets.spm_face
-
-.. autosummary::
-   :toctree: generated/
-   :template: function.rst
-
-   data_path
-
-:py:mod:`mne.datasets.brainstorm`:
-
-.. automodule:: mne.datasets.brainstorm
- :no-members:
- :no-inherited-members:
-
-.. currentmodule:: mne.datasets.brainstorm
-
-.. autosummary::
-   :toctree: generated/
-   :template: function.rst
-
-   bst_auditory.data_path
-   bst_resting.data_path
-   bst_raw.data_path
+Datasets
+========
 
-:py:mod:`mne.datasets.megsim`:
+.. currentmodule:: mne.datasets
 
-.. automodule:: mne.datasets.megsim
- :no-members:
- :no-inherited-members:
+:py:mod:`mne.datasets`:
 
-.. currentmodule:: mne.datasets.megsim
+.. automodule:: mne.datasets
+   :no-members:
+   :no-inherited-members:
 
 .. autosummary::
    :toctree: generated/
    :template: function.rst
 
-   data_path
-   load_data
+   brainstorm.bst_auditory.data_path
+   brainstorm.bst_resting.data_path
+   brainstorm.bst_raw.data_path
+   eegbci.load_data
+   fetch_hcp_mmp_parcellation
+   hf_sef.data_path
+   megsim.data_path
+   megsim.load_data
+   misc.data_path
+   mtrf.data_path
+   multimodal.data_path
+   sample.data_path
+   somato.data_path
+   spm_face.data_path
+   visual_92_categories.data_path
 
 
 Visualization
 =============
 
+.. currentmodule:: mne.viz
+
 :py:mod:`mne.viz`:
 
 .. automodule:: mne.viz
- :no-members:
- :no-inherited-members:
-
-.. currentmodule:: mne.viz
-
-Classes:
+   :no-members:
+   :no-inherited-members:
 
 .. autosummary::
    :toctree: generated/
@@ -307,12 +222,12 @@ Classes:
 
    ClickableImage
 
-Functions:
-
 .. autosummary::
    :toctree: generated/
    :template: function.rst
 
+   add_background_image
+   compare_fiff
    circular_layout
    mne_analyze_colormap
    plot_bem
@@ -330,6 +245,9 @@ Functions:
    plot_evoked_joint
    plot_evoked_field
    plot_evoked_white
+   plot_filter
+   plot_head_positions
+   plot_ideal_filter
    plot_compare_evokeds
    plot_ica_sources
    plot_ica_components
@@ -345,21 +263,14 @@ Functions:
    plot_sensors
    plot_snr_estimate
    plot_source_estimates
+   plot_vector_source_estimates
    plot_sparse_source_estimates
    plot_tfr_topomap
    plot_topo_image_epochs
    plot_topomap
-   plot_trans
-   compare_fiff
-   add_background_image
-
-.. currentmodule:: mne.io
-
-.. autosummary::
-   :toctree: generated/
-   :template: function.rst
+   plot_alignment
+   snapshot_brain_montage
 
-   show_fiff
 
 Preprocessing
 =============
@@ -370,6 +281,12 @@ Projections:
 
 .. autosummary::
    :toctree: generated/
+   :template: class.rst
+
+   Projection
+
+.. autosummary::
+   :toctree: generated/
    :template: function.rst
 
    compute_proj_epochs
@@ -378,19 +295,13 @@ Projections:
    read_proj
    write_proj
 
-.. currentmodule:: mne.preprocessing.ssp
-
-.. autosummary::
-   :toctree: generated/
-   :template: function.rst
-
-   make_eeg_average_ref_proj
-
-Manipulate channels and set sensors locations for processing and plotting:
+:py:mod:`mne.channels`:
 
 .. currentmodule:: mne.channels
 
-Classes:
+.. automodule:: mne.channels
+   :no-members:
+   :no-inherited-members:
 
 .. autosummary::
    :toctree: generated/
@@ -400,19 +311,19 @@ Classes:
    Montage
    DigMontage
 
-Functions:
-
 .. autosummary::
    :toctree: generated/
    :template: function.rst
 
    fix_mag_coil_types
    read_montage
+   get_builtin_montages
    read_dig_montage
    read_layout
    find_layout
    make_eeg_layout
    make_grid_layout
+   find_ch_connectivity
    read_ch_connectivity
    equalize_channels
    rename_channels
@@ -420,11 +331,18 @@ Functions:
 
 :py:mod:`mne.preprocessing`:
 
+.. currentmodule:: mne.preprocessing
+
 .. automodule:: mne.preprocessing
- :no-members:
- :no-inherited-members:
+   :no-members:
+   :no-inherited-members:
 
-.. currentmodule:: mne.preprocessing
+.. autosummary::
+   :toctree: generated/
+   :template: class.rst
+
+   ICA
+   Xdawn
 
 .. autosummary::
    :toctree: generated/
@@ -436,8 +354,10 @@ Functions:
    create_eog_epochs
    find_ecg_events
    find_eog_events
+   fix_stim_artifact
    ica_find_ecg_events
    ica_find_eog_events
+   infomax
    maxwell_filter
    read_ica
    run_ica
@@ -457,28 +377,31 @@ EEG referencing:
 
 :py:mod:`mne.filter`:
 
-.. automodule:: mne.filter
- :no-members:
- :no-inherited-members:
-
 .. currentmodule:: mne.filter
 
+.. automodule:: mne.filter
+   :no-members:
+   :no-inherited-members:
+
 .. autosummary::
    :toctree: generated/
    :template: function.rst
 
-   band_pass_filter
    construct_iir_filter
+   create_filter
    estimate_ringing_samples
    filter_data
-   high_pass_filter
-   low_pass_filter
    notch_filter
+   resample
 
-Head position estimation:
+:py:mod:`mne.chpi`
 
 .. currentmodule:: mne.chpi
 
+.. automodule:: mne.chpi
+   :no-members:
+   :no-inherited-members:
+
 .. autosummary::
    :toctree: generated/
    :template: function.rst
@@ -488,8 +411,20 @@ Head position estimation:
    read_head_pos
    write_head_pos
 
+:py:mod:`mne.transforms`
+
 .. currentmodule:: mne.transforms
 
+.. automodule:: mne.transforms
+   :no-members:
+   :no-inherited-members:
+
+.. autosummary::
+   :toctree: generated/
+   :template: class.rst
+
+   Transform
+
 .. autosummary::
    :toctree: generated/
    :template: function.rst
@@ -504,6 +439,13 @@ Events
 
 .. autosummary::
    :toctree: generated/
+   :template: class.rst
+
+   Annotations
+   AcqParserFIF
+
+.. autosummary::
+   :toctree: generated/
    :template: function.rst
 
    concatenate_events
@@ -517,6 +459,12 @@ Events
    write_events
    concatenate_epochs
 
+:py:mod:`mne.event`:
+
+.. automodule:: mne.event
+   :no-members:
+   :no-inherited-members:
+
 .. currentmodule:: mne.event
 
 .. autosummary::
@@ -525,6 +473,12 @@ Events
 
    define_target_events
 
+:py:mod:`mne.epochs`:
+
+.. automodule:: mne.epochs
+   :no-members:
+   :no-inherited-members:
+
 .. currentmodule:: mne.epochs
 
 .. autosummary::
@@ -563,29 +517,29 @@ Sensor Space Data
    rename_channels
 
 
-Covariance
-==========
+Covariance computation
+======================
 
 .. currentmodule:: mne
 
 .. autosummary::
    :toctree: generated/
+   :template: class.rst
+
+   Covariance
+
+.. autosummary::
+   :toctree: generated/
    :template: function.rst
 
    compute_covariance
    compute_raw_covariance
+   cov.regularize
+   cov.compute_whitener
    make_ad_hoc_cov
    read_cov
    write_cov
 
-.. currentmodule:: mne.cov
-
-.. autosummary::
-   :toctree: generated/
-   :template: function.rst
-
-   regularize
-
 
 MRI Processing
 ==============
@@ -615,11 +569,14 @@ Step by step instructions for using :func:`gui.coregistration`:
 Forward Modeling
 ================
 
-:py:mod:`mne`:
-
 .. currentmodule:: mne
 
-Functions:
+.. autosummary::
+   :toctree: generated/
+   :template: class.rst
+
+   Forward
+   SourceSpaces
 
 .. autosummary::
    :toctree: generated/
@@ -630,6 +587,8 @@ Functions:
    apply_forward_raw
    average_forward_solutions
    convert_forward_solution
+   forward.restrict_forward_to_label
+   forward.restrict_forward_to_stc
    make_bem_model
    make_bem_solution
    make_forward_dipole
@@ -645,27 +604,34 @@ Functions:
    sensitivity_map
    setup_source_space
    setup_volume_source_space
+   surface.complete_surface_info
    write_bem_surfaces
    write_trans
 
+:py:mod:`mne.bem`:
+
+.. automodule:: mne.bem
+   :no-members:
+   :no-inherited-members:
+
 .. currentmodule:: mne.bem
 
 .. autosummary::
    :toctree: generated/
-   :template: function.rst
-
-   make_watershed_bem
-   make_flash_bem
-   convert_flash_mris
+   :template: class.rst
 
-.. currentmodule:: mne.forward
+   ConductorModel
 
 .. autosummary::
    :toctree: generated/
    :template: function.rst
 
-   restrict_forward_to_label
-   restrict_forward_to_stc
+   fit_sphere_to_headshape
+   get_fitting_dig
+   make_watershed_bem
+   make_flash_bem
+   convert_flash_mris
+
 
 Inverse Solutions
 =================
@@ -673,21 +639,17 @@ Inverse Solutions
 :py:mod:`mne.minimum_norm`:
 
 .. automodule:: mne.minimum_norm
-  :no-members:
-  :no-inherited-members:
+   :no-members:
+   :no-inherited-members:
 
 .. currentmodule:: mne.minimum_norm
 
-Classes:
-
 .. autosummary::
    :toctree: generated/
    :template: class.rst
 
    InverseOperator
 
-Functions:
-
 .. autosummary::
    :toctree: generated/
    :template: function.rst
@@ -710,8 +672,8 @@ Functions:
 :py:mod:`mne.inverse_sparse`:
 
 .. automodule:: mne.inverse_sparse
-  :no-members:
-  :no-inherited-members:
+   :no-members:
+   :no-inherited-members:
 
 .. currentmodule:: mne.inverse_sparse
 
@@ -722,12 +684,13 @@ Functions:
    mixed_norm
    tf_mixed_norm
    gamma_map
+   make_stc_from_dipoles
 
 :py:mod:`mne.beamformer`:
 
 .. automodule:: mne.beamformer
-  :no-members:
-  :no-inherited-members:
+   :no-members:
+   :no-inherited-members:
 
 .. currentmodule:: mne.beamformer
 
@@ -738,16 +701,25 @@ Functions:
    lcmv
    lcmv_epochs
    lcmv_raw
+   make_lcmv
+   apply_lcmv
+   apply_lcmv_epochs
+   apply_lcmv_raw
    dics
    dics_epochs
    dics_source_power
    rap_music
-
-:py:mod:`mne`:
+   tf_dics
+   tf_lcmv
 
 .. currentmodule:: mne
 
-Functions:
+.. autosummary::
+   :toctree: generated/
+   :template: class.rst
+
+   Dipole
+   DipoleFixed
 
 .. autosummary::
    :toctree: generated/
@@ -757,9 +729,11 @@ Functions:
 
 :py:mod:`mne.dipole`:
 
-.. currentmodule:: mne.dipole
+.. automodule:: mne.dipole
+   :no-members:
+   :no-inherited-members:
 
-Functions:
+.. currentmodule:: mne.dipole
 
 .. autosummary::
    :toctree: generated/
@@ -775,6 +749,17 @@ Source Space Data
 
 .. autosummary::
    :toctree: generated/
+   :template: class.rst
+
+   BiHemiLabel
+   Label
+   MixedSourceEstimate
+   SourceEstimate
+   VectorSourceEstimate
+   VolSourceEstimate
+
+.. autosummary::
+   :toctree: generated/
    :template: function.rst
 
    compute_morph_matrix
@@ -804,13 +789,11 @@ Time-Frequency
 :py:mod:`mne.time_frequency`:
 
 .. automodule:: mne.time_frequency
- :no-members:
- :no-inherited-members:
+   :no-members:
+   :no-inherited-members:
 
 .. currentmodule:: mne.time_frequency
 
-Classes:
-
 .. autosummary::
    :toctree: generated/
    :template: class.rst
@@ -831,6 +814,9 @@ Functions that operate on mne-python objects:
    tfr_morlet
    tfr_multitaper
    tfr_stockwell
+   tfr_array_morlet
+   tfr_array_multitaper
+   tfr_array_stockwell
    read_tfrs
    write_tfrs
 
@@ -841,20 +827,20 @@ Functions that operate on ``np.ndarray`` objects:
    :template: function.rst
 
    csd_array
-   cwt_morlet
    dpss_windows
    morlet
-   single_trial_power
    stft
    istft
    stftfreq
+   psd_array_multitaper
+   psd_array_welch
 
 
 :py:mod:`mne.time_frequency.tfr`:
 
 .. automodule:: mne.time_frequency.tfr
- :no-members:
- :no-inherited-members:
+   :no-members:
+   :no-inherited-members:
 
 .. currentmodule:: mne.time_frequency.tfr
 
@@ -872,8 +858,8 @@ Connectivity Estimation
 :py:mod:`mne.connectivity`:
 
 .. automodule:: mne.connectivity
- :no-members:
- :no-inherited-members:
+   :no-members:
+   :no-inherited-members:
 
 .. currentmodule:: mne.connectivity
 
@@ -892,8 +878,8 @@ Statistics
 :py:mod:`mne.stats`:
 
 .. automodule:: mne.stats
- :no-members:
- :no-inherited-members:
+   :no-members:
+   :no-inherited-members:
 
 .. currentmodule:: mne.stats
 
@@ -911,11 +897,12 @@ Statistics
    ttest_1samp_no_p
    linear_regression
    linear_regression_raw
+   f_oneway
    f_mway_rm
    f_threshold_mway_rm
    summarize_clusters_stc
 
-Functions to compute connectivity (adjacency) matrices for cluster-level statistics
+Functions to compute neighbor/adjacency matrices for cluster-level statistics:
 
 .. currentmodule:: mne
 
@@ -938,8 +925,8 @@ Simulation
 :py:mod:`mne.simulation`:
 
 .. automodule:: mne.simulation
- :no-members:
- :no-inherited-members:
+   :no-members:
+   :no-inherited-members:
 
 .. currentmodule:: mne.simulation
 
@@ -953,6 +940,9 @@ Simulation
    simulate_sparse_stc
    select_source_in_label
 
+
+.. _api_decoding:
+
 Decoding
 ========
 
@@ -962,19 +952,36 @@ Decoding
    :no-members:
    :no-inherited-members:
 
-Classes:
-
 .. autosummary::
    :toctree: generated/
    :template: class.rst
 
    CSP
-   EpochsVectorizer
+   EMS
    FilterEstimator
-   GeneralizationAcrossTime
+   LinearModel
    PSDEstimator
    Scaler
-   TimeDecoding
+   TemporalFilter
+   TimeFrequency
+   UnsupervisedSpatialFilter
+   Vectorizer
+   ReceptiveField
+   TimeDelayingRidge
+   SlidingEstimator
+   GeneralizingEstimator
+   SPoC
+
+Functions that assist with decoding and model fitting:
+
+.. autosummary::
+   :toctree: generated/
+   :template: function.rst
+
+   compute_ems
+   cross_val_multiscore
+   get_coef
+
 
 Realtime
 ========
@@ -985,8 +992,6 @@ Realtime
    :no-members:
    :no-inherited-members:
 
-Classes:
-
 .. autosummary::
    :toctree: generated/
    :template: class.rst
@@ -1004,16 +1009,47 @@ MNE-Report
 
 :py:mod:`mne.report`:
 
-.. automodule:: mne.report
- :no-members:
- :no-inherited-members:
-
 .. currentmodule:: mne.report
 
-Classes:
+.. automodule:: mne.report
+   :no-members:
+   :no-inherited-members:
 
 .. autosummary::
    :toctree: generated/
    :template: class.rst
 
    Report
+
+
+Logging and Configuration
+=========================
+
+.. currentmodule:: mne
+
+.. autosummary::
+   :toctree: generated/
+   :template: function.rst
+
+   get_config_path
+   get_config
+   open_docs
+   set_log_level
+   set_log_file
+   set_config
+   sys_info
+   verbose
+
+:py:mod:`mne.cuda`:
+
+.. currentmodule:: mne.cuda
+
+.. automodule:: mne.cuda
+   :no-members:
+   :no-inherited-members:
+
+.. autosummary::
+   :toctree: generated/
+   :template: function.rst
+
+   init_cuda
diff --git a/doc/references.rst b/doc/references.rst
index f3ed7d5..b527491 100644
--- a/doc/references.rst
+++ b/doc/references.rst
@@ -1,4 +1,4 @@
-
+:orphan:
 
 .. _ch_reading:
 
diff --git a/doc/sphinxext/cited_mne.py b/doc/sphinxext/cited_mne.py
old mode 100644
new mode 100755
index 7de0176..0efc2ca
--- a/doc/sphinxext/cited_mne.py
+++ b/doc/sphinxext/cited_mne.py
@@ -3,8 +3,15 @@
 
 Example usage::
 
-    $ cited_mne --backend selenium --clear
+    $ ./cited_mne.py --backend selenium --clear
 
+This requires joblib, BeautifulSoup, and selenium.
+selenium in turn requires geckodriver:
+
+    https://github.com/mozilla/geckodriver/releases
+
+The process will involve window popups to satisfy
+CAPTCHA checks.
 """
 
 # Author: Mainak Jas <mainak.jas at telecom-paristech.fr>
@@ -37,12 +44,14 @@ UA = ('Mozilla/5.0 (X11; U; FreeBSD i386; en-US; rv:1.9.2.9) '
       'Gecko/20100913 Firefox/3.6.9')
 
 # ##### Templates for citations #####
-html = (u""".. _cited
+html = (u""":orphan:
+
+.. _cited:
 
-Publications from MNE users
-===========================
+Publications by users
+=====================
 
-Papers citing MNE as extracted from Google Scholar (on %s).
+Papers citing MNE (%d) as extracted from Google Scholar (on %s).
 
 """)
 
@@ -203,8 +212,6 @@ if __name__ == '__main__':
 
     random.seed()
     gen_date = time.strftime("%B %d, %Y")
-    html = html % gen_date
-
     url_tails = ['1521584321377182930', '12188330066413208874']
     papers = ['MEG and EEG data analysis with MNE-Python',
               'MNE software for processing MEG and EEG data']
@@ -227,6 +234,8 @@ if __name__ == '__main__':
     # alphabetic order
     publications = np.union1d(publications[1], publications[0]).tolist()
 
+    html = html % (len(publications), gen_date)
+
     # sort by year of publication
     years = list()
     for pub in publications:
diff --git a/doc/sphinxext/gen_commands.py b/doc/sphinxext/gen_commands.py
index cf26750..ce36f43 100644
--- a/doc/sphinxext/gen_commands.py
+++ b/doc/sphinxext/gen_commands.py
@@ -53,6 +53,8 @@ command_rst = """
 
 def generate_commands_rst(app):
     out_dir = op.abspath(op.join(op.dirname(__file__), '..', 'generated'))
+    if not op.isdir(out_dir):
+        os.mkdir(out_dir)
     out_fname = op.join(out_dir, 'commands.rst')
 
     command_path = op.join(os.path.dirname(__file__), '..', '..', 'mne',
diff --git a/doc/tutorials.rst b/doc/tutorials.rst
deleted file mode 100644
index a67d6d8..0000000
--- a/doc/tutorials.rst
+++ /dev/null
@@ -1,192 +0,0 @@
-.. _tutorials:
-
-Tutorials
-=========
-
-Once you have
-:ref:`Python and MNE-Python up and running <install_python_and_mne_python>`,
-you can use these tutorials to get started processing MEG/EEG.
-You can find each step of the processing pipeline, and re-run the
-Python code by copy-paste.
-
-These tutorials aim to capture only the most important information.
-For further reading:
-
-- For a high-level overview of what you can do with MNE-Python:
-  :ref:`what_can_you_do`
-- For more examples of analyzing M/EEG data, including more sophisticated
-  analysis: :ref:`general_examples`
-- For details about analysis steps: :ref:`manual`
-- For details about specific functions and classes: :ref:`api_reference`
-
-.. note:: The default location for the MNE-sample data is
-          my-path-to/mne-python/examples. If you downloaded data and an
-          example asks you whether to download it again, make sure
-          the data reside in the examples directory
-          and that you run the script from its current directory.
-
-          .. code-block:: bash
-
-              $ cd examples/preprocessing
-
-          Then in Python you can do::
-
-              In [1]: %run plot_find_ecg_artifacts.py
-
-
-          See :ref:`datasets` for a list of all available datasets and some
-          advanced configuration options, e.g. to specify a custom
-          location for storing the datasets.
-
-.. container:: span box
-
-  .. raw:: html
-
-    <h2>Introduction to MNE and Python</h2>
-
-  .. toctree::
-    :maxdepth: 1
-
-    auto_tutorials/plot_python_intro.rst
-    tutorials/seven_stories_about_mne.rst
-    auto_tutorials/plot_introduction.rst
-
-.. container:: span box
-
-  .. raw:: html
-
-    <h2>Background information</h2>
-
-  .. toctree::
-    :maxdepth: 1
-
-    auto_tutorials/plot_background_filtering.rst
-
-.. container:: span box
-
-  .. raw:: html
-
-    <h2>Preprocessing</h2>
-
-  .. toctree::
-    :maxdepth: 1
-
-    auto_tutorials/plot_artifacts_detection.rst
-    auto_tutorials/plot_artifacts_correction_filtering.rst
-    auto_tutorials/plot_artifacts_correction_rejection.rst
-    auto_tutorials/plot_artifacts_correction_ssp.rst
-    auto_tutorials/plot_artifacts_correction_ica.rst
-    auto_tutorials/plot_artifacts_correction_maxwell_filtering.rst
-
-.. container:: span box
-
-  .. raw:: html
-
-    <h2>Sensor-level analysis</h2>
-
-  .. toctree::
-    :maxdepth: 1
-
-    auto_tutorials/plot_epoching_and_averaging.rst
-    auto_tutorials/plot_eeg_erp.rst
-    auto_tutorials/plot_sensors_time_frequency.rst
-    auto_tutorials/plot_sensors_decoding.rst
-
-.. container:: span box
-
-  .. raw:: html
-
-    <h2>Visualization and Reporting</h2>
-
-  .. toctree::
-    :maxdepth: 1
-
-    auto_tutorials/plot_visualize_raw.rst
-    auto_tutorials/plot_visualize_epochs.rst
-    auto_tutorials/plot_visualize_evoked.rst
-    tutorials/report.rst
-
-.. container:: span box
-
-  .. raw:: html
-
-    <h2>Manipulating Data Structures and Containers</h2>
-
-  .. toctree::
-    :maxdepth: 1
-
-    auto_tutorials/plot_object_raw.rst
-    auto_tutorials/plot_modifying_data_inplace.rst
-    auto_tutorials/plot_object_epochs.rst
-    auto_tutorials/plot_object_evoked.rst
-    auto_tutorials/plot_creating_data_structures.rst
-    auto_tutorials/plot_info.rst
-
-.. container:: span box
-
-  .. raw:: html
-
-    <h2>Source-level analysis</h2>
-
-  .. toctree::
-    :maxdepth: 1
-
-    auto_tutorials/plot_forward.rst
-    auto_tutorials/plot_compute_covariance.rst
-    auto_tutorials/plot_mne_dspm_source_localization.rst
-    auto_tutorials/plot_dipole_fit.rst
-    auto_tutorials/plot_brainstorm_auditory.rst
-    auto_tutorials/plot_brainstorm_phantom_ctf.rst
-    auto_tutorials/plot_brainstorm_phantom_elekta.rst
-    auto_tutorials/plot_point_spread.rst
-
-.. container:: span box
-
-  .. raw:: html
-
-    <h2>Sensor-space Univariate Statistics</h2>
-
-  .. toctree::
-    :maxdepth: 1
-
-    auto_tutorials/plot_stats_cluster_methods.rst
-    auto_tutorials/plot_stats_spatio_temporal_cluster_sensors.rst
-    auto_tutorials/plot_stats_cluster_1samp_test_time_frequency.rst
-    auto_tutorials/plot_stats_cluster_time_frequency.rst
-
-.. container:: span box
-
-  .. raw:: html
-
-    <h2>Source-space Univariate Statistics</h2>
-
-  .. toctree::
-    :maxdepth: 1
-
-    auto_tutorials/plot_stats_cluster_time_frequency_repeated_measures_anova.rst
-    auto_tutorials/plot_stats_cluster_spatio_temporal_2samp.rst
-    auto_tutorials/plot_stats_cluster_spatio_temporal_repeated_measures_anova.rst
-    auto_tutorials/plot_stats_cluster_spatio_temporal.rst
-
-.. container:: span box
-
-  .. raw:: html
-
-    <h2>Multivariate Statistics - Decoding</h2>
-
-  .. toctree::
-    :maxdepth: 1
-
-    auto_tutorials/plot_sensors_decoding.rst
-
-.. container:: span box
-
-  .. raw:: html
-
-    <h2>Command line tools</h2>
-
-  .. toctree::
-    :maxdepth: 1
-
-    tutorials/command_line.rst
-    generated/commands.rst
diff --git a/doc/tutorials/seven_stories_about_mne.rst b/doc/tutorials/philosophy.rst
similarity index 66%
rename from doc/tutorials/seven_stories_about_mne.rst
rename to doc/tutorials/philosophy.rst
index be08a74..946358e 100644
--- a/doc/tutorials/seven_stories_about_mne.rst
+++ b/doc/tutorials/philosophy.rst
@@ -1,25 +1,10 @@
-Seven stories about MNE
-=======================
-
-
-1. What the FIF does MNE stand for?
------------------------------------
-Historically, MNE was a software for computing cortically constrained
-Minimum Norm Estimates from MEG and EEG data. The historical core
-functions of MNE were written by Matti Hämäläinen in Boston and originate
-in part from the Elekta software that is shipped with its MEG systems.
-Ah yes, the FIFF is Elektas Functional Imaging File Format that goes
-along with `.fif` file extensions and is natively used by its MEG systems.
-For these reasons the MNE software is internally relying on the FIFF files.
-Today the situation is a bit different though. MNE is nowadays developed
-mostly in Python by an international team of researchers from diverse
-laboratories and has widened its scope. MNE supports advanced sensor space
-analyses for EEG, temporal ICA, many different file formats and many other
-inverse solvers, for example beamformers. Some of our contributors even
-use it for intracranial data. If you want, MNE can be thought of as MEG'n'EEG.
-
-2. Reading data into the MNE layout
------------------------------------
+.. include:: ../git_links.inc
+
+MNE quickstart and background
+=============================
+
+1. Reading data into MNE
+------------------------
 One of the first things you might be wondering about is how to get your
 data into mne. Assuming that you have unprocessed data, you will probably
 be happy with at least one of these readers:
@@ -35,16 +20,10 @@ be happy with at least one of these readers:
 * :func:`read_raw_egi <mne.io.read_raw_egi>`
 * :func:`read_raw_nicolet <mne.io.read_raw_nicolet>`
 
-They all have in common to return an :class:`mne.io.Raw` object and the MEG
-readers perform conversions of sensor positions and channel names
-to make the meta data compatible with the conventions of the FIFF
-format. Yes, at this point MNE relies on the historical layout and
-therefore expects MEG data to look like Elekta Neuromag data and to
-conform to Freesurfer data layouts. This is somewhat relaxed for EEG
-data, which have less to do with Neuromag and very often are not
-used for source space analyses. See :ref:`ch_convert`.
+They all have in common to return an :class:`mne.io.Raw`-like object.
+See :ref:`ch_convert`.
 
-3. MNE gives you objects with methods
+2. MNE gives you objects with methods
 -------------------------------------
 We said above that there are MNE objects. This is of course computer
 science jargon. What it actually means is that you get a data structure
@@ -58,12 +37,12 @@ below. Whether you consider Raw objects that describe continuous data,
 Epochs objects describing segmented single trial data, or Evoked objects
 describing averaged data, all have in common that they share certain methods.
 
-- Try :func:`raw.plot <mne.io.Raw.plot>`,
-  :func:`epochs.plot <mne.Epochs.plot>`,
-  :func:`evoked.plot <mne.Evoked.plot>` and any other method that has
+- Try :meth:`raw.plot <mne.io.Raw.plot>`,
+  :meth:`epochs.plot <mne.Epochs.plot>`,
+  :meth:`evoked.plot <mne.Evoked.plot>` and any other method that has
   a name that starts with `plot`. By using the call operators `()`
   you invoke these methods, e.g.
-  :func:`epochs.plot() <<mne.Epochs.plot>>`.
+  :meth:`epochs.plot() <mne.Epochs.plot>`.
   Yes, you don't have to pass arguments but you will get an informative
   visualization of your data. The method knows what to do with the object.
   Look up the documentation for configuration options.
@@ -102,15 +81,15 @@ describing averaged data, all have in common that they share certain methods.
   allows you to store your data into a FIFF file.
 
 
-4. A key thing for MNE objects is the measurment info
------------------------------------------------------
-Besides `.ch_names` another important attribute is .info. It contains
+3. A key thing for MNE objects is the measurement info
+------------------------------------------------------
+Besides ``.ch_names`` another important attribute is ``.info``. It contains
 the channel information and some details about the processing history.
 This is especially relevant if your data cannot be read using the io
 functions listed above. You then need to learn how to create an info.
 See :ref:`tut_info_objects`.
 
-5. MNE is modular
+4. MNE is modular
 -----------------
 Beyond methods another concept that is important to get are *modules*.
 Think of them as name spaces, another computer science term.
@@ -124,11 +103,11 @@ processing contexts. Looking for I/O operations for raw data?::
 
     >>> from mne import io
 
-Wanna do preprocessing?::
+Want to do preprocessing?::
 
     >>> from mne import preprocessing
 
-Wanna do visualization?::
+Want to do visualization?::
 
     >>> from mne import viz
 
@@ -139,21 +118,24 @@ Decoding?::
 I'm sure you got it, so explore your intuitions when searching for
 a certain function.
 
-6. Inspect and script
+5. Inspect and script
 ---------------------
-Did you happen to notice that some of the figures returned by `.plot`
-methods allow you to interact with the data? Look at raw.plot and
-epochs.plot for example. They allow you to update channel selections,
+Did you happen to notice that some of the figures returned by ``.plot``
+methods allow you to interact with the data? Look at :meth:`raw.plot <mne.io.Raw.plot>` and
+:meth:`epochs.plot <mne.Epochs.plot>` for example. They allow you to update channel selections,
 scalings and time ranges. However, they do not replace scripting.
 The MNE philosophy is to facilitate diagnostic plotting but does
 not support doing analysis by clicking your way. MNE is meant to be
-a toolbox, and its your task to combine the tools by writing scripts.
-This should really save you time, first of all by being able to reuse
-code and avoiding to click it again. Second by documenting what you
-did. Reviewers are asking you to update your analysis that you actually
+a toolbox, and its your task to combine the tools by **writing scripts**.
+This should save you time in the long run by:
+
+1. Enabling code reuse.
+2. Documenting what you did.
+
+Reviewers are asking you to update your analysis that you actually
 finished 1 year ago? Luckily you have a script.
 
-7. Eighty percent or Python
+6. Eighty percent or Python
 ---------------------------
 A related point is that MNE functions are there to make it fun to
 process common tasks and facilitate doing difficult things.
@@ -165,7 +147,7 @@ you need in order be happy in 80 percent of the time. Where you need
 more Python is there for you. You can easily access the data, e.g.
 `raw[:10, :1000]` or `epochs.get_data()` or `evoked.data` and
 manipulate them using numpy or pass them to high-level machine learning code
-from `scikit-learn <http://scikit-learn.org>`_. Each `.plot` method
+from `scikit-learn`_. Each ``.plot`` method
 returns a matplotlib figure object. Both packages have great documentations
 and often writing Python code amounts to looking up the right library that
 allows you to tackle the problem in a few lines.
diff --git a/doc/tutorials/report.rst b/doc/tutorials/report.rst
index 866d0ab..5ecbf48 100644
--- a/doc/tutorials/report.rst
+++ b/doc/tutorials/report.rst
@@ -29,7 +29,7 @@ The command line interface
 To generate a barebones report from all the \*.fif files in the sample dataset,
 invoke the following command in a system (e.g., Bash) shell:
 
-.. code-block:: bash
+.. code-block:: console
 
     $ mne report --path MNE-sample-data/ --verbose
 
@@ -40,30 +40,30 @@ If the report is generated for a single subject, give the ``SUBJECT`` name and t
 ``SUBJECTS_DIR`` and this will generate the MRI slices (with BEM contours overlaid on top
 if available):
 
-.. code-block:: bash
+.. code-block:: console
 
     $ mne report --path MNE-sample-data/ --subject sample --subjects-dir MNE-sample-data/subjects --verbose
 
 To properly render `trans` and `covariance` files, add the measurement information:
 
-.. code-block:: bash
+.. code-block:: console
 
-    $ mne report --path MNE-sample-data/ --info MNE-sample-data/MEG/sample/sample_audvis-ave.fif \ 
+    $ mne report --path MNE-sample-data/ --info MNE-sample-data/MEG/sample/sample_audvis-ave.fif \
           --subject sample --subjects-dir MNE-sample-data/subjects --verbose
 
 To render whitened `evoked` files with baseline correction, add the noise covariance file:
-    
-.. code-block:: bash
 
-    $ mne report --path MNE-sample-data/ --info MNE-sample-data/MEG/sample/sample_audvis-ave.fif \ 
+.. code-block:: console
+
+    $ mne report --path MNE-sample-data/ --info MNE-sample-data/MEG/sample/sample_audvis-ave.fif \
           --cov MNE-sample-data/MEG/sample/sample_audvis-cov.fif --bmax 0 --subject sample \
           --subjects-dir MNE-sample-data/subjects --verbose
 
 To generate the report in parallel:
 
-.. code-block:: bash
+.. code-block:: console
 
-    $ mne report --path MNE-sample-data/ --info MNE-sample-data/MEG/sample/sample_audvis-ave.fif \ 
+    $ mne report --path MNE-sample-data/ --info MNE-sample-data/MEG/sample/sample_audvis-ave.fif \
           --subject sample --subjects-dir MNE-sample-data/subjects --verbose --jobs 6
 
 The report rendered on sample-data is shown below:
@@ -73,7 +73,7 @@ The report rendered on sample-data is shown below:
 
 For help on all the available options, do:
 
-.. code-block:: bash
+.. code-block:: console
 
     $ mne report --help
 
@@ -121,12 +121,12 @@ Save the report as an html, but do not open the html in a browser::
     >>> report.save('report.html', overwrite=True, open_browser=False) # doctest:+SKIP
     Rendering : Table of Contents...
 
-There is greater flexibility compared to the command line interface. 
+There is greater flexibility compared to the command line interface.
 Custom plots can be added to the report. Let us first generate a custom plot::
 
     >>> from mne import read_evokeds
     >>> fname = path + '/MEG/sample/sample_audvis-ave.fif'
-    >>> evoked = read_evokeds(fname, condition='Left Auditory', baseline=(None, 0), verbose=True) # doctest:+ELLIPSIS
+    >>> evoked = read_evokeds(fname, condition='Left Auditory', baseline=(None, 0), verbose=True)  # doctest: +ELLIPSIS
     Reading ...
         Read a total of 4 projection items:
             PCA-v1 (1 x 102) active
@@ -138,7 +138,7 @@ Custom plots can be added to the report. Let us first generate a custom plot::
             0 CTF compensation matrices available
             nave = 55 - aspect type = 100
     Projections have already been applied. Setting proj attribute to True.
-    Applying baseline correction ... (mode: mean)
+    Applying baseline correction (mode: mean)
     >>> fig = evoked.plot() # doctest: +SKIP
 
 To add the custom plot to the report, do::
@@ -148,8 +148,8 @@ To add the custom plot to the report, do::
     Rendering : Table of Contents...
 
 The MNE report command internally manages the sections so that plots belonging to the same section
-are rendered consecutively. Within a section, the plots are ordered in the same order that they were 
-added using the `add_figs_to_section` command. Each section is identified by a toggle button in the navigation 
+are rendered consecutively. Within a section, the plots are ordered in the same order that they were
+added using the `add_figs_to_section` command. Each section is identified by a toggle button in the navigation
 bar of the report which can be used to show or hide the contents of the section.
 
 That's it!
diff --git a/doc/whats_new.rst b/doc/whats_new.rst
index 0b30924..ad72acd 100644
--- a/doc/whats_new.rst
+++ b/doc/whats_new.rst
@@ -1,3 +1,5 @@
+:orphan:
+
 .. include:: links.inc
 .. _whats_new:
 
@@ -9,6 +11,605 @@ What's new
 
 .. currentmodule:: mne
 
+.. _current:
+
+.. _changes_0_15_2:
+
+Version 0.15.2
+--------------
+
+BUG
+~~~
+
+- Fix bug in :meth:`mne.io.Raw.plot` to correctly display event types when annotations are present by `Clemens Brunner`_
+
+
+.. _changes_0_15_1:
+
+Version 0.15.1
+--------------
+
+BUG
+~~~
+
+- Fix bug in :meth:`mne.io.set_eeg_reference` to remove an average reference projector when setting the reference to ``[]`` (i.e. do not change the existing reference) by `Clemens Brunner`_
+
+- Fix bug in :func:`mne.simulation.simulate_raw` where 1- and 3-layer BEMs were not properly transformed using ``trans`` by `Eric Larson`_
+
+.. _changes_0_15:
+
+Version 0.15
+------------
+
+Changelog
+~~~~~~~~~
+
+- :meth:`mne.channels.Layout.plot` and :func:`mne.viz.plot_layout` now allows plotting a subset of channels with ``picks`` argument by `Jaakko Leppakangas`_
+
+- Add .bvef extension (BrainVision Electrodes File) to :func:`mne.channels.read_montage` by `Jean-Baptiste Schiratti`_
+
+- Add :func:`mne.decoding.cross_val_multiscore` to allow scoring of multiple tasks, typically used with :class:`mne.decoding.SlidingEstimator`, by `Jean-Remi King`_
+
+- Add :class:`mne.decoding.ReceptiveField` module for modeling electrode response to input features by `Chris Holdgraf`_
+
+- Add :class:`mne.decoding.TimeDelayingRidge` class, used by default by :class:`mne.decoding.ReceptiveField`, to speed up auto- and cross-correlation computations and enable Laplacian regularization by `Ross Maddox`_ and `Eric Larson`_
+
+- Add new :mod:`mne.datasets.mtrf <mne.datasets.mtrf.data_path>` dataset by `Chris Holdgraf`_
+
+- Add example of time-frequency decoding with CSP by `Laura Gwilliams`_
+
+- Add :class:`mne.decoding.SPoC` to fit and apply spatial filters based on continuous target variables, by `Jean-Remi King`_ and `Alexandre Barachant`_
+
+- Add Fieldtrip's electromyogram dataset, by `Alexandre Barachant`_
+
+- Add ``reject_by_annotation`` option to :func:`mne.preprocessing.find_eog_events` (which is also utilised by :func:`mne.preprocessing.create_eog_epochs`) to omit data that is annotated as bad by `Jaakko Leppakangas`_
+
+- Add example for fast screening of event-related dynamics in frequency bands by `Denis Engemann`_
+
+- Add :meth:`mne.time_frequency.EpochsTFR.save` by `Jaakko Leppakangas`_
+
+- Add butterfly mode (toggled with 'b' key) to :meth:`mne.io.Raw.plot` by `Jaakko Leppakangas`_
+
+- Add ``axes`` parameter to plot_topo functions by `Jaakko Leppakangas`_
+
+- Add options to change time windowing in :func:`mne.chpi.filter_chpi` by `Eric Larson`_
+
+- :meth:`mne.channels.Montage.plot`, :meth:`mne.channels.DigMontage.plot`, and :func:`mne.viz.plot_montage` now allow plotting channel locations as a topomap by `Clemens Brunner`_
+
+- Add ``background_color`` parameter to :meth:`mne.Evoked.plot_topo` and :func:`mne.viz.plot_evoked_topo` and improve axes rendering as done in :func:`mne.viz.plot_compare_evokeds` by `Alex Gramfort`_
+
+- Add support for GDF files in :func:`mne.io.read_raw_edf` by `Nicolas Barascud`_
+
+- Add :func:`mne.io.find_edf_events` for getting the events as they are in the EDF/GDF header by `Jaakko Leppakangas`_
+
+- Speed up :meth:`mne.io.Raw.plot` and :meth:`mne.Epochs.plot` using (automatic) decimation based on low-passing with ``decim='auto'`` parameter by `Eric Larson`_ and `Jaakko Leppakangas`_
+
+- Add ``mne.inverse_sparse.mxne_optim.dgap_l21l1`` for computing the duality gap for TF-MxNE as the new stopping criterion by `Daniel Strohmeier`_
+
+- Add option to return a list of :class:`Dipole` objects in sparse source imaging methods by `Daniel Strohmeier`_
+
+- Add :func:`mne.inverse_sparse.make_stc_from_dipoles` to generate stc objects from lists of dipoles by `Daniel Strohmeier`_
+
+- Add :func:`mne.channels.find_ch_connectivity` that tries to infer the correct connectivity template using channel info. If no template is found, it computes the connectivity matrix using :class:`Delaunay <scipy.spatial.Delaunay>` triangulation of the 2d projected channel positions by `Jaakko Leppakangas`_
+
+- Add IO support for EGI MFF format by `Jaakko Leppakangas`_  and `ramonapariciog`_
+
+- Add option to use matplotlib backend when plotting with :func:`mne.viz.plot_source_estimates` by `Jaakko Leppakangas`_
+
+- Add :meth:`mne.channels.Montage.get_pos2d` to get the 2D positions of channels in a montage by `Clemens Brunner`_
+
+- Add MGH 60- and 70-channel standard montages to :func:`mne.channels.read_montage` by `Eric Larson`_
+
+- Add option for embedding SVG instead of PNG in HTML for :class:`mne.report.Report` by `Eric Larson`_
+
+- Add confidence intervals, number of free parameters, and χ² to :func:`mne.fit_dipole` and :func:`mne.read_dipole` by `Eric Larson`_
+
+- :attr:`mne.SourceEstimate.data` is now writable, writing to it will also update :attr:`mne.SourceEstimate.times` by `Marijn van Vliet`_
+
+- :meth:`mne.io.Raw.plot` and :meth:`mne.Epochs.plot` now use anti-aliasing to draw signals by `Clemens Brunner`_
+
+- Allow using saved ``DigMontage`` to import digitization to :func:`mne.gui.coregistration` by `Jaakko Leppakangas`_
+
+- Add function :func:`mne.channels.get_builtin_montages` to list all built-in montages by `Clemens Brunner`_
+
+- :class:`mne.decoding.SlidingEstimator` and :class:`mne.decoding.GeneralizingEstimator` now accept ``**fit_params`` at fitting by `Jean-Remi King`_
+
+- Add :class:`mne.VectorSourceEstimate` class which enables working with both source power and dipole orientations by `Marijn van Vliet`_
+
+- Add option ``pick_ori='vector'`` to :func:`mne.minimum_norm.apply_inverse` to produce :class:`mne.VectorSourceEstimate` by `Marijn van Vliet`_
+
+- Add support for :class:`numpy.random.RandomState` argument to ``seed`` in :mod:`statistical clustering functions <mne.stats>` and better documentation of exact 1-sample tests by `Eric Larson`_
+
+- Extend :func:`mne.viz.plot_epochs_image`/:meth:`mne.Epochs.plot_image` with regards to grouping by or aggregating over channels. See the new example at `examples/visualization/plot_roi_erpimage_by_rt.py` by `Jona Sassenhagen`_
+
+- Add bootstrapped confidence intervals to :func:`mne.viz.plot_compare_evokeds` by `Jona Sassenhagen`_ and `Denis Engemann`_
+
+- Add example on how to plot ERDS maps (also known as ERSP) by `Clemens Brunner`_
+
+- Add support for volume source spaces to :func:`spatial_src_connectivity` and :func:`spatio_temporal_src_connectivity` by `Alex Gramfort`_
+
+- Plotting raw data (:func:`mne.viz.plot_raw` or :meth:`mne.io.Raw.plot`) with events now includes event numbers (if there are not more than 50 events on a page) by `Clemens Brunner`_
+
+- Add filtering functions :meth:`mne.Epochs.filter` and :meth:`mne.Evoked.filter`, as well as ``pad`` argument to :meth:`mne.io.Raw.filter` by `Eric Larson`_
+
+- Add high frequency somatosensory MEG dataset by `Jussi Nurminen`_
+
+- Add reduced set of labels for HCPMMP-1.0 parcellation in :func:`mne.datasets.fetch_hcp_mmp_parcellation` by `Eric Larson`_
+
+- Enable morphing between hemispheres with :func:`mne.compute_morph_matrix` by `Christian Brodbeck`_
+
+- Add ``return_drop_log`` to :func:`mne.preprocessing.compute_proj_eog` and :func:`mne.preprocessing.compute_proj_ecg` by `Eric Larson`_
+
+- Add time cursor and category/amplitude status message into the single-channel evoked plot by `Jussi Nurminen`_
+
+BUG
+~~~
+
+- Fixed a bug when creating spherical volumetric grid source spaces in :func:`setup_volume_source_space` by improving the minimum-distance computations, which in general will decrease the number of used source space points by `Eric Larson`_
+
+- Fix bug in :meth:`mne.io.read_raw_brainvision` read .vhdr files with ANSI codepage by `Okba Bekhelifi`_ and `Alex Gramfort`_
+
+- Fix unit scaling when reading in EGI digitization files using :func:`mne.channels.read_dig_montage` by `Matt Boggess`_
+
+- Fix ``picks`` default in :meth:`mne.io.Raw.filter` to include ``ref_meg`` channels by default by `Eric Larson`_
+
+- Fix :class:`mne.decoding.CSP` order of spatial filter in ``patterns_`` by `Alexandre Barachant`_
+
+- :meth:`mne.concatenate_epochs` now maintains the relative position of events during concatenation by `Alexandre Barachant`_
+
+- Fix bug in script `mne make_scalp_surfaces` by `Denis Engemann`_ (this bug prevented creation of high-resolution meshes when they were absent in the first place.)
+
+- Fix writing of raw files with empty set of annotations by `Jaakko Leppakangas`_
+
+- Fix bug in :meth:`mne.preprocessing.ICA.plot_properties` where merging gradiometers would fail by `Jaakko Leppakangas`_
+
+- Fix :func:`mne.viz.plot_sensors` to maintain proper aspect ratio by `Eric Larson`_
+
+- Fix :func:`mne.viz.plot_topomap` to allow 0 contours by `Jaakko Leppakangas`_
+
+- Fix :class:`mne.preprocessing.ICA` source-picking to increase threshold for rank estimation to 1e-14 by `Jesper Duemose Nielsen`_
+
+- Fix :func:`mne.set_bipolar_reference` to support duplicates in anodes by `Jean-Baptiste Schiratti`_ and `Alex Gramfort`_
+
+- Fix visuals of :func:`mne.viz.plot_evoked` and a bug where ylim changes when using interactive topomap plotting by `Jaakko Leppakangas`_
+
+- Fix :meth:`mne.Evoked.plot_topomap` when using the ``mask`` argument with paired gradiometers by `Eric Larson`_
+
+- Fix bug in :meth:`mne.Label.fill` where an empty label raised an error, by `Eric Larson`_
+
+- Fix :func:`mne.io.read_raw_ctf` to also include the samples in the last block by `Jaakko Leppakangas`_
+
+- Fix :meth:`mne.preprocessing.ICA.save` to close file before attempting to delete it when write fails by `Jesper Duemose Nielsen`_
+
+- Fix :func:`mne.simulation.simulate_evoked` to use nave parameter instead of snr, by `Yousra Bekhti`_
+
+- Fix :func:`mne.read_bem_surfaces` for BEM files missing normals by `Christian Brodbeck`_
+
+- Fix :func:`mne.transform_surface_to` to actually copy when ``copy=True`` by `Eric Larson`_
+
+- Fix :func:`mne.io.read_raw_brainvision` to read vectorized data correctly by `Jaakko Leppakangas`_ and `Phillip Alday`_
+
+- Fix :func:`mne.connectivity.spectral_connectivity` so that if ``n_jobs > 1`` it does not ignore last ``n_epochs % n_jobs`` epochs by `Mikołaj Magnuski`_
+
+- Fix :func:`mne.io.read_raw_edf` to infer sampling rate correctly when reading EDF+ files where tal-channel has a higher sampling frequency by `Jaakko Leppakangas`_
+
+- Fix default value of ``kind='topomap'`` in :meth:`mne.channels.Montage.plot` to be consistent with :func:`mne.viz.plot_montage` by `Clemens Brunner`_
+
+- Fix bug in :meth:`to_data_frame <mne.io.Raw.to_data_frame>` where non-consecutive picks would make the function crash by `Jaakko Leppakangas`_
+
+- Fix channel picking and drop in :class:`mne.time_frequency.EpochsTFR` by `Lukáš Hejtmánek`_
+
+- Fix :func:`mne.SourceEstimate.transform` to properly update :attr:`mne.SourceEstimate.times` by `Marijn van Vliet`_
+
+- Fix :func:`mne.viz.plot_evoked_joint` to allow custom titles without appending information about the channels by `Jaakko Leppakangas`_
+
+- Fix writing a forward solution after being processed by :func:`mne.forward.restrict_forward_to_label` or :func:`mne.forward.restrict_forward_to_stc` by `Marijn van Vliet`_
+
+- Fix bug in :func:`mne.viz.plot_compare_evokeds` where ``truncate_yaxis`` was ignored (default is now ``False``), by `Jona Sassenhagen`_
+
+- Fix bug in :func:`mne.viz.plot_evoked` where all xlabels were removed when using ``spatial_colors=True``, by `Jesper Duemose Nielsen`_
+
+- Fix field mapping :func:`mne.make_field_map` and MEG bad channel interpolation functions (e.g., :meth:`mne.Evoked.interpolate_bads`) to choose a better number of components during pseudoinversion when few channels are available, by `Eric Larson`_
+
+- Fix bug in :func:`mne.io.read_raw_brainvision`, changed default to read coordinate information if available and added test, by `Jesper Duemose Nielsen`_
+
+- Fix bug in :meth:`mne.SourceEstimate.to_original_src` where morphing failed if two vertices map to the same target vertex, by `Marijn van Vliet`_
+
+- Fix :class:`mne.preprocessing.Xdawn` to give verbose error messages about rank deficiency and handle transforming :class:`mne.Evoked`, by `Eric Larson`_
+
+- Fix bug in DC and Nyquist frequency multitaper PSD computations, e.g. in :func:`mne.time_frequency.psd_multitaper`, by `Eric Larson`_
+
+- Fix default padding type for :meth:`mne.Epochs.resample` and :meth:`mne.Evoked.resample` to be ``'edge'`` by default, by `Eric Larson`_
+
+- Fix :func:`mne.inverse_sparse.mixed_norm`, :func:`mne.inverse_sparse.tf_mixed_norm` and :func:`mne.inverse_sparse.gamma_map` to work with volume source space and sphere head models in MEG by `Alex Gramfort`_ and `Yousra Bekhti`_
+
+- Fix :meth:`mne.Evoked.as_type` channel renaming to append ``'_v'`` instead of ``'_virtual'`` to channel names to comply with shorter naming (15 char) requirements, by `Eric Larson`_
+
+- Fix treatment of CTF HPI coils as fiducial points in :func:`mne.gui.coregistration` by `Eric Larson`_
+
+- Fix resampling of events along with raw in :func:`mne.io.Raw` to now take into consideration the value of ``first_samp`` by `Chris Bailey`_
+
+- Fix labels of PSD plots in :func:`mne.viz.plot_raw_psd` by `Alejandro Weinstein`_
+
+- Fix depth weighting of sparse solvers (:func:`mne.inverse_sparse.mixed_norm`, :func:`mne.inverse_sparse.tf_mixed_norm` and :func:`mne.inverse_sparse.gamma_map`) with free orientation source spaces to improve orientation estimation by `Alex Gramfort`_ and `Yousra Bekhti`_
+
+- Fix the threshold in :func:`mne.beamformer.rap_music` to properly estimate the rank by `Yousra Bekhti`_
+
+- Fix treatment of vector inverse in :func:`mne.minimum_norm.apply_inverse_epochs` by `Emily Stephen`_
+
+- Fix :func:`mne.find_events` when passing a list as stim_channel parameter by `Alex Gramfort`_
+
+    - Fix parallel processing when computing covariance with shrinkage estimators by `Denis Engemann`_
+
+API
+~~~
+- Add ``skip_by_annotation`` to :meth:`mne.io.Raw.filter` to process data concatenated with e.g. :func:`mne.concatenate_raws` separately. This parameter will default to the old behavior (treating all data as a single block) in 0.15 but will change to ``skip_by_annotation='edge'``, which will separately filter the concatenated chunks separately, in 0.16. This should help prevent potential problems with filter-induced ringing in concatenated files, by `Eric Larson`_
+
+- ICA channel names have now been reformatted to start from zero, e.g. ``"ICA000"``, to match indexing schemes in :class:`mne.preprocessing.ICA` and related functions, by `Stefan Repplinger`_ and `Eric Larson`_
+
+- Add :func:`mne.beamformer.make_lcmv` and :func:`mne.beamformer.apply_lcmv`, :func:`mne.beamformer.apply_lcmv_epochs`, and :func:`mne.beamformer.apply_lcmv_raw` to enable the separate computation and application of LCMV beamformer weights by `Britta Westner`_, `Alex Gramfort`_, and `Denis Engemann`_.
+
+- Add ``weight_norm`` parameter to enable both unit-noise-gain beamformer and neural activity index (weight normalization) and make whitening optional by allowing ``noise_cov=None`` in :func:`mne.beamformer.lcmv`, :func:`mne.beamformer.lcmv_epochs`, and :func:`mne.beamformer.lcmv_raw`, by `Britta Westner`_, `Alex Gramfort`_, and `Denis Engemann`_.
+
+- Add new filtering mode ``fir_design='firwin'`` (default in the next 0.16 release) that gets improved attenuation using fewer samples compared to ``fir_design='firwin2'`` (default in the current 0.15 release) by `Eric Larson`_
+
+- Make the goodness of fit (GOF) of the dipoles returned by :func:`mne.beamformer.rap_music` consistent with the GOF of dipoles returned by :func:`mne.fit_dipole` by `Alex Gramfort`_.
+
+- :class:`mne.decoding.SlidingEstimator` will now replace ``mne.decoding.TimeDecoding`` to make it generic and fully compatible with scikit-learn, by `Jean-Remi King`_ and `Alex Gramfort`_
+
+- :class:`mne.decoding.GeneralizingEstimator` will now replace ``mne.decoding.GeneralizationAcrossTime`` to make it generic and fully compatible with scikit-learn, by `Jean-Remi King`_ and `Alex Gramfort`_
+
+- ``mne.viz.decoding.plot_gat_times``, ``mne.viz.decoding.plot_gat_matrix`` are now deprecated. Use matplotlib instead as shown in the examples, by `Jean-Remi King`_ and `Alex Gramfort`_
+
+- Add ``norm_trace`` parameter to control single-epoch covariance normalization in :class:mne.decoding.CSP, by `Jean-Remi King`_
+
+- Allow passing a list of channel names as ``show_names`` in function  :func:`mne.viz.plot_sensors` and methods :meth:`mne.Evoked.plot_sensors`, :meth:`mne.Epochs.plot_sensors` and :meth:`mne.io.Raw.plot_sensors` to show only a subset of channel names by `Jaakko Leppakangas`_
+
+- Make function `mne.io.eeglab.read_events_eeglab` public to allow loading overlapping events from EEGLAB files, by `Jona Sassenhagen`_.
+
+- :func:`mne.find_events` ``mask_type`` parameter will change from ``'not_and'`` to ``'and'`` in 0.16.
+
+- Instead of raising an error, duplicate channel names in the data file are now appended with a running number by `Jaakko Leppakangas`_
+
+- :func:`mne.io.read_raw_edf` has now ``'auto'`` option for ``stim_channel`` (default in version 0.16) that automatically detects if EDF annotations or GDF events exist in the header and constructs the stim channel based on these events by `Jaakko Leppakangas`_
+
+- :meth:`mne.io.Raw.plot_psd` now rejects data annotated bad by default. Turn off with ``reject_by_annotation=False``, by `Eric Larson`_
+
+- :func:`mne.set_eeg_reference` and the related methods (e.g., :meth:`mne.io.Raw.set_eeg_reference`) have a new argument ``projection``, which if set to False directly applies an average reference instead of adding an SSP projector, by `Clemens Brunner`_
+
+- Deprecate ``plot_trans`` in favor of :func:`mne.viz.plot_alignment` and add ``bem`` parameter for plotting conductor model by `Jaakko Leppakangas`_
+
+- :func:`mne.beamformer.tf_lcmv` now has a ``raw`` parameter to accommodate epochs objects that already have data loaded with ``preload=True``, with :meth:`mne.Epochs.load_data`, or that are read from disk, by `Eric Larson`_
+
+- :func:`mne.time_frequency.psd_welch` and :func:`mne.time_frequency.psd_array_welch` now use a Hamming window (instead of a Hann window) by `Clemens Brunner`_
+
+- ``picks`` parameter in :func:`mne.beamformer.lcmv`, :func:`mne.beamformer.lcmv_epochs`, :func:`mne.beamformer.lcmv_raw`, :func:`mne.beamformer.tf_lcmv` and :func:`mne.beamformer.rap_music` is now deprecated and will be removed in 0.16, by `Britta Westner`_, `Alex Gramfort`_, and `Denis Engemann`_.
+
+- The keyword argument ``frequencies`` has been deprecated in favor of ``freqs`` in various time-frequency functions, e.g. :func:`mne.time_frequency.tfr_array_morlet`, by `Eric Larson`_
+
+- Add ``patterns=False`` parameter in :class:`mne.decoding.ReceptiveField`. Turn on to compute inverse model coefficients, by `Nicolas Barascud`_
+
+- The ``scale``, ``scale_time``, and ``unit`` parameters have been deprecated in favor of ``scalings``, ``scalings_time``, and ``units`` in :func:`mne.viz.plot_evoked_topomap` and related functions, by `Eric Larson`_
+
+- ``loose`` parameter in inverse solvers has now a default value ``'auto'`` depending if the source space is a surface, volume, or discrete type by `Alex Gramfort`_ and `Yousra Bekhti`_
+
+- The behavior of ``'mean_flip'`` label-flipping in :meth:`mne.extract_label_time_course` and related functions has been changed such that the flip, instead of having arbitrary sign, maximally aligns in the positive direction of the normals of the label, by `Eric Larson`_
+
+- Deprecate force_fixed and surf_ori in :func:`mne.read_forward_solution` by `Daniel Strohmeier`_
+
+- :func:`mne.convert_forward_solution` has a new argument ``use_cps``, which controls wether information on cortical patch statistics is applied while generating surface-oriented forward solutions with free and fixed orientation by `Daniel Strohmeier`_
+
+- :func:`mne.write_forward_solution` writes a forward solution as a forward solution with free orientation in X/Y/Z RAS coordinates if it is derived from a forward solution with free orientation and as a forward solution with fixed orientation in surface-based local coordinates otherwise by `Daniel Strohmeier`_
+
+- ``loose=None`` in inverse solvers is deprecated, use explicitly ``loose=0`` for fixed constraint and ``loose=1.0`` for free orientations by `Eric Larson`_
+
+- Zero-channel-value in PSD calculation in :func:`mne.viz.plot_raw_psd` has been relaxed from error to warning by `Alejandro Weinstein`_
+
+- Expose "rank" parameter in :func:`mne.viz.evoked.plot_evoked_white` to correct rank estimates on the spot during visualization by `Denis Engemann`_, `Eric Larson`_, `Alex Gramfort`_.
+
+Authors
+~~~~~~~
+
+People who contributed to this release  (in alphabetical order):
+
+* akshay0724
+* Alejandro Weinstein
+* Alexander Rudiuk
+* Alexandre Barachant
+* Alexandre Gramfort
+* Andrew Dykstra
+* Britta Westner
+* Chris Bailey
+* Chris Holdgraf
+* Christian Brodbeck
+* Christopher Holdgraf
+* Clemens Brunner
+* Cristóbal Moënne-Loccoz
+* Daniel McCloy
+* Daniel Strohmeier
+* Denis A. Engemann
+* Emily P. Stephen
+* Eric Larson
+* Fede Raimondo
+* Jaakko Leppakangas
+* Jean-Baptiste Schiratti
+* Jean-Remi King
+* Jesper Duemose Nielsen
+* Joan Massich
+* Jon Houck
+* Jona Sassenhagen
+* Jussi Nurminen
+* Laetitia Grabot
+* Laura Gwilliams
+* Luke Bloy
+* Lukáš Hejtmánek
+* Mainak Jas
+* Marijn van Vliet
+* Mathurin Massias
+* Matt Boggess
+* Mikolaj Magnuski
+* Nicolas Barascud
+* Nicole Proulx
+* Phillip Alday
+* Ramonapariciog Apariciogarcia
+* Robin Tibor Schirrmeister
+* Rodrigo Hübner
+* S. M. Gutstein
+* Simon Kern
+* Teon Brooks
+* Yousra Bekhti
+
+.. _changes_0_14:
+
+Version 0.14
+------------
+
+Changelog
+~~~~~~~~~
+
+- Add example of time-frequency decoding with CSP by `Laura Gwilliams`_
+
+- Automatically create a legend in :func:`mne.viz.plot_evoked_topo` by `Jussi Nurminen`_
+
+- Add I/O support for Artemis123 infant/toddler MEG data by `Luke Bloy`_
+
+- Add filter plotting functions :func:`mne.viz.plot_filter` and :func:`mne.viz.plot_ideal_filter` as well as filter creation function :func:`mne.filter.create_filter` by `Eric Larson`_
+
+- Add HCP-MMP1.0 parcellation dataset downloader by `Eric Larson`_
+
+- Add option to project EEG electrodes onto the scalp in ``mne.viz.plot_trans`` by `Eric Larson`_
+
+- Add option to plot individual sensors in :meth:`mne.io.Raw.plot_psd` by `Alex Gramfort`_ and `Eric Larson`_
+
+- Add option to plot ECoG electrodes in ``mne.viz.plot_trans`` by `Eric Larson`_
+
+- Add convenient default values to :meth:`mne.io.Raw.apply_hilbert` and :meth:`mne.io.Raw.apply_function` by `Denis Engemann`_
+
+- Remove MNE-C requirement for :ref:`mne make_scalp_surfaces <gen_mne_make_scalp_surfaces>` by `Eric Larson`_
+
+- Add support for FastTrack Polhemus ``.mat`` file outputs in ``hsp`` argument of :func:`mne.channels.read_dig_montage` by `Eric Larson`_
+
+- Add option to convert 3d electrode plots to a snapshot with 2d electrode positions with :func:`mne.viz.snapshot_brain_montage` by `Chris Holdgraf`_
+
+- Add skull surface plotting option to ``mne.viz.plot_trans`` by `Jaakko Leppakangas`_
+
+- Add minimum-phase filtering option in :meth:`mne.io.Raw.filter` by `Eric Larson`_
+
+- Add support for reading ASCII BrainVision files in :func:`mne.io.read_raw_brainvision` by `Eric Larson`_
+
+- Add method of ICA objects for retrieving the component maps :meth:`mne.preprocessing.ICA.get_components` by `Jona Sassenhagen`_
+
+- Add option to plot events in :func:`mne.viz.plot_epochs` by `Jaakko Leppakangas`_
+
+- Add dipole definitions for older phantom at Otaniemi in :func:`mne.dipole.get_phantom_dipoles` by `Eric Larson`_
+
+- Add spatial colors option for :func:`mne.viz.plot_raw_psd` by `Jaakko Leppakangas`_
+
+- Add functions like :func:`get_volume_labels_from_src` to handle mixed source spaces by `Annalisa Pascarella`_
+
+- Add convenience function for opening MNE documentation :func:`open_docs` by `Eric Larson`_
+
+- Add option in :meth:`mne.io.Raw.plot` to display the time axis relative to ``raw.first_samp`` by `Mainak Jas`_
+
+- Add new :mod:`mne.datasets.visual_92_categories <mne.datasets.visual_92_categories.data_path>` dataset by `Jaakko Leppakangas`_
+
+- Add option in :func:`mne.io.read_raw_edf` to allow channel exclusion by `Jaakko Leppakangas`_
+
+- Allow integer event codes in :func:`mne.read_epochs_eeglab` by `Jaakko Leppakangas`_
+
+- Add ability to match channel names in a case insensitive manner when applying a :class:`mne.channels.Montage` by `Marijn van Vliet`_
+
+- Add ``yscale`` keyword argument to :meth:`mne.time_frequency.AverageTFR.plot` that allows specifying whether to present the frequency axis in linear (``'linear'``) or log (``'log'``) scale. The default value is ``'auto'`` which detects whether frequencies are log-spaced and sets yscale to log. Added by `Mikołaj Magnuski`_
+
+- Add :ref:`Representational Similarity Analysis (RSA) <rsa_noplot>` example on :mod:`mne.datasets.visual_92_categories.data_path` dataset by `Jaakko Leppakangas`_, `Jean-Remi King`_ and `Alex Gramfort`_
+
+- Add support for NeuroScan files with event type 3 in :func:`mne.io.read_raw_cnt` by `Marijn van Vliet`_
+
+- Add interactive annotation mode to :meth:`mne.io.Raw.plot` (accessed by pressing 'a') by `Jaakko Leppakangas`_
+
+- Add support for deleting all projectors or a list of indices in :meth:`mne.io.Raw.del_proj` by `Eric Larson`_
+
+- Add source space plotting with :meth:`mne.SourceSpaces.plot` using ``mne.viz.plot_trans`` by `Eric Larson`_
+
+- Add :func:`mne.decoding.get_coef` to retrieve and inverse the coefficients of a linear model - typically a spatial filter or pattern, by `Jean-Remi King`_
+
+- Add support for reading in EGI MFF digitization coordinate files in :func:`mne.channels.read_dig_montage` by `Matt Boggess`_
+
+- Add ``n_per_seg`` keyword argument to :func:`mne.time_frequency.psd_welch` and :func:`mne.time_frequency.psd_array_welch` that allows to control segment length independently of ``n_fft`` and use zero-padding when ``n_fft > n_per_seg`` by `Mikołaj Magnuski`_
+
+- Add annotation aware data getter :meth:`mne.io.Raw.get_data` by `Jaakko Leppakangas`_
+
+- Add support of dipole location visualization with MRI slice overlay with matplotlib to :func:`mne.viz.plot_dipole_locations` via mode='orthoview' parameter by `Jaakko Leppakangas`_ and `Alex Gramfort`_
+
+- Add plotting of head positions as a function of time in :func:`mne.viz.plot_head_positions` by `Eric Larson`_
+
+- Add ``real_filter`` option to :func:`mne.beamformer.dics`, :func:`mne.beamformer.dics_source_power`, :func:`mne.beamformer.tf_dics` and :func:`mne.beamformer.dics_epochs` by `Eric Larson`_, `Alex Gramfort`_ and `Andrea Brovelli`_.
+
+- Add a demo script showing how to use a custom inverse solver with MNE by `Alex Gramfort`_
+
+- Functions :func:`mne.preprocessing.create_ecg_epochs`, :func:`mne.preprocessing.create_eog_epochs`, :func:`mne.compute_raw_covariance` and ICA methods :meth:`mne.preprocessing.ICA.score_sources`, :meth:`mne.preprocessing.ICA.find_bads_ecg`, :meth:`mne.preprocessing.ICA.find_bads_eog` are now annotation aware by `Jaakko Leppakangas`_
+
+- Allow using ``spatial_colors`` for non-standard layouts by creating custom layouts from channel locations and add ``to_sphere`` keyword to :func:`mne.viz.plot_sensors` to allow plotting sensors that are not on the head surface by `Jaakko Leppakangas`_
+
+- Concatenating raws with :func:`mne.concatenate_raws` now creates boundary annotations automatically by `Jaakko Leppakangas`_
+
+- :func:`mne.viz.plot_projs_topomap` now supports plotting EEG topomaps by passing in :class:`mne.Info` by `Eric Larson`_
+
+BUG
+~~~
+
+- Fix bug with DICS and LCMV (e.g., :func:`mne.beamformer.lcmv`, :func:`mne.beamformer.dics`) where regularization was done improperly. The default ``reg=0.01`` has been changed to ``reg=0.05``, by `Andrea Brovelli`_, `Alex Gramfort`_, and `Eric Larson`_
+
+- Fix callback function call in ``mne.viz.topo._plot_topo_onpick`` by `Erkka Heinila`_
+
+- Fix reading multi-file CTF recordings in :func:`mne.io.read_raw_ctf` by `Niklas Wilming`_
+
+- Fix computation of AR coefficients across channels in :func:`mne.time_frequency.fit_iir_model_raw` by `Eric Larson`_
+
+- Fix maxfilter channel names extra space bug in :func:`mne.preprocessing.maxwell_filter` by `Sheraz Khan`_
+
+- :func:`mne.channels.find_layout` now leaves out the excluded channels by `Jaakko Leppakangas`_
+
+- Array data constructors :class:`mne.io.RawArray` and :class:`EvokedArray` now make a copy of the info structure by `Jaakko Leppakangas`_
+
+- Fix bug with finding layouts in :func:`mne.viz.plot_projs_topomap` by `Eric Larson`_
+
+- Fix bug :func:`mne.io.anonymize_info` when Info does not contain 'file_id' or 'meas_id' fields by `Jean-Remi King`_
+
+- Fix colormap selection in :func:`mne.viz.plot_evoked_topomap` when using positive vmin with negative data by `Jaakko Leppakangas`_
+
+- Fix channel name comparison in :func:`mne.channels.read_montage` so that if ``ch_names`` is provided, the returned montage will have channel names in the same letter case by `Jaakko Leppakangas`_
+
+- Fix :meth:`inst.set_montage(montage) <mne.io.Raw.set_montage>` to only set ``inst.info['dev_head_t']`` if ``dev_head_t=True`` in :func:`mne.channels.read_dig_montage` by `Eric Larson`_
+
+- Fix handling of events in :class:`mne.realtime.RtEpochs` when the triggers were split between two buffers resulting in missing and/or duplicate epochs by `Mainak Jas`_ and `Antti Rantala`_
+
+- Fix bug with automatic decimation in :func:`mne.io.read_raw_kit` by `Keith Doelling`_
+
+- Fix bug with :func:`setup_volume_source_space` where arguments ``subject`` and ``subjects_dir`` were ignored by `Jaakko Leppakangas`_
+
+- Fix sanity check for incompatible ``threshold`` and ``tail`` values in clustering functions like :func:`mne.stats.spatio_temporal_cluster_1samp_test` by `Eric Larson`_
+
+- Fix ``_bad_dropped`` not being set when loading eeglab epoched files via :func:`mne.read_epochs_eeglab` which resulted in :func:`len` not working by `Mikołaj Magnuski`_
+
+- Fix a bug in :meth:`mne.time_frequency.AverageTFR.plot` when plotting without a colorbar by `Jaakko Leppakangas`_
+
+- Fix ``_filenames`` attribute in creation of :class:`mne.io.RawArray` with :meth:`mne.preprocessing.ICA.get_sources` by `Paul Pasler`_
+
+- Fix contour levels in :func:`mne.viz.plot_evoked_topomap` to be uniform across topomaps by `Jaakko Leppakangas`_
+
+- Fix bug in :func:`mne.preprocessing.maxwell_filter` where fine calibration indices were mismatched leading to an ``AssertionError`` by `Eric Larson`_
+
+- Fix bug in :func:`mne.preprocessing.fix_stim_artifact` where non-data channels were interpolated by `Eric Larson`_
+
+- :class:`mne.decoding.Scaler` now scales each channel independently using data from all time points (epochs and times) instead of scaling all channels for each time point. It also now accepts parameter ``scalings`` to determine the data scaling method (default is ``None`` to use static channel-type-based scaling), by `Asish Panda`_, `Jean-Remi King`_, and `Eric Larson`_
+
+- Raise error if the cv parameter of ``mne.decoding.GeneralizationAcrossTime`` and ``mne.decoding.TimeDecoding`` is not a partition and the predict_mode is "cross-validation" by `Jean-Remi King`_
+
+- Fix bug in :func:`mne.io.read_raw_edf` when ``preload=False`` and channels have different sampling rates by `Jaakko Leppakangas`_
+
+- Fix :func:`mne.read_labels_from_annot` to set ``label.values[:]=1`` rather than 0 for consistency with the :class:`Label` class by `Jon Houck`_
+
+- Fix plotting non-uniform freqs (for example log-spaced) in :meth:`mne.time_frequency.AverageTFR.plot` by `Mikołaj Magnuski`_
+
+- Fix :func:`mne.minimum_norm.compute_source_psd` when used with ``pick_ori=None`` by `Annalisa Pascarella`_ and `Alex Gramfort`_
+
+- Fix bug in :class:`mne.Annotations` where concatenating two raws where ``orig_time`` of the second run is ``None`` by `Jaakko Leppakangas`_
+
+- Fix reading channel location from eeglab ``.set`` files when some of the channels do not provide this information. Previously all channel locations were ignored in such case, now they are read - unless a montage is provided by the user in which case only channel names are read from set file. By `Mikołaj Magnuski`_
+
+- Fix reading eeglab ``.set`` files when ``.chanlocs`` structure does not contain ``X``, ``Y`` or ``Z`` fields by `Mikołaj Magnuski`_
+
+- Fix bug with :func:`mne.simulation.simulate_raw` when ``interp != 'zero'`` by `Eric Larson`_
+
+- Fix :func:`mne.fit_dipole` to handle sphere model rank deficiency properly by `Alex Gramfort`_
+
+- Raise error in :func:`mne.concatenate_epochs` when concatenated epochs have conflicting event_id by `Mikołaj Magnuski`_
+
+- Fix handling of ``n_components=None`` in :class:`mne.preprocessing.ICA` by `Richard Höchenberger`_
+
+- Fix reading of fiducials correctly from CTF data in :func:`mne.io.read_raw_ctf` by `Jaakko Leppakangas`_
+
+- Fix :func:`mne.beamformer.rap_music` to return dipoles with amplitudes in Am instead of nAm by `Jaakko Leppakangas`_
+
+- Fix computation of duality gap in ``mne.inverse_sparse.mxne_optim.dgap_l21`` by `Mathurin Massias`_
+
+API
+~~~
+
+- The filtering functions ``band_pass_filter``, ``band_stop_filter``, ``low_pass_filter``, and ``high_pass_filter`` have been deprecated in favor of :func:`mne.filter.filter_data` by `Eric Larson`_
+
+- :class:`EvokedArray` now has default value ``tmin=0.`` by `Jaakko Leppakangas`_
+
+- The ``ch_type`` argument for ``mne.viz.plot_trans`` has been deprecated, use ``eeg_sensors`` and ``meg_sensors`` instead, by `Eric Larson`_
+
+- The default ``tmax=60.`` in :meth:`mne.io.Raw.plot_psd` will change to ``tmax=np.inf`` in 0.15, by `Eric Larson`_
+
+- Base classes :class:`mne.io.BaseRaw` and :class:`mne.BaseEpochs` are now public to allow easier typechecking, by `Daniel McCloy`_
+
+- :func:`mne.io.read_raw_edf` now combines triggers from multiple tal channels to 'STI 014' by `Jaakko Leppakangas`_
+
+- The measurement info :class:`Info` no longer contains a potentially misleading ``info['filename']`` entry. Use class properties like :attr:`mne.io.Raw.filenames` or :attr:`mne.Epochs.filename` instead by `Eric Larson`_
+
+- Default fiducial name change from 'nz' to 'nasion' in :func:`mne.channels.read_montage`, so that it is the same for both :class: `mne.channels.Montage` and :class: `mne.channels.DigMontage` by `Leonardo Barbosa`_
+
+- MNE's additional files for the ``fsaverage`` head/brain model are now included in MNE-Python, and the now superfluous ``mne_root`` parameter to  :func:`create_default_subject` has been deprecated by `Christian Brodbeck`_
+
+- An ``overwrite=False`` default parameter has been added to :func:`write_source_spaces` to protect against accidental overwrites, by `Eric Larson`_
+
+- The :class:`mne.decoding.LinearModel` class will no longer support `plot_filters` and `plot_patterns`, use :class:`mne.EvokedArray` with :func:`mne.decoding.get_coef` instead, by `Jean-Remi King`_
+
+- Made functions :func:`mne.time_frequency.tfr_array_multitaper`, :func:`mne.time_frequency.tfr_array_morlet`, :func:`mne.time_frequency.tfr_array_stockwell`, :func:`mne.time_frequency.psd_array_multitaper` and :func:`mne.time_frequency.psd_array_welch` public to allow computing TFRs and PSDs on numpy arrays by `Jaakko Leppakangas`_
+
+- :meth:`mne.preprocessing.ICA.fit` now rejects data annotated bad by default. Turn off with ``reject_by_annotation=False``, by `Jaakko Leppakangas`_
+
+- :func:`mne.io.read_raw_egi` now names channels with pattern 'E<idx>'. This behavior can be changed with parameter ``channel_naming`` by `Jaakko Leppakangas`_
+
+- the `name`` parameter in :class:`mne.Epochs` is deprecated, by `Jaakko Leppakangas`_
+
+Authors
+~~~~~~~
+
+People who contributed to this release  (in alphabetical order):
+
+* Alexander Rudiuk
+* Alexandre Gramfort
+* Annalisa Pascarella
+* Antti Rantala
+* Asish Panda
+* Burkhard Maess
+* Chris Holdgraf
+* Christian Brodbeck
+* Cristóbal Moënne-Loccoz
+* Daniel McCloy
+* Denis A. Engemann
+* Eric Larson
+* Erkka Heinila
+* Hermann Sonntag
+* Jaakko Leppakangas
+* Jakub Kaczmarzyk
+* Jean-Remi King
+* Jon Houck
+* Jona Sassenhagen
+* Jussi Nurminen
+* Keith Doelling
+* Leonardo S. Barbosa
+* Lorenz Esch
+* Lorenzo Alfine
+* Luke Bloy
+* Mainak Jas
+* Marijn van Vliet
+* Matt Boggess
+* Matteo Visconti
+* Mikolaj Magnuski
+* Niklas Wilming
+* Paul Pasler
+* Richard Höchenberger
+* Sheraz Khan
+* Stefan Repplinger
+* Teon Brooks
+* Yaroslav Halchenko
+
 .. _changes_0_13:
 
 Version 0.13
@@ -17,240 +618,246 @@ Version 0.13
 Changelog
 ~~~~~~~~~
 
-    - Add new class :class:`AcqParserFIF` to parse Elekta/Neuromag MEG acquisition info, allowing e.g. collecting epochs according to acquisition-defined averaging categories by `Jussi Nurminen`_
+- Add new class :class:`AcqParserFIF` to parse Elekta/Neuromag MEG acquisition info, allowing e.g. collecting epochs according to acquisition-defined averaging categories by `Jussi Nurminen`_
+
+- Adds automatic determination of FIR filter parameters ``filter_length``, ``l_trans_bandwidth``, and ``h_trans_bandwidth`` and adds ``phase`` argument in e.g. in :meth:`mne.io.Raw.filter` by `Eric Larson`_
+
+- Adds faster ``n_fft='auto'`` option to :meth:`mne.io.Raw.apply_hilbert` by `Eric Larson`_
 
-    - Adds automatic determination of FIR filter parameters ``filter_length``, ``l_trans_bandwidth``, and ``h_trans_bandwidth`` and adds ``phase`` argument in e.g. in :meth:`mne.io.Raw.filter` by `Eric Larson`_
+- Adds new function :func:`mne.time_frequency.csd_array` to compute the cross-spectral density of multivariate signals stored in an array, by `Nick Foti`_
 
-    - Adds faster ``n_fft='auto'`` option to :meth:`mne.io.Raw.apply_hilbert` by `Eric Larson`_
+- Add order params 'selection' and 'position' for :func:`mne.viz.plot_raw` to allow plotting of specific brain regions by `Jaakko Leppakangas`_
 
-    - Adds new function :func:`mne.time_frequency.csd_array` to compute the cross-spectral density of multivariate signals stored in an array, by `Nick Foti`_
+- Added the ability to decimate :class:`mne.Evoked` objects with :func:`mne.Evoked.decimate` by `Eric Larson`_
 
-    - Add order params 'selection' and 'position' for :func:`mne.viz.plot_raw` to allow plotting of specific brain regions by `Jaakko Leppakangas`_
+- Add generic array-filtering function :func:`mne.filter.filter_data` by `Eric Larson`_
 
-    - Added the ability to decimate :class:`mne.Evoked` objects with :func:`mne.Evoked.decimate` by `Eric Larson`_
+- ``mne.viz.plot_trans`` now also shows head position indicators by `Christian Brodbeck`_
 
-    - Add generic array-filtering function :func:`mne.filter.filter_data` by `Eric Larson`_
+- Add label center of mass function :func:`mne.Label.center_of_mass` by `Eric Larson`_
 
-    - :func:`mne.viz.plot_trans` now also shows head position indicators by `Christian Brodbeck`_
+- Added :func:`mne.viz.plot_ica_properties` that allows plotting of independent component properties similar to ``pop_prop`` in EEGLAB. Also :class:`mne.preprocessing.ICA` has :func:`mne.preprocessing.ICA.plot_properties` method now. Added by `Mikołaj Magnuski`_
 
-    - Add label center of mass function :func:`mne.Label.center_of_mass` by `Eric Larson`_
+- Add second-order sections (instead of ``(b, a)`` form) IIR filtering for reduced numerical error by `Eric Larson`_
 
-    - Added :func:`mne.viz.plot_ica_properties` that allows plotting of independent component properties similar to ``pop_prop`` in EEGLAB. Also :class:`mne.preprocessing.ICA` has :func:`mne.preprocessing.ICA.plot_properties` method now. Added by `Mikołaj Magnuski`_
+- Add interactive colormap option to image plotting functions by `Jaakko Leppakangas`_
 
-    - Add second-order sections (instead of ``(b, a)`` form) IIR filtering for reduced numerical error by `Eric Larson`_
+- Add support for the University of Maryland KIT system by `Christian Brodbeck`_
 
-    - Add interactive colormap option to image plotting functions by `Jaakko Leppakangas`_
+- Add support for \*.elp and \*.hsp files to the KIT2FIFF converter and :func:`mne.channels.read_dig_montage` by `Teon Brooks`_ and `Christian Brodbeck`_
 
-    - Add support for the University of Maryland KIT system by `Christian Brodbeck`_
+- Add option to preview events in the KIT2FIFF GUI by `Christian Brodbeck`_
 
-    - Add support for \*.elp and \*.hsp files to the KIT2FIFF converter and :func:`mne.channels.read_dig_montage` by `Teon Brooks`_ and `Christian Brodbeck`_
+- Add approximation of size of :class:`io.Raw`, :class:`Epochs`, and :class:`Evoked` in :func:`repr` by `Eric Larson`_
 
-    - Add option to preview events in the KIT2FIFF GUI by `Christian Brodbeck`_
+- Add possibility to select a subset of sensors by lasso selector to :func:`mne.viz.plot_sensors` and :func:`mne.viz.plot_raw` when using order='selection' or order='position' by `Jaakko Leppakangas`_
 
-    - Add approximation of size of :class:`io.Raw`, :class:`Epochs`, and :class:`Evoked` in :func:`repr` by `Eric Larson`_
+- Add the option to plot brain surfaces and source spaces to :func:`viz.plot_bem` by `Christian Brodbeck`_
 
-    - Add possibility to select a subset of sensors by lasso selector to :func:`mne.viz.plot_sensors` and :func:`mne.viz.plot_raw` when using order='selection' or order='position' by `Jaakko Leppakangas`_
+- Add the ``--filterchpi`` option to :ref:`mne browse_raw <gen_mne_browse_raw>`, by `Felix Raimundo`_
 
-    - Add the option to plot brain surfaces and source spaces to :func:`viz.plot_bem` by `Christian Brodbeck`_
+- Add the ``--no-decimate`` option to :ref:`mne make_scalp_surfaces <gen_mne_make_scalp_surfaces>` to skip the high-resolution surface decimation step, by `Eric Larson`_
 
-    - Add the ``--filterchpi`` option to :ref:`mne browse_raw <gen_mne_browse_raw>`, by `Felix Raimundo`_
+- Add new class :class:`mne.decoding.EMS` to transform epochs with the event-matched spatial filters and add 'cv' parameter to :func:`mne.decoding.compute_ems`, by `Jean-Remi King`_
 
-    - Add the ``--no-decimate`` option to :ref:`mne make_scalp_surfaces <gen_mne_make_scalp_surfaces>` to skip the high-resolution surface decimation step, by `Eric Larson`_
+- Added :class:`mne.time_frequency.EpochsTFR` and average parameter in :func:`mne.time_frequency.tfr_morlet` and :func:`mne.time_frequency.tfr_multitaper` to compute time-frequency transforms on single trial epochs without averaging, by `Jean-Remi King`_ and `Alex Gramfort`_
 
-    - Add new class :class:`mne.decoding.EMS` to transform epochs with the event-matched spatial filters and add 'cv' parameter to :func:`mne.decoding.compute_ems`, by `Jean-Remi King`_
+- Added :class:`mne.decoding.TimeFrequency` to transform signals in scikit-learn pipelines, by `Jean-Remi King`_
 
-    - Added :class:`mne.time_frequency.EpochsTFR` and average parameter in :func:`mne.time_frequency.tfr_morlet` and :func:`mne.time_frequency.tfr_multitaper` to compute time-frequency transforms on single trial epochs without averaging, by `Jean-Remi King`_ and `Alex Gramfort`_
+- Added :class:`mne.decoding.UnsupervisedSpatialFilter` providing interface for scikit-learn decomposition algorithms to be used with MNE data, by `Jean-Remi King`_ and `Asish Panda`_
 
-    - Added :class:`mne.decoding.TimeFrequency` to transform signals in scikit-learn pipelines, by `Jean-Remi King`_
+- Added support for multiclass decoding in :class:`mne.decoding.CSP`, by `Jean-Remi King`_ and `Alexandre Barachant`_
 
-    - Added :class:`mne.decoding.UnsupervisedSpatialFilter` providing interface for scikit-learn decomposition algorithms to be used with MNE data, by `Jean-Remi King`_ and `Asish Panda`_
+- Components obtained from :class:`mne.preprocessing.ICA` are now sorted by explained variance, by `Mikołaj Magnuski`_
 
-    - Added support for multiclass decoding in :class:`mne.decoding.CSP`, by `Jean-Remi King`_ and `Alexandre Barachant`_
+- Adding an EEG reference channel using :func:`mne.add_reference_channels` will now use its digitized location from the FIFF file, if present, by `Chris Bailey`_
 
-    - Components obtained from :class:`mne.preprocessing.ICA` are now sorted by explained variance, by `Mikołaj Magnuski`_
+- Added interactivity to :func:`mne.preprocessing.ICA.plot_components` - passing an instance of :class:`io.Raw` or :class:`Epochs` in ``inst`` argument allows to open component properties by clicking on component topomaps, by `Mikołaj Magnuski`_
 
-    - Adding an EEG reference channel using :func:`mne.io.add_reference_channels` will now use its digitized location from the FIFF file, if present, by `Chris Bailey`_
+- Adds new function :func:`mne.viz.plot_compare_evokeds` to show multiple evoked time courses at a single location, or the mean over a ROI, or the GFP, automatically averaging and calculating a CI if multiple subjects are given, by `Jona Sassenhagen`_
 
-    - Added interactivity to :func:`mne.preprocessing.ICA.plot_components` - passing an instance of :class:`io.Raw` or :class:`Epochs` in ``inst`` argument allows to open component properties by clicking on component topomaps, by `Mikołaj Magnuski`_
+- Added `transform_into` parameter into :class:`mne.decoding.CSP` to retrieve the average power of each source or the time course of each source, by `Jean-Remi King`_
 
-    - Adds new function :func:`mne.viz.plot_compare_evokeds` to show multiple evoked time courses at a single location, or the mean over a ROI, or the GFP, automatically averaging and calculating a CI if multiple subjects are given, by `Jona Sassenhagen`_
+- Added support for reading MaxShield (IAS) evoked data (e.g., from the acquisition machine) in :func:`mne.read_evokeds` by `Eric Larson`_
 
-    - Added `transform_into` parameter into :class:`mne.decoding.CSP` to retrieve the average power of each source or the time course of each source, by `Jean-Remi King`_
+- Added support for functional near-infrared spectroscopy (fNIRS) channels by `Jaakko Leppakangas`_
 
-    - Added support for reading MaxShield (IAS) evoked data (e.g., from the acquisition machine) in :func:`mne.read_evokeds` by `Eric Larson`_
+- Added :attr:`mne.io.Raw.acqparser` convenience attribute for :class:`mne.AcqParserFIF` by `Eric Larson`_
 
-    - Added support for functional near-infrared spectroscopy (fNIRS) channels by `Jaakko Leppakangas`_
+- Added example of Representational Similarity Analysis, by `Jean-Remi King`_
 
 BUG
 ~~~
 
-    - Fixed a bug where selecting epochs using hierarchical event IDs (HIDs) was *and*-like instead of *or*-like. When doing e.g. ``epochs[('Auditory', 'Left')]``, previously all trials that contain ``'Auditory'`` *and* ``'Left'`` (like ``'Auditory/Left'``) would be selected, but now any conditions matching ``'Auditory'`` *or* ``'Left'`` will be selected (like ``'Auditory/Left'``, ``'Auditory/Right'``, and ``'Visual/Left'``). This is now consistent with how epoch selection was done witho [...]
+- Fixed a bug where selecting epochs using hierarchical event IDs (HIDs) was *and*-like instead of *or*-like. When doing e.g. ``epochs[('Auditory', 'Left')]``, previously all trials that contain ``'Auditory'`` *and* ``'Left'`` (like ``'Auditory/Left'``) would be selected, but now any conditions matching ``'Auditory'`` *or* ``'Left'`` will be selected (like ``'Auditory/Left'``, ``'Auditory/Right'``, and ``'Visual/Left'``). This is now consistent with how epoch selection was done without H [...]
 
-    - Fixed Infomax/Extended Infomax when the user provides an initial weights matrix by `Jair Montoya Martinez`_
+- Fixed Infomax/Extended Infomax when the user provides an initial weights matrix by `Jair Montoya Martinez`_
 
-    - Fixed the default raw FIF writing buffer size to be 1 second instead of 10 seconds by `Eric Larson`_
+- Fixed the default raw FIF writing buffer size to be 1 second instead of 10 seconds by `Eric Larson`_
 
-    - Fixed channel selection order when MEG channels do not come first in :func:`mne.preprocessing.maxwell_filter` by `Eric Larson`_
+- Fixed channel selection order when MEG channels do not come first in :func:`mne.preprocessing.maxwell_filter` by `Eric Larson`_
 
-    - Fixed color ranges to correspond to the colorbar when plotting several time instances with :func:`mne.viz.plot_evoked_topomap` by `Jaakko Leppakangas`_
+- Fixed color ranges to correspond to the colorbar when plotting several time instances with :func:`mne.viz.plot_evoked_topomap` by `Jaakko Leppakangas`_
 
-    - Added units to :func:`mne.io.read_raw_brainvision` for reading non-data channels and enable default behavior of inferring channel type by unit by `Jaakko Leppakangas`_ and `Pablo-Arias`_
+- Added units to :func:`mne.io.read_raw_brainvision` for reading non-data channels and enable default behavior of inferring channel type by unit by `Jaakko Leppakangas`_ and `Pablo-Arias`_
 
-    - Fixed minor bugs with :func:`mne.Epochs.resample` and :func:`mne.Epochs.decimate` by `Eric Larson`_
+- Fixed minor bugs with :func:`mne.Epochs.resample` and :func:`mne.Epochs.decimate` by `Eric Larson`_
 
-    - Fixed a bug where duplicate vertices were not strictly checked by :func:`mne.simulation.simulate_stc` by `Eric Larson`_
+- Fixed a bug where duplicate vertices were not strictly checked by :func:`mne.simulation.simulate_stc` by `Eric Larson`_
 
-    - Fixed a bug where some FIF files could not be read with :func:`mne.io.show_fiff` by `Christian Brodbeck`_ and `Eric Larson`_
+- Fixed a bug where some FIF files could not be read with :func:`mne.io.show_fiff` by `Christian Brodbeck`_ and `Eric Larson`_
 
-    - Fixed a bug where ``merge_grads=True`` causes :func:`mne.viz.plot_evoked_topo` to fail when plotting a list of evokeds by `Jaakko Leppakangas`_
+- Fixed a bug where ``merge_grads=True`` causes :func:`mne.viz.plot_evoked_topo` to fail when plotting a list of evokeds by `Jaakko Leppakangas`_
 
-    - Fixed a bug when setting multiple bipolar references with :func:`mne.io.set_bipolar_reference` by `Marijn van Vliet`_.
+- Fixed a bug when setting multiple bipolar references with :func:`set_bipolar_reference` by `Marijn van Vliet`_.
 
-    - Fixed image scaling in :func:`mne.viz.plot_epochs_image` when plotting more than one channel by `Jaakko Leppakangas`_
+- Fixed image scaling in :func:`mne.viz.plot_epochs_image` when plotting more than one channel by `Jaakko Leppakangas`_
 
-    - Fixed :class:`mne.preprocessing.Xdawn` to fit shuffled epochs by `Jean-Remi King`_
+- Fixed :class:`mne.preprocessing.Xdawn` to fit shuffled epochs by `Jean-Remi King`_
 
-    - Fixed a bug with channel order determination that could lead to an ``AssertionError`` when using :class:`mne.Covariance` matrices by `Eric Larson`_
+- Fixed a bug with channel order determination that could lead to an ``AssertionError`` when using :class:`mne.Covariance` matrices by `Eric Larson`_
 
-    - Fixed the check for CTF gradient compensation in :func:`mne.preprocessing.maxwell_filter` by `Eric Larson`_
+- Fixed the check for CTF gradient compensation in :func:`mne.preprocessing.maxwell_filter` by `Eric Larson`_
 
-    - Fixed the import of EDF files with encoding characters in :func:`mne.io.read_raw_edf` by `Guillaume Dumas`_
+- Fixed the import of EDF files with encoding characters in :func:`mne.io.read_raw_edf` by `Guillaume Dumas`_
 
-    - Fixed :class:`mne.Epochs` to ensure that detrend parameter is not a boolean by `Jean-Remi King`_
+- Fixed :class:`mne.Epochs` to ensure that detrend parameter is not a boolean by `Jean-Remi King`_
 
-    - Fixed bug with :func:`mne.realtime.FieldTripClient.get_data_as_epoch` when ``picks=None`` which crashed the function by `Mainak Jas`_
+- Fixed bug with :func:`mne.realtime.FieldTripClient.get_data_as_epoch` when ``picks=None`` which crashed the function by `Mainak Jas`_
 
-    - Fixed reading of units in ``.elc`` montage files (from ``UnitsPosition`` field) so that :class:`mne.channels.Montage` objects are now returned with the ``pos`` attribute correctly in meters, by `Chris Mullins`_
+- Fixed reading of units in ``.elc`` montage files (from ``UnitsPosition`` field) so that :class:`mne.channels.Montage` objects are now returned with the ``pos`` attribute correctly in meters, by `Chris Mullins`_
 
-    - Fixed reading of BrainVision files by `Phillip Alday`_:
+- Fixed reading of BrainVision files by `Phillip Alday`_:
 
-        - Greater support for BVA files, especially older ones: alternate text coding schemes with fallback to Latin-1 as well as units in column headers
+- Greater support for BVA files, especially older ones: alternate text coding schemes with fallback to Latin-1 as well as units in column headers
 
-        - Use online software filter information when present
+- Use online software filter information when present
 
-        - Fix comparisons of filter settings for determining "strictest"/"weakest" filter
+- Fix comparisons of filter settings for determining "strictest"/"weakest" filter
 
-        - Weakest filter is now used for heterogeneous channel filter settings, leading to more consistent behavior with filtering methods applied to a subset of channels (e.g. ``Raw.filter`` with ``picks != None``).
+- Weakest filter is now used for heterogeneous channel filter settings, leading to more consistent behavior with filtering methods applied to a subset of channels (e.g. ``Raw.filter`` with ``picks != None``).
 
-    - Fixed plotting and timing of :class:`Annotations` and restricted addition of annotations outside data range to prevent problems with cropping and concatenating data by `Jaakko Leppakangas`_
+- Fixed plotting and timing of :class:`Annotations` and restricted addition of annotations outside data range to prevent problems with cropping and concatenating data by `Jaakko Leppakangas`_
 
-    - Fixed ICA plotting functions to refer to IC index instead of component number by `Andreas Hojlund`_ and `Jaakko Leppakangas`_
+- Fixed ICA plotting functions to refer to IC index instead of component number by `Andreas Hojlund`_ and `Jaakko Leppakangas`_
 
-    - Fixed bug with ``picks`` when interpolating MEG channels by `Mainak Jas`_.
+- Fixed bug with ``picks`` when interpolating MEG channels by `Mainak Jas`_.
 
-    - Fixed bug in padding of Stockwell transform for signal of length a power of 2 by `Johannes Niediek`_
+- Fixed bug in padding of Stockwell transform for signal of length a power of 2 by `Johannes Niediek`_
 
 API
 ~~~
 
-    - The ``add_eeg_ref`` argument in core functions like :func:`mne.io.read_raw_fif` and :class:`mne.Epochs` has been deprecated in favor of using :func:`mne.set_eeg_reference` and equivalent instance methods like :meth:`raw.set_eeg_reference() <mne.io.Raw.set_eeg_reference>`. In functions like :func:`mne.io.read_raw_fif` where the default in 0.13 and older versions is ``add_eeg_ref=True``, the default will change to ``add_eeg_ref=False`` in 0.14, and the argument will be removed in 0.15.
+- The ``add_eeg_ref`` argument in core functions like :func:`mne.io.read_raw_fif` and :class:`mne.Epochs` has been deprecated in favor of using :func:`mne.set_eeg_reference` and equivalent instance methods like :meth:`raw.set_eeg_reference() <mne.io.Raw.set_eeg_reference>`. In functions like :func:`mne.io.read_raw_fif` where the default in 0.13 and older versions is ``add_eeg_ref=True``, the default will change to ``add_eeg_ref=False`` in 0.14, and the argument will be removed in 0.15.
 
-    - Multiple aspects of FIR filtering in MNE-Python has been refactored:
+- Multiple aspects of FIR filtering in MNE-Python has been refactored:
 
-          1. New recommended defaults for ``l_trans_bandwidth='auto'``, ``h_trans_bandwidth='auto'``, and ``filter_length='auto'``. This should generally reduce filter artifacts at the expense of slight decrease in effective filter stop-band attenuation. For details see :ref:`tut_filtering_in_python`. The default values of ``l_trans_bandwidth=h_trans_bandwidth=0.5`` and ``filter_length='10s'`` will change to ``'auto'`` in 0.14.
+  1. New recommended defaults for ``l_trans_bandwidth='auto'``, ``h_trans_bandwidth='auto'``, and ``filter_length='auto'``. This should generally reduce filter artifacts at the expense of slight decrease in effective filter stop-band attenuation. For details see :ref:`tut_filtering_in_python`. The default values of ``l_trans_bandwidth=h_trans_bandwidth=0.5`` and ``filter_length='10s'`` will change to ``'auto'`` in 0.14.
 
-          2. The ``filter_length=None`` option (i.e. use ``len(x)``) has been deprecated.
+  2. The ``filter_length=None`` option (i.e. use ``len(x)``) has been deprecated.
 
-          3. An improved ``phase='zero'`` zero-phase FIR filtering has been added. Instead of running the designed filter forward and backward, the filter is applied once and we compensate for the linear phase of the filter. The previous ``phase='zero-double'`` default will change to ``phase='zero'`` in 0.14.
+  3. An improved ``phase='zero'`` zero-phase FIR filtering has been added. Instead of running the designed filter forward and backward, the filter is applied once and we compensate for the linear phase of the filter. The previous ``phase='zero-double'`` default will change to ``phase='zero'`` in 0.14.
 
-          4. A warning is provided when the filter is longer than the signal of interest, as this is unlikely to produce desired results.
+  4. A warning is provided when the filter is longer than the signal of interest, as this is unlikely to produce desired results.
 
-          5. Previously, if the filter was as long or longer than the signal of interest, direct FFT-based computations were used. Now a single code path (overlap-add filtering) is used for all FIR filters. This could cause minor changes in how short signals are filtered.
+  5. Previously, if the filter was as long or longer than the signal of interest, direct FFT-based computations were used. Now a single code path (overlap-add filtering) is used for all FIR filters. This could cause minor changes in how short signals are filtered.
 
-    - Support for Python 2.6 has been dropped, and the minimum supported dependencies are NumPy_ 1.8, SciPy_ 0.12, and Matplotlib_ 1.3 by `Eric Larson`_
+- Support for Python 2.6 has been dropped, and the minimum supported dependencies are NumPy_ 1.8, SciPy_ 0.12, and Matplotlib_ 1.3 by `Eric Larson`_
 
-    - When CTF gradient compensation is applied to raw data, it is no longer reverted on save of :meth:`mne.io.Raw.save` by `Eric Larson`_
+- When CTF gradient compensation is applied to raw data, it is no longer reverted on save of :meth:`mne.io.Raw.save` by `Eric Larson`_
 
-    - Adds :func:`mne.time_frequency.csd_epochs` to replace :func:`mne.time_frequency.csd_compute_epochs` for naming consistency. :func:`mne.time_frequency.csd_compute_epochs` is now deprecated and will be removed in mne 0.14, by `Nick Foti`_
+- Adds :func:`mne.time_frequency.csd_epochs` to replace ``mne.time_frequency.csd_compute_epochs`` for naming consistency. ``mne.time_frequency.csd_compute_epochs`` is now deprecated and will be removed in mne 0.14, by `Nick Foti`_
 
-    - Weighted addition and subtraction of :class:`Evoked` as ``ev1 + ev2`` and ``ev1 - ev2`` have been deprecated, use explicit :func:`mne.combine_evoked(..., weights='nave') <mne.combine_evoked>` instead by `Eric Larson`_
+- Weighted addition and subtraction of :class:`Evoked` as ``ev1 + ev2`` and ``ev1 - ev2`` have been deprecated, use explicit :func:`mne.combine_evoked(..., weights='nave') <mne.combine_evoked>` instead by `Eric Larson`_
 
-    - Deprecated support for passing a lits of filenames to :class:`mne.io.Raw` constructor, use :func:`mne.io.read_raw_fif` and :func:`mne.concatenate_raws` instead by `Eric Larson`_
+- Deprecated support for passing a lits of filenames to :class:`mne.io.Raw` constructor, use :func:`mne.io.read_raw_fif` and :func:`mne.concatenate_raws` instead by `Eric Larson`_
 
-    - Added options for setting data and date formats manually in :func:`mne.io.read_raw_cnt` by `Jaakko Leppakangas`_
+- Added options for setting data and date formats manually in :func:`mne.io.read_raw_cnt` by `Jaakko Leppakangas`_
 
-    - Now channels with units of 'C', 'µS', 'uS', 'ARU' and 'S' will be turned to misc by default in :func:`mne.io.read_raw_brainvision` by `Jaakko Leppakangas`_
+- Now channels with units of 'C', 'µS', 'uS', 'ARU' and 'S' will be turned to misc by default in :func:`mne.io.read_raw_brainvision` by `Jaakko Leppakangas`_
 
-    - Add :func:`mne.io.anonymize_info` function to anonymize measurements and add methods to :class:`mne.io.Raw`, :class:`mne.Epochs` and :class:`mne.Evoked`, by `Jean-Remi King`_
+- Add :func:`mne.io.anonymize_info` function to anonymize measurements and add methods to :class:`mne.io.Raw`, :class:`mne.Epochs` and :class:`mne.Evoked`, by `Jean-Remi King`_
 
-    - Now it is possible to plot only a subselection of channels in :func:`mne.viz.plot_raw` by using an array for order parameter by `Jaakko Leppakangas`_
+- Now it is possible to plot only a subselection of channels in :func:`mne.viz.plot_raw` by using an array for order parameter by `Jaakko Leppakangas`_
 
-    - EOG channels can now be incuded when calling :func:`mne.preprocessing.ICA.fit` and a proper error is raised when trying to include unsupported channels by `Alexander Rudiuk`_
+- EOG channels can now be incuded when calling :func:`mne.preprocessing.ICA.fit` and a proper error is raised when trying to include unsupported channels by `Alexander Rudiuk`_
 
-    - :func:`mne.concatenate_epochs` and :func:`mne.compute_covariance` now check to see if all :class:`Epochs` instances have the same MEG-to-Head transformation, and errors by default if they do not by `Eric Larson`_
+- :func:`mne.concatenate_epochs` and :func:`mne.compute_covariance` now check to see if all :class:`Epochs` instances have the same MEG-to-Head transformation, and errors by default if they do not by `Eric Larson`_
 
-    - Added option to pass a list of axes to :func:`mne.viz.epochs.plot_epochs_image` by `Mikołaj Magnuski`_
+- Added option to pass a list of axes to :func:`mne.viz.plot_epochs_image` by `Mikołaj Magnuski`_
 
-    - Constructing IIR filters in :func:`mne.filter.construct_iir_filter` defaults to ``output='ba'`` in 0.13 but this will be changed to ``output='sos'`` by `Eric Larson`_
+- Constructing IIR filters in :func:`mne.filter.construct_iir_filter` defaults to ``output='ba'`` in 0.13 but this will be changed to ``output='sos'`` by `Eric Larson`_
 
-    - Add ``zorder`` parameter to :func:`mne.Evoked.plot` and derived functions to sort allow sorting channels by e.g. standard deviation, by `Jona Sassenhagen`_
+- Add ``zorder`` parameter to :func:`mne.Evoked.plot` and derived functions to sort allow sorting channels by e.g. standard deviation, by `Jona Sassenhagen`_
 
-    - The ``baseline`` parameter of :func:`mne.Epochs.apply_baseline` is set by default (None, 0), by `Felix Raimundo`_
+- The ``baseline`` parameter of :func:`mne.Epochs.apply_baseline` is set by default (None, 0), by `Felix Raimundo`_
 
-    - Adds :func:`mne.Evoked.apply_baseline` to be consistent with :func:`mne.Epochs.apply_baseline`, by `Felix Raimundo`_
+- Adds :func:`mne.Evoked.apply_baseline` to be consistent with :func:`mne.Epochs.apply_baseline`, by `Felix Raimundo`_
 
-    - Deprecated the `baseline` parameter in :class:`mne.Evoked`, by `Felix Raimundo`_
+- Deprecated the `baseline` parameter in :class:`mne.Evoked`, by `Felix Raimundo`_
 
-    - The API of :meth:`mne.SourceEstimate.plot` and :func:`mne.viz.plot_source_estimates` has been updated to reflect current PySurfer 0.6 API. The ``config_opts`` parameter is now deprecated and will be removed in mne 0.14, and the default representation for time will change from ``ms`` to ``s`` in mne 0.14. By `Christian Brodbeck`_
+- The API of :meth:`mne.SourceEstimate.plot` and :func:`mne.viz.plot_source_estimates` has been updated to reflect current PySurfer 0.6 API. The ``config_opts`` parameter is now deprecated and will be removed in mne 0.14, and the default representation for time will change from ``ms`` to ``s`` in mne 0.14. By `Christian Brodbeck`_
 
-    - The default dataset location has been changed from ``examples/`` in the MNE-Python root directory to ``~/mne_data`` in the user's home directory, by `Eric Larson`_
+- The default dataset location has been changed from ``examples/`` in the MNE-Python root directory to ``~/mne_data`` in the user's home directory, by `Eric Larson`_
 
-    - A new option ``set_env`` has been added to :func:`mne.set_config` that defaults to ``False`` in 0.13 but will change to ``True`` in 0.14, by `Eric Larson`_
+- A new option ``set_env`` has been added to :func:`mne.set_config` that defaults to ``False`` in 0.13 but will change to ``True`` in 0.14, by `Eric Larson`_
 
-    - The ``compensation`` parameter in :func:`mne.io.read_raw_fif` has been deprecated in favor of the method :meth:`mne.io.Raw.apply_gradient_compensation` by `Eric Larson`_
+- The ``compensation`` parameter in :func:`mne.io.read_raw_fif` has been deprecated in favor of the method :meth:`mne.io.Raw.apply_gradient_compensation` by `Eric Larson`_
 
-    - :class:`mne.decoding.EpochsVectorizer` has been deprecated in favor of :class:`mne.decoding.Vectorizer` by `Asish Panda`_
+- ``mne.decoding.EpochsVectorizer`` has been deprecated in favor of :class:`mne.decoding.Vectorizer` by `Asish Panda`_
 
-    - The `epochs_data` parameter has been deprecated in :class:`mne.decoding.CSP`, in favour of the ``X`` parameter to comply to scikit-learn API, by `Jean-Remi King`_
+- The `epochs_data` parameter has been deprecated in :class:`mne.decoding.CSP`, in favour of the ``X`` parameter to comply to scikit-learn API, by `Jean-Remi King`_
 
-    - Deprecated :func:`mne.time_frequency.cwt_morlet` and :func:`mne.time_frequency.single_trial_power` in favour of :func:`mne.time_frequency.tfr_morlet` with parameter average=False, by `Jean-Remi King`_ and `Alex Gramfort`_
+- Deprecated ``mne.time_frequency.cwt_morlet`` and ``mne.time_frequency.single_trial_power`` in favour of :func:`mne.time_frequency.tfr_morlet` with parameter average=False, by `Jean-Remi King`_ and `Alex Gramfort`_
 
-    - Add argument ``mask_type`` to func:`mne.read_events` and func:`mne.find_events` to support MNE-C style of trigger masking by `Teon Brooks`_ and `Eric Larson`_
+- Add argument ``mask_type`` to func:`mne.read_events` and func:`mne.find_events` to support MNE-C style of trigger masking by `Teon Brooks`_ and `Eric Larson`_
 
-    - Extended Infomax is now the new default in :func:`mne.preprocessing.infomax` (``extended=True``), by `Clemens Brunner`_
+- Extended Infomax is now the new default in :func:`mne.preprocessing.infomax` (``extended=True``), by `Clemens Brunner`_
 
-    - :func:`mne.io.read_raw_eeglab` and :func:`mne.io.read_epochs_eeglab` now take additional argument ``uint16_codec`` that allows to define the encoding of character arrays in set file. This helps in rare cases when reading a set file fails with ``TypeError: buffer is too small for requested array``. By `Mikołaj Magnuski`_
+- :func:`mne.io.read_raw_eeglab` and :func:`mne.read_epochs_eeglab` now take additional argument ``uint16_codec`` that allows to define the encoding of character arrays in set file. This helps in rare cases when reading a set file fails with ``TypeError: buffer is too small for requested array``. By `Mikołaj Magnuski`_
 
-    - Added :class:`mne.decoding.TemporalFilter` to filter data in scikit-learn pipelines, by `Asish Panda`_
+- Added :class:`mne.decoding.TemporalFilter` to filter data in scikit-learn pipelines, by `Asish Panda`_
 
-    - :func:`mne.preprocessing.create_ecg_epochs` now includes all the channels when ``picks=None`` by `Jaakko Leppakangas`_
+- :func:`mne.preprocessing.create_ecg_epochs` now includes all the channels when ``picks=None`` by `Jaakko Leppakangas`_
+
+- :func:`mne.set_eeg_reference` now allows moving from a custom to an average EEG reference by `Marijn van Vliet`_
 
 Authors
 ~~~~~~~
 
 The committer list for this release is the following (sorted by alphabetical order):
 
-    * Alexander Rudiuk
-    * Alexandre Barachant
-    * Alexandre Gramfort
-    * Asish Panda
-    * Camilo Lamus
-    * Chris Holdgraf
-    * Christian Brodbeck
-    * Christopher J. Bailey
-    * Christopher Mullins
-    * Clemens Brunner
-    * Denis A. Engemann
-    * Eric Larson
-    * Federico Raimondo
-    * Félix Raimundo
-    * Guillaume Dumas
-    * Jaakko Leppakangas
-    * Jair Montoya
-    * Jean-Remi King
-    * Johannes Niediek
-    * Jona Sassenhagen
-    * Jussi Nurminen
-    * Keith Doelling
-    * Mainak Jas
-    * Marijn van Vliet
-    * Michael Krause
-    * Mikolaj Magnuski
-    * Nick Foti
-    * Phillip Alday
-    * Simon-Shlomo Poil
-    * Teon Brooks
-    * Yaroslav Halchenko
+* Alexander Rudiuk
+* Alexandre Barachant
+* Alexandre Gramfort
+* Asish Panda
+* Camilo Lamus
+* Chris Holdgraf
+* Christian Brodbeck
+* Christopher J. Bailey
+* Christopher Mullins
+* Clemens Brunner
+* Denis A. Engemann
+* Eric Larson
+* Federico Raimondo
+* Félix Raimundo
+* Guillaume Dumas
+* Jaakko Leppakangas
+* Jair Montoya
+* Jean-Remi King
+* Johannes Niediek
+* Jona Sassenhagen
+* Jussi Nurminen
+* Keith Doelling
+* Mainak Jas
+* Marijn van Vliet
+* Michael Krause
+* Mikolaj Magnuski
+* Nick Foti
+* Phillip Alday
+* Simon-Shlomo Poil
+* Teon Brooks
+* Yaroslav Halchenko
 
 .. _changes_0_12:
 
@@ -260,205 +867,209 @@ Version 0.12
 Changelog
 ~~~~~~~~~
 
-    - Add ``overlay_times`` parameter to :func:`mne.viz.plot_epochs_image` to be able to display for example reaction times on top of the images, by `Alex Gramfort`_
+- Add ``overlay_times`` parameter to :func:`mne.viz.plot_epochs_image` to be able to display for example reaction times on top of the images, by `Alex Gramfort`_
 
-    - Animation for evoked topomap in :func:`mne.Evoked.animate_topomap` by `Jaakko Leppakangas`_
+- Animation for evoked topomap in :func:`mne.Evoked.animate_topomap` by `Jaakko Leppakangas`_
 
-    - Make :func:`mne.channels.find_layout` more robust for KIT systems in the presence of bad or missing channels by `Jaakko Leppakangas`_
+- Make :func:`mne.channels.find_layout` more robust for KIT systems in the presence of bad or missing channels by `Jaakko Leppakangas`_
 
-    - Add raw movement compensation to :func:`mne.preprocessing.maxwell_filter` by `Eric Larson`_
+- Add raw movement compensation to :func:`mne.preprocessing.maxwell_filter` by `Eric Larson`_
 
-    - Add :class:`mne.Annotations` for for annotating segments of raw data by `Jaakko Leppakangas`_
+- Add :class:`mne.Annotations` for for annotating segments of raw data by `Jaakko Leppakangas`_
 
-    - Add reading of .fif file montages by `Eric Larson`_
+- Add reading of .fif file montages by `Eric Larson`_
 
-    - Add system config utility :func:`mne.sys_info` by `Eric Larson`_
+- Add system config utility :func:`mne.sys_info` by `Eric Larson`_
 
-    - Automatic cross-validation and scoring metrics in :func:`mne.decoding.GeneralizationAcrossTime`, by `Jean-Remi King`_
+- Automatic cross-validation and scoring metrics in ``mne.decoding.GeneralizationAcrossTime``, by `Jean-Remi King`_
 
-    - :func:`mne.decoding.GeneralizationAcrossTime` accepts non-deterministic cross-validations, by `Jean-Remi King`_
+- ``mne.decoding.GeneralizationAcrossTime`` accepts non-deterministic cross-validations, by `Jean-Remi King`_
 
-    - Add plotting RMS of gradiometer pairs in :func:`mne.viz.plot_evoked_topo` by `Jaakko Leppakangas`_
+- Add plotting RMS of gradiometer pairs in :func:`mne.viz.plot_evoked_topo` by `Jaakko Leppakangas`_
 
-    - Add regularization methods to :func:`mne.compute_raw_covariance` by `Eric Larson`_.
+- Add regularization methods to :func:`mne.compute_raw_covariance` by `Eric Larson`_.
 
-    - Add command ``mne show_info`` to quickly show the measurement info from a .fif file from the terminal by `Alex Gramfort`_.
+- Add command ``mne show_info`` to quickly show the measurement info from a .fif file from the terminal by `Alex Gramfort`_.
 
-    - Add creating forward operator for dipole object :func:`mne.make_forward_dipole` by `Chris Bailey`_
+- Add creating forward operator for dipole object :func:`mne.make_forward_dipole` by `Chris Bailey`_
 
-    - Add reading and estimation of fixed-position dipole time courses (similar to Elekta ``xfit``) using :func:`mne.read_dipole` and :func:`mne.fit_dipole` by `Eric Larson`_.
+- Add reading and estimation of fixed-position dipole time courses (similar to Elekta ``xfit``) using :func:`mne.read_dipole` and :func:`mne.fit_dipole` by `Eric Larson`_.
 
-    - Accept :class:`mne.decoding.GeneralizationAcrossTime`'s ``scorer`` parameter to be a string that refers to a scikit-learn_ metric scorer by `Asish Panda`_.
+- Accept ``mne.decoding.GeneralizationAcrossTime``'s ``scorer`` parameter to be a string that refers to a scikit-learn_ metric scorer by `Asish Panda`_.
 
-    - Add method :func:`mne.Epochs.plot_image` calling :func:`mne.viz.plot_epochs_image` for better usability by `Asish Panda`_.
+- Add method :func:`mne.Epochs.plot_image` calling :func:`mne.viz.plot_epochs_image` for better usability by `Asish Panda`_.
 
-    - Add :func:`mne.io.read_raw_cnt` for reading Neuroscan CNT files by `Jaakko Leppakangas`_
+- Add :func:`mne.io.read_raw_cnt` for reading Neuroscan CNT files by `Jaakko Leppakangas`_
 
-    - Add ``decim`` parameter to :func:`mne.time_frequency.cwt_morlet`, by `Jean-Remi King`_
+- Add ``decim`` parameter to ``mne.time_frequency.cwt_morlet``, by `Jean-Remi King`_
 
-    - Add method :func:`mne.Epochs.plot_topo_image` by `Jaakko Leppakangas`_
+- Add method :func:`mne.Epochs.plot_topo_image` by `Jaakko Leppakangas`_
 
-    - Add the ability to read events when importing raw EEGLAB files, by `Jona Sassenhagen`_.
+- Add the ability to read events when importing raw EEGLAB files, by `Jona Sassenhagen`_.
 
-    - Add function :func:`mne.viz.plot_sensors` and methods :func:`mne.Epochs.plot_sensors`, :func:`mne.io.Raw.plot_sensors` and :func:`mne.Evoked.plot_sensors` for plotting sensor positions and :func:`mne.viz.plot_layout` and :func:`mne.channels.Layout.plot` for plotting layouts by `Jaakko Leppakangas`_
+- Add function :func:`mne.viz.plot_sensors` and methods :func:`mne.Epochs.plot_sensors`, :func:`mne.io.Raw.plot_sensors` and :func:`mne.Evoked.plot_sensors` for plotting sensor positions and :func:`mne.viz.plot_layout` and :func:`mne.channels.Layout.plot` for plotting layouts by `Jaakko Leppakangas`_
 
-    - Add epoch rejection based on annotated segments by `Jaakko Leppakangas`_
+- Add epoch rejection based on annotated segments by `Jaakko Leppakangas`_
 
-    - Add option to use new-style MEG channel names in :func:`mne.read_selection` by `Eric Larson`_
+- Add option to use new-style MEG channel names in :func:`mne.read_selection` by `Eric Larson`_
 
-    - Add option for ``proj`` in :class:`mne.EpochsArray` by `Eric Larson`_
+- Add option for ``proj`` in :class:`mne.EpochsArray` by `Eric Larson`_
 
-    - Enable the usage of :func:`mne.viz.topomap.plot_topomap` with an :class:`mne.io.Info` instance for location information, by `Jona Sassenhagen`_.
+- Enable the usage of :func:`mne.viz.plot_topomap` with an :class:`mne.Info` instance for location information, by `Jona Sassenhagen`_.
 
-    - Add support for electrocorticography (ECoG) channel type by `Eric Larson`_
+- Add support for electrocorticography (ECoG) channel type by `Eric Larson`_
 
-    - Add option for ``first_samp`` in :func:`mne.make_fixed_length_events` by `Jon Houck`_
+- Add option for ``first_samp`` in :func:`mne.make_fixed_length_events` by `Jon Houck`_
 
-    - Add ability to auto-scale channel types for :func:`mne.viz.plot_raw` and :func:`mne.viz.plot_epochs` and corresponding object plotting methods by `Chris Holdgraf`_
+- Add ability to auto-scale channel types for :func:`mne.viz.plot_raw` and :func:`mne.viz.plot_epochs` and corresponding object plotting methods by `Chris Holdgraf`_
 
 BUG
 ~~~
 
-    - :func:`compute_raw_psd`, :func:`compute_epochs_psd`, :func:`psd_multitaper`, and :func:`psd_welch` no longer remove rows/columns of the SSP matrix before applying SSP projectors when picks are provided by `Chris Holdgraf`_.
+- ``mne.time_frequency.compute_raw_psd``, ``mne.time_frequency.compute_epochs_psd``, :func:`mne.time_frequency.psd_multitaper`, and :func:`mne.time_frequency.psd_welch` no longer remove rows/columns of the SSP matrix before applying SSP projectors when picks are provided by `Chris Holdgraf`_.
+
+- :func:`mne.Epochs.plot_psd` no longer calls a Welch PSD, and instead uses a Multitaper method which is more appropriate for epochs. Flags for this function are passed to :func:`mne.time_frequency.psd_multitaper` by `Chris Holdgraf`_
+
+- Time-cropping functions (e.g., :func:`mne.Epochs.crop`, :func:`mne.Evoked.crop`, :func:`mne.io.Raw.crop`, :func:`mne.SourceEstimate.crop`) made consistent with behavior of ``tmin`` and ``tmax`` of :class:`mne.Epochs`, where nearest sample is kept. For example, for MGH data acquired with ``sfreq=600.614990234``, constructing ``Epochs(..., tmin=-1, tmax=1)`` has bounds ``+/-1.00064103``, and now ``epochs.crop(-1, 1)`` will also have these bounds (previously they would have been ``+/-0.99 [...]
 
-    - :func:`mne.Epochs.plot_psd` no longer calls a Welch PSD, and instead uses a Multitaper method which is more appropriate for epochs. Flags for this function are passed to :func:`mne.time_frequency.psd_multitaper` by `Chris Holdgraf`_
+- Fix EEG spherical spline interpolation code to account for average reference by `Mainak Jas`_ (`#2758 <https://github.com/mne-tools/mne-python/pull/2758>`_)
 
-    - Time-cropping functions (e.g., :func:`mne.Epochs.crop`, :func:`mne.Evoked.crop`, :func:`mne.io.Raw.crop`, :func:`mne.SourceEstimate.crop`) made consistent with behavior of ``tmin`` and ``tmax`` of :class:`mne.Epochs`, where nearest sample is kept. For example, for MGH data acquired with ``sfreq=600.614990234``, constructing ``Epochs(..., tmin=-1, tmax=1)`` has bounds ``+/-1.00064103``, and now ``epochs.crop(-1, 1)`` will also have these bounds (previously they would have been ``+/- [...]
+- MEG projectors are removed after Maxwell filtering by `Eric Larson`_
 
-    - Fix EEG spherical spline interpolation code to account for average reference by `Mainak Jas`_ (`#2758 <https://github.com/mne-tools/mne-python/pull/2758>`_)
+- Fix ``mne.decoding.TimeDecoding`` to allow specifying ``clf`` by `Jean-Remi King`_
 
-    - MEG projectors are removed after Maxwell filtering by `Eric Larson`_
+- Fix bug with units (uV) in 'Brain Vision Data Exchange Header File Version 1.0' by `Federico Raimondo`_
 
-    - Fix :func:`mne.decoding.TimeDecoding` to allow specifying ``clf`` by `Jean-Remi King`_
+- Fix bug where :func:`mne.preprocessing.maxwell_filter` ``destination`` parameter did not properly set device-to-head transform by `Eric Larson`_
 
-    - Fix bug with units (uV) in 'Brain Vision Data Exchange Header File Version 1.0' by `Federico Raimondo`_
+- Fix bug in rank calculation of ``mne.utils.estimate_rank``, :func:`mne.io.Raw.estimate_rank`, and covariance functions where the tolerance was set to slightly too small a value, new 'auto' mode uses values from ``scipy.linalg.orth`` by `Eric Larson`_.
 
-    - Fix bug where :func:`mne.preprocessing.maxwell_filter` ``destination`` parameter did not properly set device-to-head transform by `Eric Larson`_
+- Fix bug when specifying irregular ``train_times['slices']`` in ``mne.decoding.GeneralizationAcrossTime``, by `Jean-Remi King`_
 
-    - Fix bug in rank calculation of :func:`mne.utils.estimate_rank`, :func:`mne.io.Raw.estimate_rank`, and covariance functions where the tolerance was set to slightly too small a value, new 'auto' mode uses values from ``scipy.linalg.orth`` by `Eric Larson`_.
+- Fix colorbar range on norm data by `Jaakko Leppakangas`_
 
-    - Fix bug when specifying irregular ``train_times['slices']`` in :func:`mne.decoding.GeneralizationAcrossTime`, by `Jean-Remi King`_
+- Fix bug in :func:`mne.preprocessing.run_ica`, which used the ``ecg_criterion`` parameter for the EOG criterion instead of ``eog_criterion`` by `Christian Brodbeck`_
 
-    - Fix colorbar range on norm data by `Jaakko Leppakangas`_
+- Fix normals in CTF data reader by `Eric Larson`_
 
-    - Fix bug in :func:`mne.preprocessing.run_ica`, which used the ``ecg_criterion`` parameter for the EOG criterion instead of ``eog_criterion`` by `Christian Brodbeck`_
+- Fix bug in :func:`mne.io.read_raw_ctf`, when omitting samples at the end by `Jaakko Leppakangas`_
 
-    - Fix normals in CTF data reader by `Eric Larson`_
+- Fix ``info['lowpass']`` value for downsampled raw data by `Eric Larson`_
 
-    - Fix bug in :func:`mne.io.read_raw_ctf`, when omitting samples at the end by `Jaakko Leppakangas`_
+- Remove measurement date from :class:`mne.Info` in :func:`mne.io.Raw.anonymize` by `Eric Larson`_
 
-    - Fix ``info['lowpass']`` value for downsampled raw data by `Eric Larson`_
+- Fix bug that caused synthetic ecg channel creation even if channel was specified for ECG peak detection in :func:`mne.preprocessing.create_ecg_epochs` by `Jaakko Leppakangas`_
 
-    - Remove measurement date from :class:`mne.Info` in :func:`mne.io.Raw.anonymize` by `Eric Larson`_
+- Fix bug with vmin and vmax when None is passed in :func:`mne.viz.plot_topo_image_epochs` by `Jaakko Leppakangas`_
 
-    - Fix bug that caused synthetic ecg channel creation even if channel was specified for ECG peak detection in :func:`mne.preprocessing.create_ecg_epochs` by `Jaakko Leppakangas`_
+- Fix bug with :func:`mne.label_sign_flip` (and :func:`mne.extract_label_time_course`) by `Natalie Klein`_ and `Eric Larson`_
 
-    - Fix bug with vmin and vmax when None is passed in :func:`mne.viz.plot_topo_image_epochs` by `Jaakko Leppakangas`_
+- Add copy parameter in :func:`mne.Epochs.apply_baseline` and :func:`mne.io.Raw.filter` methods by `Jona Sassenhagen`_ and `Alex Gramfort`_
 
-    - Fix bug with :func:`mne.label_sign_flip` (and :func:`mne.extract_label_time_course`) by `Natalie Klein`_ and `Eric Larson`_
+- Fix bug in :func:`mne.merge_events` when using ``replace_events=False`` by `Alex Gramfort`_
 
-    - Add copy parameter in :func:`mne.Epochs.apply_baseline` and :func:`mne.io.Raw.filter` methods by `Jona Sassenhagen`_ and `Alex Gramfort`_
+- Fix bug in :class:`mne.Evoked` type setting in :func:`mne.stats.linear_regression_raw` by `Eric Larson`_
 
-    - Fix bug in :func:`mne.merge_events` when using ``replace_events=False`` by `Alex Gramfort`_
+- Fix bug in :class: `mne.io.edf.RawEDF` highpass filter setting to take max highpass to match warning message by `Teon Brooks`_
 
-    - Fix bug in :class:`mne.Evoked` type setting in :func:`mne.stats.linear_regression_raw` by `Eric Larson`_
+- Fix bugs with coordinane frame adjustments in ``mne.viz.plot_trans`` by `Eric Larson`_
 
-    - Fix bug in :class: `mne.io.edf.RawEDF` highpass filter setting to take max highpass to match warning message by `Teon Brooks`_
+- Fix bug in colormap selection in :func:`mne.Evoked.plot_projs_topomap` by `Jaakko Leppakangas`_
 
-    - Fix bugs with coordinane frame adjustments in :func:`mne.viz.plot_trans` by `Eric Larson`_
+- Fix bug in source normal adjustment that occurred when 1) patch information is available (e.g., when distances have been calculated) and 2) points are excluded from the source space (by inner skull distance) by `Eric Larson`_
 
-    - Fix bug in colormap selection in :func:`mne.Evoked.plot_projs_topomap` by `Jaakko Leppakangas`_
+- Fix bug when merging info that has a field with list of dicts by `Jaakko Leppakangas`_
 
-    - Fix bug in source normal adjustment that occurred when 1) patch information is available (e.g., when distances have been calculated) and 2) points are excluded from the source space (by inner skull distance) by `Eric Larson`_
+- The BTi/4D reader now considers user defined channel labels instead of the hard-ware names, however only for channels other than MEG. By `Denis Engemann`_ and `Alex Gramfort`_.
 
-    - Fix bug when merging info that has a field with list of dicts by `Jaakko Leppakangas`_
+- The BTi reader :func:`mne.io.read_raw_bti` can now read 2500 system data, by `Eric Larson`_
 
-    - The BTI/4D reader now considers user defined channel labels instead of the hard-ware names, however only for channels other than MEG. By `Denis Engemann`_ and `Alex Gramfort`_.
+- Fix bug in :func:`mne.compute_raw_covariance` where rejection by non-data channels (e.g. EOG) was not done properly by `Eric Larson`_.
 
-    - Fix bug in :func:`mne.compute_raw_covariance` where rejection by non-data channels (e.g. EOG) was not done properly by `Eric Larson`_.
+- Change default scoring method of ``mne.decoding.GeneralizationAcrossTime`` and ``mne.decoding.TimeDecoding`` to estimate the scores within the cross-validation as in scikit-learn_ as opposed to across all cross-validated ``y_pred``. The method can be changed with the ``score_mode`` parameter by `Jean-Remi King`_
 
-    - Change default scoring method of :func:`mne.decoding.GeneralizationAcrossTime` and :func:`mne.decoding.TimeDecoding` to estimate the scores within the cross-validation as in scikit-learn_ as opposed to across all cross-validated ``y_pred``. The method can be changed with the ``score_mode`` parameter by `Jean-Remi King`_
+- Fix bug in :func:`mne.io.Raw.save` where, in rare cases, automatically split files could end up writing an extra empty file that wouldn't be read properly by `Eric Larson`_
 
-    - Fix bug in :func:`mne.io.Raw.save` where, in rare cases, automatically split files could end up writing an extra empty file that wouldn't be read properly by `Eric Larson`_
+- Fix :class:`mne.realtime.StimServer` by removing superfluous argument ``ip`` used while initializing the object by `Mainak Jas`_.
 
-    - Fix :class:`mne.realtime.StimServer` by removing superfluous argument ``ip`` used while initializing the object by `Mainak Jas`_.
+- Fix removal of projectors in :func:`mne.preprocessing.maxwell_filter` in ``st_only=True`` mode by `Eric Larson`_
 
 API
 ~~~
 
-    - The default `picks=None` in :func:`mne.viz.plot_epochs_image` now only plots the first 5 channels, not all channels, by `Jona Sassenhagen`_
+- The default `picks=None` in :func:`mne.viz.plot_epochs_image` now only plots the first 5 channels, not all channels, by `Jona Sassenhagen`_
 
-    - The ``mesh_color`` parameter in :func:`mne.viz.plot_dipole_locations` has been removed (use `brain_color` instead), by `Marijn van Vliet`_
+- The ``mesh_color`` parameter in :func:`mne.viz.plot_dipole_locations` has been removed (use `brain_color` instead), by `Marijn van Vliet`_
 
-    - Deprecated functions :func:`mne.time_frequency.compute_raw_psd` and :func:`mne.time_frequency.compute_epochs_psd`, replaced by :func:`mne.time_frequency.psd_welch` by `Chris Holdgraf`_
+- Deprecated functions ``mne.time_frequency.compute_raw_psd`` and ``mne.time_frequency.compute_epochs_psd``, replaced by :func:`mne.time_frequency.psd_welch` by `Chris Holdgraf`_
 
-    - Deprecated function :func:`mne.time_frequency.multitaper_psd` and replaced by :func:`mne.time_frequency.psd_multitaper` by `Chris Holdgraf`_
+- Deprecated function ``mne.time_frequency.multitaper_psd`` and replaced by :func:`mne.time_frequency.psd_multitaper` by `Chris Holdgraf`_
 
-    - The ``y_pred`` attribute in :func:`mne.decoding.GeneralizationAcrossTime` and :func:`mne.decoding.TimeDecoding` is now a numpy array, by `Jean-Remi King`_
+- The ``y_pred`` attribute in ``mne.decoding.GeneralizationAcrossTime`` and ``mne.decoding.TimeDecoding`` is now a numpy array, by `Jean-Remi King`_
 
-    - The :func:`mne.bem.fit_sphere_to_headshape` function now default to ``dig_kinds='auto'`` which will use extra digitization points, falling back to extra plus eeg digitization points if there not enough extra points are available.
+- The :func:`mne.bem.fit_sphere_to_headshape` function now default to ``dig_kinds='auto'`` which will use extra digitization points, falling back to extra plus eeg digitization points if there not enough extra points are available.
 
-    - The :func:`mne.bem.fit_sphere_to_headshape` now has a ``units`` argument that should be set explicitly. This will default to ``units='mm'`` in 0.12 for backward compatibility but change to ``units='m'`` in 0.13.
+- The :func:`mne.bem.fit_sphere_to_headshape` now has a ``units`` argument that should be set explicitly. This will default to ``units='mm'`` in 0.12 for backward compatibility but change to ``units='m'`` in 0.13.
 
-    - Added default parameters in Epochs class namely ``event_id=None``, ``tmin=-0.2`` and ``tmax=0.5``.
+- Added default parameters in Epochs class namely ``event_id=None``, ``tmin=-0.2`` and ``tmax=0.5``.
 
-    - To unify and extend the behavior of :func:`mne.comupute_raw_covariance` relative to :func:`mne.compute_covariance`, the default parameter ``tstep=0.2`` now discards any epochs at the end of the :class:`mne.io.Raw` instance that are not the full ``tstep`` duration. This will slightly change the computation of :func:`mne.compute_raw_covaraince`, but should only potentially have a big impact if the :class:`mne.io.Raw` instance is short relative to ``tstep`` and the last, too short (no [...]
+- To unify and extend the behavior of :func:`mne.compute_raw_covariance` relative to :func:`mne.compute_covariance`, the default parameter ``tstep=0.2`` now discards any epochs at the end of the :class:`mne.io.Raw` instance that are not the full ``tstep`` duration. This will slightly change the computation of :func:`mne.compute_raw_covariance`, but should only potentially have a big impact if the :class:`mne.io.Raw` instance is short relative to ``tstep`` and the last, too short (now dis [...]
 
-    - The default ``picks=None`` in :func:`mne.io.Raw.filter` now picks eeg, meg, seeg, and ecog channels, by `Jean-Remi King`_ and `Eric Larson`_
+- The default ``picks=None`` in :func:`mne.io.Raw.filter` now picks eeg, meg, seeg, and ecog channels, by `Jean-Remi King`_ and `Eric Larson`_
 
-    - EOG, ECG and EMG channels are now plotted by default (if present in data) when using :func:`mne.viz.plot_evoked` by `Marijn van Vliet`_
+- EOG, ECG and EMG channels are now plotted by default (if present in data) when using :func:`mne.viz.plot_evoked` by `Marijn van Vliet`_
 
-    - Replace pseudoinverse-based solver with much faster Cholesky solver in :func:`mne.stats.linear_regression_raw`, by `Jona Sassenhagen`_.
+- Replace pseudoinverse-based solver with much faster Cholesky solver in :func:`mne.stats.linear_regression_raw`, by `Jona Sassenhagen`_.
 
-    - CTF data reader now reads EEG locations from .pos file as HPI points by `Jaakko Leppakangas`_
+- CTF data reader now reads EEG locations from .pos file as HPI points by `Jaakko Leppakangas`_
 
-    - Subselecting channels can now emit a warning if many channels have been subselected from projection vectors. We recommend only computing projection vertors for and applying projectors to channels that will be used in the final analysis. However, after picking a subset of channels, projection vectors can be renormalized with :func:`mne.Info.normalize_proj` if necessary to avoid warnings about subselection. Changes by `Eric Larson`_ and `Alex Gramfort`_.
+- Subselecting channels can now emit a warning if many channels have been subselected from projection vectors. We recommend only computing projection vertors for and applying projectors to channels that will be used in the final analysis. However, after picking a subset of channels, projection vectors can be renormalized with :func:`mne.Info.normalize_proj` if necessary to avoid warnings about subselection. Changes by `Eric Larson`_ and `Alex Gramfort`_.
 
-    - Rename and deprecate :func:`mne.Epochs.drop_bad_epochs` to :func:`mne.Epochs.drop_bad`, and :func:`mne.Epochs.drop_epochs` to :func:`mne.Epochs.drop` by `Alex Gramfort`_.
+- Rename and deprecate ``mne.Epochs.drop_bad_epochs`` to :func:`mne.Epochs.drop_bad`, and `mne.Epochs.drop_epochs`` to :func:`mne.Epochs.drop` by `Alex Gramfort`_.
 
-    - The C wrapper :func:`mne.do_forward_solution` has been deprecated in favor of the native Python version :func:`mne.make_forward_solution` by `Eric Larson`_
+- The C wrapper ``mne.do_forward_solution`` has been deprecated in favor of the native Python version :func:`mne.make_forward_solution` by `Eric Larson`_
 
-    - The ``events`` parameter of :func:`mne.epochs.EpochsArray` is set by default to chronological time-samples and event values to 1, by `Jean-Remi King`_
+- The ``events`` parameter of :func:`mne.EpochsArray` is set by default to chronological time-samples and event values to 1, by `Jean-Remi King`_
 
 Authors
 ~~~~~~~
 
 The committer list for this release is the following (preceded by number of commits):
 
-    * 348	Eric Larson
-    * 347	Jaakko Leppakangas
-    * 157	Alexandre Gramfort
-    * 139	Jona Sassenhagen
-    * 67	Jean-Remi King
-    * 32	Chris Holdgraf
-    * 31	Denis A. Engemann
-    * 30	Mainak Jas
-    * 16	Christopher J. Bailey
-    * 13	Marijn van Vliet
-    * 10	Mark Wronkiewicz
-    * 9	Teon Brooks
-    * 9	kaichogami
-    * 8	Clément Moutard
-    * 5	Camilo Lamus
-    * 5	mmagnuski
-    * 4	Christian Brodbeck
-    * 4	Daniel McCloy
-    * 4	Yousra Bekhti
-    * 3	Fede Raimondo
-    * 1	Jussi Nurminen
-    * 1	MartinBaBer
-    * 1	Mikolaj Magnuski
-    * 1	Natalie Klein
-    * 1	Niklas Wilming
-    * 1	Richard Höchenberger
-    * 1	Sagun Pai
-    * 1	Sourav Singh
-    * 1	Tom Dupré la Tour
-    * 1	jona-sassenhagen@
-    * 1	kambysese
-    * 1	pbnsilva
-    * 1	sviter
-    * 1	zuxfoucault
+* 348	Eric Larson
+* 347	Jaakko Leppakangas
+* 157	Alexandre Gramfort
+* 139	Jona Sassenhagen
+* 67	Jean-Remi King
+* 32	Chris Holdgraf
+* 31	Denis A. Engemann
+* 30	Mainak Jas
+* 16	Christopher J. Bailey
+* 13	Marijn van Vliet
+* 10	Mark Wronkiewicz
+* 9	Teon Brooks
+* 9	kaichogami
+* 8	Clément Moutard
+* 5	Camilo Lamus
+* 5	mmagnuski
+* 4	Christian Brodbeck
+* 4	Daniel McCloy
+* 4	Yousra Bekhti
+* 3	Fede Raimondo
+* 1	Jussi Nurminen
+* 1	MartinBaBer
+* 1	Mikolaj Magnuski
+* 1	Natalie Klein
+* 1	Niklas Wilming
+* 1	Richard Höchenberger
+* 1	Sagun Pai
+* 1	Sourav Singh
+* 1	Tom Dupré la Tour
+* 1	jona-sassenhagen@
+* 1	kambysese
+* 1	pbnsilva
+* 1	sviter
+* 1	zuxfoucault
 
 .. _changes_0_11:
 
@@ -468,90 +1079,90 @@ Version 0.11
 Changelog
 ~~~~~~~~~
 
-    - Maxwell filtering (SSS) implemented in :func:`mne.preprocessing.maxwell_filter` by `Mark Wronkiewicz`_ as part of Google Summer of Code, with help from `Samu Taulu`_, `Jukka Nenonen`_, and `Jussi Nurminen`_. Our implementation includes support for:
+- Maxwell filtering (SSS) implemented in :func:`mne.preprocessing.maxwell_filter` by `Mark Wronkiewicz`_ as part of Google Summer of Code, with help from `Samu Taulu`_, `Jukka Nenonen`_, and `Jussi Nurminen`_. Our implementation includes support for:
 
-        - Fine calibration
+  - Fine calibration
 
-        - Cross-talk correction
+  - Cross-talk correction
 
-        - Temporal SSS (tSSS)
+  - Temporal SSS (tSSS)
 
-        - Head position translation
+  - Head position translation
 
-        - Internal component regularization
+  - Internal component regularization
 
-    - Compensation for movements using Maxwell filtering on epoched data in :func:`mne.epochs.average_movements` by `Eric Larson`_ and `Samu Taulu`_
+- Compensation for movements using Maxwell filtering on epoched data in :func:`mne.epochs.average_movements` by `Eric Larson`_ and `Samu Taulu`_
 
-    - Add reader for Nicolet files in :func:`mne.io.read_raw_nicolet` by `Jaakko Leppakangas`_
+- Add reader for Nicolet files in :func:`mne.io.read_raw_nicolet` by `Jaakko Leppakangas`_
 
-    - Add FIFF persistence for ICA labels by `Denis Engemann`_
+- Add FIFF persistence for ICA labels by `Denis Engemann`_
 
-    - Display ICA labels in :func:`mne.viz.plot_ica_scores` and :func:`mne.viz.plot_ica_sources` (for evoked objects) by `Denis Engemann`_
+- Display ICA labels in :func:`mne.viz.plot_ica_scores` and :func:`mne.viz.plot_ica_sources` (for evoked objects) by `Denis Engemann`_
 
-    - Plot spatially color coded lines in :func:`mne.Evoked.plot` by `Jona Sassenhagen`_ and `Jaakko Leppakangas`_
+- Plot spatially color coded lines in :func:`mne.Evoked.plot` by `Jona Sassenhagen`_ and `Jaakko Leppakangas`_
 
-    - Add reader for CTF data in :func:`mne.io.read_raw_ctf` by `Eric Larson`_
+- Add reader for CTF data in :func:`mne.io.read_raw_ctf` by `Eric Larson`_
 
-    - Add support for Brainvision v2 in :func:`mne.io.read_raw_brainvision` by `Teon Brooks`_
+- Add support for Brainvision v2 in :func:`mne.io.read_raw_brainvision` by `Teon Brooks`_
 
-    - Improve speed of generalization across time :class:`mne.decoding.GeneralizationAcrossTime` decoding up to a factor of seven by `Jean-Remi King`_ and `Federico Raimondo`_ and `Denis Engemann`_.
+- Improve speed of generalization across time ``mne.decoding.GeneralizationAcrossTime`` decoding up to a factor of seven by `Jean-Remi King`_ and `Federico Raimondo`_ and `Denis Engemann`_.
 
-    - Add the explained variance for each principal component, ``explained_var``, key to the :class:`mne.io.Projection` by `Teon Brooks`_
+- Add the explained variance for each principal component, ``explained_var``, key to the :class:`mne.Projection` by `Teon Brooks`_
 
-    - Added methods :func:`mne.Epochs.add_eeg_average_proj`, :func:`mne.io.Raw.add_eeg_average_proj`, and :func:`mne.Evoked.add_eeg_average_proj` to add an average EEG reference.
+- Added methods ``mne.Epochs.add_eeg_average_proj``, ``mne.io.Raw.add_eeg_average_proj``, and ``mne.Evoked.add_eeg_average_proj`` to add an average EEG reference.
 
-    - Add reader for EEGLAB data in :func:`mne.io.read_raw_eeglab` and :func:`mne.read_epochs_eeglab` by `Mainak Jas`_
+- Add reader for EEGLAB data in :func:`mne.io.read_raw_eeglab` and :func:`mne.read_epochs_eeglab` by `Mainak Jas`_
 
 BUG
 ~~~
 
-    - Fix bug that prevented homogeneous bem surfaces to be displayed in HTML reports by `Denis Engemann`_
+- Fix bug that prevented homogeneous bem surfaces to be displayed in HTML reports by `Denis Engemann`_
 
-    - Added safeguards against ``None`` and negative values in reject and flat parameters in :class:`mne.Epochs` by `Eric Larson`_
+- Added safeguards against ``None`` and negative values in reject and flat parameters in :class:`mne.Epochs` by `Eric Larson`_
 
-    - Fix train and test time window-length in :class:`mne.decoding.GeneralizationAcrossTime` by `Jean-Remi King`_
+- Fix train and test time window-length in ``mne.decoding.GeneralizationAcrossTime`` by `Jean-Remi King`_
 
-    - Added lower bound in :func:`mne.stats.linear_regression` on p-values ``p_val`` (and resulting ``mlog10_p_val``) using double floating point arithmetic limits by `Eric Larson`_
+- Added lower bound in :func:`mne.stats.linear_regression` on p-values ``p_val`` (and resulting ``mlog10_p_val``) using double floating point arithmetic limits by `Eric Larson`_
 
-    - Fix channel name pick in :func:`mne.Evoked.get_peak` method by `Alex Gramfort`_
+- Fix channel name pick in :func:`mne.Evoked.get_peak` method by `Alex Gramfort`_
 
-    - Fix drop percentages to take into account ``ignore`` option in :func:`mne.viz.plot_drop_log` and :func:`mne.Epochs.plot_drop_log` by `Eric Larson`_.
+- Fix drop percentages to take into account ``ignore`` option in :func:`mne.viz.plot_drop_log` and :func:`mne.Epochs.plot_drop_log` by `Eric Larson`_.
 
-    - :class:`mne.EpochsArray` no longer has an average EEG reference silently added (but not applied to the data) by default. Use :func:`mne.EpochsArray.add_eeg_ref` to properly add one.
+- :class:`mne.EpochsArray` no longer has an average EEG reference silently added (but not applied to the data) by default. Use ``mne.EpochsArray.add_eeg_ref`` to properly add one.
 
-    - Fix :func:`mne.io.read_raw_ctf` to read ``n_samp_tot`` instead of ``n_samp`` by `Jaakko Leppakangas`_
+- Fix :func:`mne.io.read_raw_ctf` to read ``n_samp_tot`` instead of ``n_samp`` by `Jaakko Leppakangas`_
 
 API
 ~~~
 
-    - :func:`mne.io.read_raw_brainvision` now has ``event_id`` argument to assign non-standard trigger events to a trigger value by `Teon Brooks`_
+- :func:`mne.io.read_raw_brainvision` now has ``event_id`` argument to assign non-standard trigger events to a trigger value by `Teon Brooks`_
 
-    - :func:`mne.read_epochs` now has ``add_eeg_ref=False`` by default, since average EEG reference can be added before writing or after reading using the method :func:`mne.Epochs.add_eeg_ref`.
+- :func:`mne.read_epochs` now has ``add_eeg_ref=False`` by default, since average EEG reference can be added before writing or after reading using the method ``mne.Epochs.add_eeg_ref``.
 
-    - :class:`mne.EpochsArray` no longer has an average EEG reference silently added (but not applied to the data) by default. Use :func:`mne.EpochsArray.add_eeg_average_proj` to properly add one.
+- :class:`mne.EpochsArray` no longer has an average EEG reference silently added (but not applied to the data) by default. Use ``mne.EpochsArray.add_eeg_average_proj`` to properly add one.
 
 Authors
 ~~~~~~~
 
 The committer list for this release is the following (preceded by number of commits):
 
-  * 171  Eric Larson
-  * 117  Jaakko Leppakangas
-  *  58  Jona Sassenhagen
-  *  52  Mainak Jas
-  *  46  Alexandre Gramfort
-  *  33  Denis A. Engemann
-  *  28  Teon Brooks
-  *  24  Clemens Brunner
-  *  23  Christian Brodbeck
-  *  15  Mark Wronkiewicz
-  *  10  Jean-Remi King
-  *   5  Marijn van Vliet
-  *   3  Fede Raimondo
-  *   2  Alexander Rudiuk
-  *   2  emilyps14
-  *   2  lennyvarghese
-  *   1  Marian Dovgialo
+* 171  Eric Larson
+* 117  Jaakko Leppakangas
+*  58  Jona Sassenhagen
+*  52  Mainak Jas
+*  46  Alexandre Gramfort
+*  33  Denis A. Engemann
+*  28  Teon Brooks
+*  24  Clemens Brunner
+*  23  Christian Brodbeck
+*  15  Mark Wronkiewicz
+*  10  Jean-Remi King
+*   5  Marijn van Vliet
+*   3  Fede Raimondo
+*   2  Alexander Rudiuk
+*   2  emilyps14
+*   2  lennyvarghese
+*   1  Marian Dovgialo
 
 .. _changes_0_10:
 
@@ -561,144 +1172,144 @@ Version 0.10
 Changelog
 ~~~~~~~~~
 
-    - Add support for generalized M-way repeated measures ANOVA for fully balanced designs with :func:`mne.stats.f_mway_rm` by `Denis Engemann`_
+- Add support for generalized M-way repeated measures ANOVA for fully balanced designs with :func:`mne.stats.f_mway_rm` by `Denis Engemann`_
 
-    - Add epochs browser to interactively view and manipulate epochs with :func:`mne.viz.plot_epochs` by `Jaakko Leppakangas`_
+- Add epochs browser to interactively view and manipulate epochs with :func:`mne.viz.plot_epochs` by `Jaakko Leppakangas`_
 
-    - Speed up TF-MxNE inverse solver with block coordinate descent by `Daniel Strohmeier`_ and `Yousra Bekhti`_
+- Speed up TF-MxNE inverse solver with block coordinate descent by `Daniel Strohmeier`_ and `Yousra Bekhti`_
 
-    - Speed up zero-phase overlap-add (default) filtering by a factor of up to 2 using linearity by `Ross Maddox`_ and `Eric Larson`_
+- Speed up zero-phase overlap-add (default) filtering by a factor of up to 2 using linearity by `Ross Maddox`_ and `Eric Larson`_
 
-    - Add support for scaling and adjusting the number of channels/time per view by `Jaakko Leppakangas`_
+- Add support for scaling and adjusting the number of channels/time per view by `Jaakko Leppakangas`_
 
-    - Add support to toggle the show/hide state of all sections with a single keypress ('t') in :class:`mne.Report` by `Mainak Jas`_
+- Add support to toggle the show/hide state of all sections with a single keypress ('t') in :class:`mne.report.Report` by `Mainak Jas`_
 
-    - Add support for BEM model creation :func:`mne.make_bem_model` by `Eric Larson`_
+- Add support for BEM model creation :func:`mne.make_bem_model` by `Eric Larson`_
 
-    - Add support for BEM solution computation :func:`mne.make_bem_solution` by `Eric Larson`_
+- Add support for BEM solution computation :func:`mne.make_bem_solution` by `Eric Larson`_
 
-    - Add ICA plotters for raw and epoch components by `Jaakko Leppakangas`_
+- Add ICA plotters for raw and epoch components by `Jaakko Leppakangas`_
 
-    - Add new object :class:`mne.decoding.TimeDecoding` for decoding sensors' evoked response across time by `Jean-Remi King`_
+- Add new object ``mne.decoding.TimeDecoding`` for decoding sensors' evoked response across time by `Jean-Remi King`_
 
-    - Add command ``mne freeview_bem_surfaces`` to quickly check BEM surfaces with Freeview by `Alex Gramfort`_.
+- Add command ``mne freeview_bem_surfaces`` to quickly check BEM surfaces with Freeview by `Alex Gramfort`_.
 
-    - Add support for splitting epochs into multiple files in :func:`mne.Epochs.save` by `Mainak Jas`_ and `Alex Gramfort`_
+- Add support for splitting epochs into multiple files in :func:`mne.Epochs.save` by `Mainak Jas`_ and `Alex Gramfort`_
 
-    - Add support for jointly resampling a raw object and event matrix to avoid issues with resampling status channels by `Marijn van Vliet`_
+- Add support for jointly resampling a raw object and event matrix to avoid issues with resampling status channels by `Marijn van Vliet`_
 
-    - Add new method :class:`mne.preprocessing.Xdawn` for denoising and decoding of ERP/ERF by `Alexandre Barachant`_
+- Add new method :class:`mne.preprocessing.Xdawn` for denoising and decoding of ERP/ERF by `Alexandre Barachant`_
 
-    - Add support for plotting patterns/filters in :class:`mne.decoding.csp.CSP` and :class:`mne.decoding.base.LinearModel` by `Romain Trachel`_
+- Add support for plotting patterns/filters in :class:`mne.decoding.CSP` and :class:`mne.decoding.LinearModel` by `Romain Trachel`_
 
-    - Add new object :class:`mne.decoding.base.LinearModel` for decoding M/EEG data and interpreting coefficients of linear models with patterns attribute by `Romain Trachel`_ and `Alex Gramfort`_
+- Add new object :class:`mne.decoding.LinearModel` for decoding M/EEG data and interpreting coefficients of linear models with patterns attribute by `Romain Trachel`_ and `Alex Gramfort`_
 
-    - Add support to append new channels to an object from a list of other objects by `Chris Holdgraf`_
+- Add support to append new channels to an object from a list of other objects by `Chris Holdgraf`_
 
-    - Add interactive plotting of topomap from time-frequency representation by `Jaakko Leppakangas`_
+- Add interactive plotting of topomap from time-frequency representation by `Jaakko Leppakangas`_
 
-    - Add ``plot_topo`` method to ``Evoked`` object by `Jaakko Leppakangas`_
+- Add ``plot_topo`` method to ``Evoked`` object by `Jaakko Leppakangas`_
 
-    - Add fetcher :mod:`mne.datasets.brainstorm` for datasets used by Brainstorm in their tutorials by `Mainak Jas`_
+- Add fetcher :mod:`mne.datasets.brainstorm <mne.datasets>` for datasets used by Brainstorm in their tutorials by `Mainak Jas`_
 
-    - Add interactive plotting of single trials by right clicking on channel name in epochs browser by `Jaakko Leppakangas`_
+- Add interactive plotting of single trials by right clicking on channel name in epochs browser by `Jaakko Leppakangas`_
 
-    - New logos and logo generation script by `Daniel McCloy`_
+- New logos and logo generation script by `Daniel McCloy`_
 
-    - Add ability to plot topomap with a "skirt" (channels outside of the head circle) by `Marijn van Vliet`_
+- Add ability to plot topomap with a "skirt" (channels outside of the head circle) by `Marijn van Vliet`_
 
-    - Add multiple options to ICA infomax and extended infomax algorithms (number of subgaussian components, computation of bias, iteration status printing), enabling equivalent computations to those performed by EEGLAB by `Jair Montoya Martinez`_
+- Add multiple options to ICA infomax and extended infomax algorithms (number of subgaussian components, computation of bias, iteration status printing), enabling equivalent computations to those performed by EEGLAB by `Jair Montoya Martinez`_
 
-    - Add :func:`mne.Epochs.apply_baseline` method to ``Epochs`` objects by `Teon Brooks`_
+- Add :func:`mne.Epochs.apply_baseline` method to ``Epochs`` objects by `Teon Brooks`_
 
-    - Add ``preload`` argument to :func:`mne.read_epochs` to enable on-demand reads from disk by `Eric Larson`_
+- Add ``preload`` argument to :func:`mne.read_epochs` to enable on-demand reads from disk by `Eric Larson`_
 
-    - Big rewrite of simulation module by `Yousra Bekhti`_, `Mark Wronkiewicz`_, `Eric Larson`_ and `Alex Gramfort`_. Allows to simulate raw with artefacts (ECG, EOG) and evoked data, exploiting the forward solution. See :func:`mne.simulation.simulate_raw`, :func:`mne.simulation.simulate_evoked` and :func:`mne.simulation.simulate_sparse_stc`
+- Big rewrite of simulation module by `Yousra Bekhti`_, `Mark Wronkiewicz`_, `Eric Larson`_ and `Alex Gramfort`_. Allows to simulate raw with artefacts (ECG, EOG) and evoked data, exploiting the forward solution. See :func:`mne.simulation.simulate_raw`, :func:`mne.simulation.simulate_evoked` and :func:`mne.simulation.simulate_sparse_stc`
 
-    - Add :func:`mne.Epochs.load_data` method to :class:`mne.Epochs` by `Teon Brooks`_
+- Add :func:`mne.Epochs.load_data` method to :class:`mne.Epochs` by `Teon Brooks`_
 
-    - Add support for drawing topomaps by selecting an area in :func:`mne.Evoked.plot` by `Jaakko Leppakangas`_
+- Add support for drawing topomaps by selecting an area in :func:`mne.Evoked.plot` by `Jaakko Leppakangas`_
 
-    - Add support for finding peaks in evoked data in :func:`mne.Evoked.plot_topomap` by `Jona Sassenhagen`_ and `Jaakko Leppakangas`_
+- Add support for finding peaks in evoked data in :func:`mne.Evoked.plot_topomap` by `Jona Sassenhagen`_ and `Jaakko Leppakangas`_
 
-    - Add source space morphing in :func:`morph_source_spaces` and :func:`SourceEstimate.to_original_src` by `Eric Larson`_ and `Denis Engemann`_
+- Add source space morphing in :func:`morph_source_spaces` and :func:`SourceEstimate.to_original_src` by `Eric Larson`_ and `Denis Engemann`_
 
-    - Adapt ``corrmap`` function (Viola et al. 2009) to semi-automatically detect similar ICs across data sets by `Jona Sassenhagen`_ and `Denis Engemann`_ and `Eric Larson`_
+- Adapt ``corrmap`` function (Viola et al. 2009) to semi-automatically detect similar ICs across data sets by `Jona Sassenhagen`_ and `Denis Engemann`_ and `Eric Larson`_
 
-    - New ``mne flash_bem`` command to compute BEM surfaces from Flash MRI images by `Lorenzo Desantis`_, `Alex Gramfort`_ and `Eric Larson`_. See :func:`mne.bem.utils.make_flash_bem`.
+- New ``mne flash_bem`` command to compute BEM surfaces from Flash MRI images by `Lorenzo Desantis`_, `Alex Gramfort`_ and `Eric Larson`_. See :func:`mne.bem.make_flash_bem`.
 
-    - New gfp parameter in :func:`mne.Evoked.plot` method to display Global Field Power (GFP) by `Eric Larson`_.
+- New gfp parameter in :func:`mne.Evoked.plot` method to display Global Field Power (GFP) by `Eric Larson`_.
 
-    - Add :func:`mne.Report.add_slider_to_section` methods to :class:`mne.Report` by `Teon Brooks`_
+- Add :meth:`mne.report.Report.add_slider_to_section` methods to :class:`mne.report.Report` by `Teon Brooks`_
 
 BUG
 ~~~
 
-    - Fix ``mne.io.add_reference_channels`` not setting ``info[nchan]`` correctly by `Federico Raimondo`_
+- Fix ``mne.io.add_reference_channels`` not setting ``info[nchan]`` correctly by `Federico Raimondo`_
 
-    - Fix ``mne.stats.bonferroni_correction`` reject mask output to use corrected p-values by `Denis Engemann`_
+- Fix ``mne.stats.bonferroni_correction`` reject mask output to use corrected p-values by `Denis Engemann`_
 
-    - Fix FFT filter artifacts when using short windows in overlap-add by `Eric Larson`_
+- Fix FFT filter artifacts when using short windows in overlap-add by `Eric Larson`_
 
-    - Fix picking channels from forward operator could return a channel ordering different from ``info['chs']`` by `Chris Bailey`_
+- Fix picking channels from forward operator could return a channel ordering different from ``info['chs']`` by `Chris Bailey`_
 
-    - Fix dropping of events after downsampling stim channels by `Marijn van Vliet`_
+- Fix dropping of events after downsampling stim channels by `Marijn van Vliet`_
 
-    - Fix scaling in :func:``mne.viz.utils._setup_vmin_vmax`` by `Jaakko Leppakangas`_
+- Fix scaling in :func:``mne.viz.utils._setup_vmin_vmax`` by `Jaakko Leppakangas`_
 
-    - Fix order of component selection in :class:`mne.decoding.csp.CSP` by `Clemens Brunner`_
+- Fix order of component selection in :class:`mne.decoding.CSP` by `Clemens Brunner`_
 
 API
 ~~~
 
-    - Rename and deprecate ``mne.viz.plot_topo`` for ``mne.viz.plot_evoked_topo`` by `Jaakko Leppakangas`_
+- Rename and deprecate ``mne.viz.plot_topo`` for ``mne.viz.plot_evoked_topo`` by `Jaakko Leppakangas`_
 
-    - Deprecated :class: `mne.decoding.transformer.ConcatenateChannels` and replaced by :class: `mne.decoding.transformer.EpochsVectorizer` by `Romain Trachel`_
+- Deprecated :class: `mne.decoding.transformer.ConcatenateChannels` and replaced by :class: `mne.decoding.transformer.EpochsVectorizer` by `Romain Trachel`_
 
-    - Deprecated `lws` and renamed `ledoit_wolf` for the ``reg`` argument in :class:`mne.decoding.csp.CSP` by `Romain Trachel`_
+- Deprecated `lws` and renamed `ledoit_wolf` for the ``reg`` argument in :class:`mne.decoding.CSP` by `Romain Trachel`_
 
-    - Redesigned and rewrote :func:`mne.Epochs.plot` (no backwards compatibility) during the GSOC 2015 by `Jaakko Leppakangas`_, `Mainak Jas`_, `Federico Raimondo`_ and `Denis Engemann`_
+- Redesigned and rewrote :meth:`mne.Epochs.plot` (no backwards compatibility) during the GSOC 2015 by `Jaakko Leppakangas`_, `Mainak Jas`_, `Federico Raimondo`_ and `Denis Engemann`_
 
-    - Deprecated and renamed :func:`mne.viz.plot_image_epochs` for :func:`mne.plot.plot_epochs_image` by `Teon Brooks`_
+- Deprecated and renamed ``mne.viz.plot_image_epochs`` for ``mne.plot.plot_epochs_image`` by `Teon Brooks`_
 
-    - ``picks`` argument has been added to :func:`mne.time_frequency.tfr_morlet`, :func:`mne.time_frequency.tfr_multitaper` by `Teon Brooks`_
+- ``picks`` argument has been added to :func:`mne.time_frequency.tfr_morlet`, :func:`mne.time_frequency.tfr_multitaper` by `Teon Brooks`_
 
-    - :func:`mne.io.Raw.preload_data` has been deprecated for :func:`mne.io.Raw.load_data` by `Teon Brooks`_
+- ``mne.io.Raw.preload_data`` has been deprecated for :func:`mne.io.Raw.load_data` by `Teon Brooks`_
 
-    - ``RawBrainVision`` objects now always have event channel ``'STI 014'``, and recordings with no events will have this channel set to zero by `Eric Larson`_
+- ``RawBrainVision`` objects now always have event channel ``'STI 014'``, and recordings with no events will have this channel set to zero by `Eric Larson`_
 
 Authors
 ~~~~~~~
 
 The committer list for this release is the following (preceded by number of commits):
 
-   * 273  Eric Larson
-   * 270  Jaakko Leppakangas
-   * 194  Alexandre Gramfort
-   * 128  Denis A. Engemann
-   * 114  Jona Sassenhagen
-   * 107  Mark Wronkiewicz
-   *  97  Teon Brooks
-   *  81  Lorenzo De Santis
-   *  55  Yousra Bekhti
-   *  54  Jean-Remi King
-   *  48  Romain Trachel
-   *  45  Mainak Jas
-   *  40  Alexandre Barachant
-   *  32  Marijn van Vliet
-   *  26  Jair Montoya
-   *  22  Chris Holdgraf
-   *  16  Christopher J. Bailey
-   *   7  Christian Brodbeck
-   *   5  Natalie Klein
-   *   5  Fede Raimondo
-   *   5  Alan Leggitt
-   *   5  Roan LaPlante
-   *   5  Ross Maddox
-   *   4  Dan G. Wakeman
-   *   3  Daniel McCloy
-   *   3  Daniel Strohmeier
-   *   1  Jussi Nurminen
+* 273  Eric Larson
+* 270  Jaakko Leppakangas
+* 194  Alexandre Gramfort
+* 128  Denis A. Engemann
+* 114  Jona Sassenhagen
+* 107  Mark Wronkiewicz
+*  97  Teon Brooks
+*  81  Lorenzo De Santis
+*  55  Yousra Bekhti
+*  54  Jean-Remi King
+*  48  Romain Trachel
+*  45  Mainak Jas
+*  40  Alexandre Barachant
+*  32  Marijn van Vliet
+*  26  Jair Montoya
+*  22  Chris Holdgraf
+*  16  Christopher J. Bailey
+*   7  Christian Brodbeck
+*   5  Natalie Klein
+*   5  Fede Raimondo
+*   5  Alan Leggitt
+*   5  Roan LaPlante
+*   5  Ross Maddox
+*   4  Dan G. Wakeman
+*   3  Daniel McCloy
+*   3  Daniel Strohmeier
+*   1  Jussi Nurminen
 
 .. _changes_0_9:
 
@@ -708,238 +1319,237 @@ Version 0.9
 Changelog
 ~~~~~~~~~
 
-   - Add support for mayavi figures in ``add_section`` method in Report by `Mainak Jas`_
+- Add support for mayavi figures in ``add_section`` method in Report by `Mainak Jas`_
 
-   - Add extract volumes of interest from freesurfer segmentation and setup as volume source space by `Alan Leggitt`_
+- Add extract volumes of interest from freesurfer segmentation and setup as volume source space by `Alan Leggitt`_
 
-   - Add support to combine source spaces of different types by `Alan Leggitt`_
+- Add support to combine source spaces of different types by `Alan Leggitt`_
 
-   - Add support for source estimate for mixed source spaces by `Alan Leggitt`_
+- Add support for source estimate for mixed source spaces by `Alan Leggitt`_
 
-   - Add ``SourceSpaces.save_as_volume`` method by `Alan Leggitt`_
+- Add ``SourceSpaces.save_as_volume`` method by `Alan Leggitt`_
 
-   - Automatically compute proper box sizes when generating layouts on the fly by `Marijn van Vliet`_
+- Automatically compute proper box sizes when generating layouts on the fly by `Marijn van Vliet`_
 
-   - Average evoked topographies across time points by `Denis Engemann`_
+- Average evoked topographies across time points by `Denis Engemann`_
 
-   - Add option to Report class to save images as vector graphics (SVG) by `Denis Engemann`_
+- Add option to Report class to save images as vector graphics (SVG) by `Denis Engemann`_
 
-   - Add events count to ``mne.viz.plot_events`` by `Denis Engemann`_
+- Add events count to ``mne.viz.plot_events`` by `Denis Engemann`_
 
-   - Add support for stereotactic EEG (sEEG) channel type by `Marmaduke Woodman`_
+- Add support for stereotactic EEG (sEEG) channel type by `Marmaduke Woodman`_
 
-   - Add support for montage files by `Denis Engemann`_, `Marijn van Vliet`_, `Jona Sassenhagen`_, `Alex Gramfort`_ and `Teon Brooks`_
+- Add support for montage files by `Denis Engemann`_, `Marijn van Vliet`_, `Jona Sassenhagen`_, `Alex Gramfort`_ and `Teon Brooks`_
 
-   - Add support for spatiotemporal permutation clustering on sensors by `Denis Engemann`_
+- Add support for spatiotemporal permutation clustering on sensors by `Denis Engemann`_
 
-   - Add support for multitaper time-frequency analysis by `Hari Bharadwaj`_
+- Add support for multitaper time-frequency analysis by `Hari Bharadwaj`_
 
-   - Add Stockwell (S) transform for time-frequency representations by `Denis Engemann`_ and `Alex Gramfort`_
+- Add Stockwell (S) transform for time-frequency representations by `Denis Engemann`_ and `Alex Gramfort`_
 
-   - Add reading and writing support for time frequency data (AverageTFR objects) by  `Denis Engemann`_
+- Add reading and writing support for time frequency data (AverageTFR objects) by  `Denis Engemann`_
 
-   - Add reading and writing support for digitizer data, and function for adding dig points to info by `Teon Brooks`_
+- Add reading and writing support for digitizer data, and function for adding dig points to info by `Teon Brooks`_
 
-   - Add  ``plot_projs_topomap`` method to ``Raw``, ``Epochs`` and ``Evoked`` objects by `Teon Brooks`_
+- Add  ``plot_projs_topomap`` method to ``Raw``, ``Epochs`` and ``Evoked`` objects by `Teon Brooks`_
 
-   - Add EEG (based on spherical splines) and MEG (based on field interpolation) bad channel interpolation method to ``Raw``, ``Epochs`` and ``Evoked`` objects
-     by `Denis Engemann`_ and `Mainak Jas`_
+- Add EEG (based on spherical splines) and MEG (based on field interpolation) bad channel interpolation method to ``Raw``, ``Epochs`` and ``Evoked`` objects by `Denis Engemann`_ and `Mainak Jas`_
 
-   - Add parameter to ``whiten_evoked``, ``compute_whitener`` and ``prepare_noise_cov`` to set the exact rank by `Martin Luessi`_ and `Denis Engemann`_
+- Add parameter to ``whiten_evoked``, ``compute_whitener`` and ``prepare_noise_cov`` to set the exact rank by `Martin Luessi`_ and `Denis Engemann`_
 
-   - Add fiff I/O for processing history and MaxFilter info by `Denis Engemann`_ and `Eric Larson`_
+- Add fiff I/O for processing history and MaxFilter info by `Denis Engemann`_ and `Eric Larson`_
 
-   - Add automated regularization with support for multiple sensor types to ``compute_covariance`` by `Denis Engemann`_ and `Alex Gramfort`_
+- Add automated regularization with support for multiple sensor types to ``compute_covariance`` by `Denis Engemann`_ and `Alex Gramfort`_
 
-   - Add ``Evoked.plot_white`` method to diagnose the quality of the estimated noise covariance and its impact on spatial whitening by `Denis Engemann`_ and `Alex Gramfort`_
+- Add ``Evoked.plot_white`` method to diagnose the quality of the estimated noise covariance and its impact on spatial whitening by `Denis Engemann`_ and `Alex Gramfort`_
 
-   - Add ``mne.evoked.grand_average`` function to compute grand average of Evoked data while interpolating bad EEG channels if necessary by `Mads Jensen`_ and `Alex Gramfort`_
+- Add ``mne.evoked.grand_average`` function to compute grand average of Evoked data while interpolating bad EEG channels if necessary by `Mads Jensen`_ and `Alex Gramfort`_
 
-   - Improve EEG referencing support and add support for bipolar referencing by `Marijn van Vliet`_ and `Alex Gramfort`_
+- Improve EEG referencing support and add support for bipolar referencing by `Marijn van Vliet`_ and `Alex Gramfort`_
 
-   - Enable TFR calculation on Evoked objects by `Eric Larson`_
+- Enable TFR calculation on Evoked objects by `Eric Larson`_
 
-   - Add support for combining Evoked datasets with arbitrary weights (e.g., for oddball paradigms) by `Eric Larson`_ and `Alex Gramfort`_
+- Add support for combining Evoked datasets with arbitrary weights (e.g., for oddball paradigms) by `Eric Larson`_ and `Alex Gramfort`_
 
-   - Add support for concatenating a list of Epochs objects by `Denis Engemann`_
+- Add support for concatenating a list of Epochs objects by `Denis Engemann`_
 
-   - Labels support subtraction (``label_1 - label_2``) by `Christian Brodbeck`_
+- Labels support subtraction (``label_1 - label_2``) by `Christian Brodbeck`_
 
-   - Add GeneralizationAcrossTime object with support for cross-condition generalization by `Jean-Remi King`_ and `Denis Engemann`_
+- Add GeneralizationAcrossTime object with support for cross-condition generalization by `Jean-Remi King`_ and `Denis Engemann`_
 
-   - Add support for single dipole fitting by `Eric Larson`_
+- Add support for single dipole fitting by `Eric Larson`_
 
-   - Add support for spherical models in forward calculations by `Eric Larson`_
+- Add support for spherical models in forward calculations by `Eric Larson`_
 
-   - Add support for SNR estimation by `Eric Larson`_
+- Add support for SNR estimation by `Eric Larson`_
 
-   - Add support for Savitsky-Golay filtering of Evoked and Epochs by `Eric Larson`_
+- Add support for Savitsky-Golay filtering of Evoked and Epochs by `Eric Larson`_
 
-   - Add support for adding an empty reference channel to data by `Teon Brooks`_
+- Add support for adding an empty reference channel to data by `Teon Brooks`_
 
-   - Add reader function ``mne.io.read_raw_fif`` for Raw FIF files by `Teon Brooks`_
+- Add reader function ``mne.io.read_raw_fif`` for Raw FIF files by `Teon Brooks`_
 
-   - Add example of creating MNE objects from arbitrary data and NEO files by `Jaakko Leppakangas`_
+- Add example of creating MNE objects from arbitrary data and NEO files by `Jaakko Leppakangas`_
 
-   - Add ``plot_psd`` and ``plot_psd_topomap`` methods to epochs by `Yousra Bekhti`_, `Eric Larson`_ and `Denis Engemann`_
+- Add ``plot_psd`` and ``plot_psd_topomap`` methods to epochs by `Yousra Bekhti`_, `Eric Larson`_ and `Denis Engemann`_
 
-   - ``evoked.pick_types``, ``epochs.pick_types``, and ``tfr.pick_types`` added by `Eric Larson`_
+- ``evoked.pick_types``, ``epochs.pick_types``, and ``tfr.pick_types`` added by `Eric Larson`_
 
-   - ``rename_channels`` and ``set_channel_types`` added as methods to ``Raw``, ``Epochs`` and ``Evoked`` objects by `Teon Brooks`_
+- ``rename_channels`` and ``set_channel_types`` added as methods to ``Raw``, ``Epochs`` and ``Evoked`` objects by `Teon Brooks`_
 
-   - Add RAP-MUSIC inverse method by `Yousra Bekhti`_ and `Alex Gramfort`_
+- Add RAP-MUSIC inverse method by `Yousra Bekhti`_ and `Alex Gramfort`_
 
-   - Add ``evoked.as_type`` to  allow remapping data in MEG channels to virtual magnetometer or gradiometer channels by `Mainak Jas`_
+- Add ``evoked.as_type`` to  allow remapping data in MEG channels to virtual magnetometer or gradiometer channels by `Mainak Jas`_
 
-   - Add :func:`mne.Report.add_bem_to_section`, :func:`mne.Report.add_htmls_to_section` methods to :class:`mne.Report` by `Teon Brooks`_
+- Add :meth:`mne.report.Report.add_bem_to_section`, :meth:`mne.report.Report.add_htmls_to_section` methods to :class:`mne.report.Report` by `Teon Brooks`_
 
-   - Add support for KIT epochs files with ``read_epochs_kit`` by `Teon Brooks`_
+- Add support for KIT epochs files with ``read_epochs_kit`` by `Teon Brooks`_
 
-   - Add whitening plots for evokeds to ``mne.Report`` by `Mainak Jas`_
+- Add whitening plots for evokeds to ``mne.Report`` by `Mainak Jas`_
 
-   - Add ``DigMontage`` class and reader to interface with digitization info by `Teon Brooks`_ and `Christian Brodbeck`_
+- Add ``DigMontage`` class and reader to interface with digitization info by `Teon Brooks`_ and `Christian Brodbeck`_
 
-   - Add ``set_montage`` method to the ``Raw``, ``Epochs``, and ``Evoked`` objects by `Teon Brooks`_ and `Denis Engemann`_
+- Add ``set_montage`` method to the ``Raw``, ``Epochs``, and ``Evoked`` objects by `Teon Brooks`_ and `Denis Engemann`_
 
-   - Add support for capturing sensor positions when clicking on an image by `Chris Holdgraf`_
+- Add support for capturing sensor positions when clicking on an image by `Chris Holdgraf`_
 
-   - Add support for custom sensor positions when creating Layout objects by `Chris Holdgraf`_
+- Add support for custom sensor positions when creating Layout objects by `Chris Holdgraf`_
 
 BUG
 ~~~
 
-   - Fix energy conservation for STFT with tight frames by `Daniel Strohmeier`_
+- Fix energy conservation for STFT with tight frames by `Daniel Strohmeier`_
 
-   - Fix incorrect data matrix when tfr was plotted with parameters ``tmin``, ``tmax``, ``fmin`` and ``fmax`` by `Mainak Jas`_
+- Fix incorrect data matrix when tfr was plotted with parameters ``tmin``, ``tmax``, ``fmin`` and ``fmax`` by `Mainak Jas`_
 
-   - Fix channel names in topomaps by `Alex Gramfort`_
+- Fix channel names in topomaps by `Alex Gramfort`_
 
-   - Fix mapping of ``l_trans_bandwidth`` (to low frequency) and ``h_trans_bandwidth`` (to high frequency) in ``_BaseRaw.filter`` by `Denis Engemann`_
+- Fix mapping of ``l_trans_bandwidth`` (to low frequency) and ``h_trans_bandwidth`` (to high frequency) in ``_BaseRaw.filter`` by `Denis Engemann`_
 
-   - Fix scaling source spaces when distances have to be recomputed by `Christian Brodbeck`_
+- Fix scaling source spaces when distances have to be recomputed by `Christian Brodbeck`_
 
-   - Fix repeated samples in client to FieldTrip buffer by `Mainak Jas`_ and `Federico Raimondo`_
+- Fix repeated samples in client to FieldTrip buffer by `Mainak Jas`_ and `Federico Raimondo`_
 
-   - Fix highpass and lowpass units read from Brainvision vhdr files by `Alex Gramfort`_
+- Fix highpass and lowpass units read from Brainvision vhdr files by `Alex Gramfort`_
 
-   - Add missing attributes for BrainVision and KIT systems needed for resample by `Teon Brooks`_
+- Add missing attributes for BrainVision and KIT systems needed for resample by `Teon Brooks`_
 
-   - Fix file extensions of SSP projection files written by mne commands (from _proj.fif to -prof.fif) by `Alex Gramfort`_
+- Fix file extensions of SSP projection files written by mne commands (from _proj.fif to -prof.fif) by `Alex Gramfort`_
 
-   - Generating EEG layouts no longer requires digitization points by `Marijn van Vliet`_
+- Generating EEG layouts no longer requires digitization points by `Marijn van Vliet`_
 
-   - Add missing attributes to BTI, KIT, and BrainVision by `Eric Larson`_
+- Add missing attributes to BTI, KIT, and BrainVision by `Eric Larson`_
 
-   - The API change to the edf, brainvision, and egi break backwards compatibility for when importing eeg data by `Teon Brooks`_
+- The API change to the edf, brainvision, and egi break backwards compatibility for when importing eeg data by `Teon Brooks`_
 
-   - Fix bug in ``mne.viz.plot_topo`` if ylim was passed for single sensor layouts by `Denis Engemann`_
+- Fix bug in ``mne.viz.plot_topo`` if ylim was passed for single sensor layouts by `Denis Engemann`_
 
-   - Average reference projections will no longer by automatically added after applying a custom EEG reference by `Marijn van Vliet`_
+- Average reference projections will no longer by automatically added after applying a custom EEG reference by `Marijn van Vliet`_
 
-   - Fix picks argument to filter in n dimensions (affects FilterEstimator), and highpass filter in FilterEstimator by `Mainak Jas`_
+- Fix picks argument to filter in n dimensions (affects FilterEstimator), and highpass filter in FilterEstimator by `Mainak Jas`_
 
-   - Fix beamformer code LCMV/DICS for CTF data with reference channels by `Denis Engemann`_ and `Alex Gramfort`_
+- Fix beamformer code LCMV/DICS for CTF data with reference channels by `Denis Engemann`_ and `Alex Gramfort`_
 
-   - Fix scalings for bad EEG channels in ``mne.viz.plot_topo`` by `Marijn van Vliet`_
+- Fix scalings for bad EEG channels in ``mne.viz.plot_topo`` by `Marijn van Vliet`_
 
-   - Fix EGI reading when no events are present by `Federico Raimondo`_
+- Fix EGI reading when no events are present by `Federico Raimondo`_
 
-   - Add functionality to determine plot limits automatically or by data percentiles by `Mark Wronkiewicz`_
+- Add functionality to determine plot limits automatically or by data percentiles by `Mark Wronkiewicz`_
 
-   - Fix bug in mne.io.edf where the channel offsets were omitted in the voltage calculations by `Teon Brooks`_
+- Fix bug in mne.io.edf where the channel offsets were omitted in the voltage calculations by `Teon Brooks`_
 
-   - Decouple section ordering in command-line from python interface for mne-report by `Mainak Jas`_
+- Decouple section ordering in command-line from python interface for mne-report by `Mainak Jas`_
 
-   - Fix bug with ICA resetting by `Denis Engemann`_
+- Fix bug with ICA resetting by `Denis Engemann`_
 
 API
 ~~~
 
-   - apply_inverse functions have a new boolean parameter ``prepared`` which saves computation time by calling ``prepare_inverse_operator`` only if it is False
+- apply_inverse functions have a new boolean parameter ``prepared`` which saves computation time by calling ``prepare_inverse_operator`` only if it is False
 
-   - find_events and read_events functions have a new parameter ``mask`` to set some bits to a don't care state by `Teon Brooks`_
+- find_events and read_events functions have a new parameter ``mask`` to set some bits to a don't care state by `Teon Brooks`_
 
-   - New channels module including layouts, electrode montages, and neighbor definitions of sensors which deprecates ``mne.layouts`` by `Denis Engemann`_
+- New channels module including layouts, electrode montages, and neighbor definitions of sensors which deprecates ``mne.layouts`` by `Denis Engemann`_
 
-   - ``read_raw_brainvision``, ``read_raw_edf``, ``read_raw_egi`` all use a standard montage import by `Teon Brooks`_
+- ``read_raw_brainvision``, ``read_raw_edf``, ``read_raw_egi`` all use a standard montage import by `Teon Brooks`_
 
-   - Fix missing calibration factors for ``mne.io.egi.read_raw_egi`` by `Denis Engemann`_ and `Federico Raimondo`_
+- Fix missing calibration factors for ``mne.io.egi.read_raw_egi`` by `Denis Engemann`_ and `Federico Raimondo`_
 
-   - Allow multiple filename patterns as a list (e.g., \*raw.fif and \*-eve.fif) to be parsed by mne report in ``Report.parse_folder()`` by `Mainak Jas`_
+- Allow multiple filename patterns as a list (e.g., \*raw.fif and \*-eve.fif) to be parsed by mne report in ``Report.parse_folder()`` by `Mainak Jas`_
 
-   - ``read_hsp``, ``read_elp``, and ``write_hsp``, ``write_mrk`` were removed and made private by `Teon Brooks`_
+- ``read_hsp``, ``read_elp``, and ``write_hsp``, ``write_mrk`` were removed and made private by `Teon Brooks`_
 
-   - When computing the noise covariance or MNE inverse solutions, the rank is estimated empirically using more sensitive thresholds, which stabilizes results by `Denis Engemann`_ and `Eric Larson`_ and `Alex Gramfort`_
+- When computing the noise covariance or MNE inverse solutions, the rank is estimated empirically using more sensitive thresholds, which stabilizes results by `Denis Engemann`_ and `Eric Larson`_ and `Alex Gramfort`_
 
-   - Raw FIFF files can be preloaded after class instantiation using ``raw.preload_data()``
+- Raw FIFF files can be preloaded after class instantiation using ``raw.preload_data()``
 
-   - Add ``label`` parameter to ``apply_inverse`` by `Teon Brooks`_
+- Add ``label`` parameter to ``apply_inverse`` by `Teon Brooks`_
 
-   - Deprecated ``label_time_courses`` for ``in_label`` method in `SourceEstimate` by `Teon Brooks`_
+- Deprecated ``label_time_courses`` for ``in_label`` method in `SourceEstimate` by `Teon Brooks`_
 
-   - Deprecated ``as_data_frame`` for ``to_data_frame`` by `Chris Holdgraf`_
+- Deprecated ``as_data_frame`` for ``to_data_frame`` by `Chris Holdgraf`_
 
-   - Add ``transform``, ``unit`` parameters to ``read_montage`` by `Teon Brooks`_
+- Add ``transform``, ``unit`` parameters to ``read_montage`` by `Teon Brooks`_
 
-   - Deprecated ``fmin, fmid, fmax`` in stc.plot and added ``clim`` by `Mark Wronkiewicz`_
+- Deprecated ``fmin, fmid, fmax`` in stc.plot and added ``clim`` by `Mark Wronkiewicz`_
 
-   - Use ``scipy.signal.welch`` instead of matplotlib.psd inside ``compute_raw_psd`` and ``compute_epochs_psd`` by `Yousra Bekhti`_ `Eric Larson`_ and `Denis Engemann`_. As a consquence, ``Raw.plot_raw_psds`` has been deprecated.
+- Use ``scipy.signal.welch`` instead of matplotlib.psd inside ``compute_raw_psd`` and ``compute_epochs_psd`` by `Yousra Bekhti`_ `Eric Larson`_ and `Denis Engemann`_. As a consquence, ``Raw.plot_raw_psds`` has been deprecated.
 
-   - ``Raw`` instances returned by ``mne.forward.apply_forward_raw`` now always have times starting from
-     zero to be consistent with all other ``Raw`` instances. To get the former ``start`` and ``stop`` times,
-     use ``raw.first_samp / raw.info['sfreq']`` and ``raw.last_samp / raw.info['sfreq']``.
+- ``Raw`` instances returned by ``mne.forward.apply_forward_raw`` now always have times starting from
+  zero to be consistent with all other ``Raw`` instances. To get the former ``start`` and ``stop`` times,
+  use ``raw.first_samp / raw.info['sfreq']`` and ``raw.last_samp / raw.info['sfreq']``.
 
-   - ``pick_types_evoked`` has been deprecated in favor of ``evoked.pick_types``.
+- ``pick_types_evoked`` has been deprecated in favor of ``evoked.pick_types``.
 
-   - Deprecated changing the sensor type of channels in ``rename_channels`` by `Teon Brooks`_
+- Deprecated changing the sensor type of channels in ``rename_channels`` by `Teon Brooks`_
 
-   - CUDA is no longer initialized at module import, but only when first used.
+- CUDA is no longer initialized at module import, but only when first used.
 
-   - ``add_figs_to_section`` and ``add_images_to_section`` now have a ``textbox`` parameter to add comments to the image by `Teon Brooks`_
+- ``add_figs_to_section`` and ``add_images_to_section`` now have a ``textbox`` parameter to add comments to the image by `Teon Brooks`_
 
-   - Deprecated ``iir_filter_raw`` for ``fit_iir_model_raw``.
+- Deprecated ``iir_filter_raw`` for ``fit_iir_model_raw``.
 
-   - Add ``montage`` parameter to the ``create_info`` function to create the info using montages by `Teon Brooks`_
+- Add ``montage`` parameter to the ``create_info`` function to create the info using montages by `Teon Brooks`_
 
 Authors
 ~~~~~~~
 
 The committer list for this release is the following (preceded by number of commits):
 
-   * 515  Eric Larson
-   * 343  Denis A. Engemann
-   * 304  Alexandre Gramfort
-   * 300  Teon Brooks
-   * 142  Mainak Jas
-   * 119  Jean-Remi King
-   *  77  Alan Leggitt
-   *  75  Marijn van Vliet
-   *  63  Chris Holdgraf
-   *  57  Yousra Bekhti
-   *  49  Mark Wronkiewicz
-   *  44  Christian Brodbeck
-   *  30  Jona Sassenhagen
-   *  29  Hari Bharadwaj
-   *  27  Clément Moutard
-   *  24  Ingoo Lee
-   *  18  Marmaduke Woodman
-   *  16  Martin Luessi
-   *  10  Jaakko Leppakangas
-   *   9  Andrew Dykstra
-   *   9  Daniel Strohmeier
-   *   7  kjs
-   *   6  Dan G. Wakeman
-   *   5  Federico Raimondo
-   *   3  Basile Pinsard
-   *   3  Christoph Dinh
-   *   3  Hafeza Anevar
-   *   2  Martin Billinger
-   *   2  Roan LaPlante
-   *   1  Manoj Kumar
-   *   1  Matt Tucker
-   *   1  Romain Trachel
-   *   1  mads jensen
-   *   1  sviter
+* 515  Eric Larson
+* 343  Denis A. Engemann
+* 304  Alexandre Gramfort
+* 300  Teon Brooks
+* 142  Mainak Jas
+* 119  Jean-Remi King
+*  77  Alan Leggitt
+*  75  Marijn van Vliet
+*  63  Chris Holdgraf
+*  57  Yousra Bekhti
+*  49  Mark Wronkiewicz
+*  44  Christian Brodbeck
+*  30  Jona Sassenhagen
+*  29  Hari Bharadwaj
+*  27  Clément Moutard
+*  24  Ingoo Lee
+*  18  Marmaduke Woodman
+*  16  Martin Luessi
+*  10  Jaakko Leppakangas
+*   9  Andrew Dykstra
+*   9  Daniel Strohmeier
+*   7  kjs
+*   6  Dan G. Wakeman
+*   5  Federico Raimondo
+*   3  Basile Pinsard
+*   3  Christoph Dinh
+*   3  Hafeza Anevar
+*   2  Martin Billinger
+*   2  Roan LaPlante
+*   1  Manoj Kumar
+*   1  Matt Tucker
+*   1  Romain Trachel
+*   1  mads jensen
+*   1  sviter
 
 .. _changes_0_8:
 
@@ -949,192 +1559,192 @@ Version 0.8
 Changelog
 ~~~~~~~~~
 
-   - Add Python3 support by `Nick Ward`_, `Alex Gramfort`_, `Denis Engemann`_, and `Eric Larson`_
+- Add Python3 support by `Nick Ward`_, `Alex Gramfort`_, `Denis Engemann`_, and `Eric Larson`_
 
-   - Add ``get_peak`` method for evoked and stc objects by  `Denis Engemann`_
+- Add ``get_peak`` method for evoked and stc objects by  `Denis Engemann`_
 
-   - Add ``iter_topography`` function for radically simplified custom sensor topography plotting by `Denis Engemann`_
+- Add ``iter_topography`` function for radically simplified custom sensor topography plotting by `Denis Engemann`_
 
-   - Add field line interpolation by `Eric Larson`_
+- Add field line interpolation by `Eric Larson`_
 
-   - Add full provenance tacking for epochs and improve ``drop_log`` by `Tal Linzen`_, `Alex Gramfort`_ and `Denis Engemann`_
+- Add full provenance tacking for epochs and improve ``drop_log`` by `Tal Linzen`_, `Alex Gramfort`_ and `Denis Engemann`_
 
-   - Add systematic contains method to ``Raw``, ``Epochs`` and ``Evoked`` for channel type membership testing by `Denis Engemann`_
+- Add systematic contains method to ``Raw``, ``Epochs`` and ``Evoked`` for channel type membership testing by `Denis Engemann`_
 
-   - Add fiff unicode writing and reading support by `Denis Engemann`_
+- Add fiff unicode writing and reading support by `Denis Engemann`_
 
-   - Add 3D MEG/EEG field plotting function and evoked method by `Denis Engemann`_ and  `Alex Gramfort`_
+- Add 3D MEG/EEG field plotting function and evoked method by `Denis Engemann`_ and  `Alex Gramfort`_
 
-   - Add consistent channel-dropping methods to ``Raw``, ``Epochs`` and ``Evoked`` by `Denis Engemann`_ and  `Alex Gramfort`_
+- Add consistent channel-dropping methods to ``Raw``, ``Epochs`` and ``Evoked`` by `Denis Engemann`_ and  `Alex Gramfort`_
 
-   - Add ``equalize_channnels`` function to set common channels for a list of ``Raw``, ``Epochs``, or ``Evoked`` objects by `Denis Engemann`_
+- Add ``equalize_channnels`` function to set common channels for a list of ``Raw``, ``Epochs``, or ``Evoked`` objects by `Denis Engemann`_
 
-   - Add ``plot_events`` function to visually display paradigm by `Alex Gramfort`_
+- Add ``plot_events`` function to visually display paradigm by `Alex Gramfort`_
 
-   - Improved connectivity circle plot by `Martin Luessi`_
+- Improved connectivity circle plot by `Martin Luessi`_
 
-   - Add ability to anonymize measurement info by `Eric Larson`_
+- Add ability to anonymize measurement info by `Eric Larson`_
 
-   - Add callback to connectivity circle plot to isolate connections to clicked nodes `Roan LaPlante`_
+- Add callback to connectivity circle plot to isolate connections to clicked nodes `Roan LaPlante`_
 
-   - Add ability to add patch information to source spaces by `Eric Larson`_
+- Add ability to add patch information to source spaces by `Eric Larson`_
 
-   - Add ``split_label`` function to divide labels into multiple parts by `Christian Brodbeck`_
+- Add ``split_label`` function to divide labels into multiple parts by `Christian Brodbeck`_
 
-   - Add ``color`` attribute to ``Label`` objects by `Christian Brodbeck`_
+- Add ``color`` attribute to ``Label`` objects by `Christian Brodbeck`_
 
-   - Add ``max`` mode for ``extract_label_time_course`` by `Mads Jensen`_
+- Add ``max`` mode for ``extract_label_time_course`` by `Mads Jensen`_
 
-   - Add ``rename_channels`` function to change channel names and types in info object by `Dan Wakeman`_ and `Denis Engemann`_
+- Add ``rename_channels`` function to change channel names and types in info object by `Dan Wakeman`_ and `Denis Engemann`_
 
-   - Add  ``compute_ems`` function to extract the time course of experimental effects by `Denis Engemann`_, `Sébastien Marti`_ and `Alex Gramfort`_
+- Add  ``compute_ems`` function to extract the time course of experimental effects by `Denis Engemann`_, `Sébastien Marti`_ and `Alex Gramfort`_
 
-   - Add option to expand Labels defined in a source space to the original surface (``Label.fill()``) by `Christian Brodbeck`_
+- Add option to expand Labels defined in a source space to the original surface (``Label.fill()``) by `Christian Brodbeck`_
 
-   - GUIs can be invoked form the command line using `$ mne coreg` and `$ mne kit2fiff` by `Christian Brodbeck`_
+- GUIs can be invoked form the command line using `$ mne coreg` and `$ mne kit2fiff` by `Christian Brodbeck`_
 
-   - Add ``add_channels_epochs`` function to combine different recordings at the Epochs level by `Christian Brodbeck`_ and `Denis Engemann`_
+- Add ``add_channels_epochs`` function to combine different recordings at the Epochs level by `Christian Brodbeck`_ and `Denis Engemann`_
 
-   - Add support for EGI Netstation simple binary files by `Denis Engemann`_
+- Add support for EGI Netstation simple binary files by `Denis Engemann`_
 
-   - Add support for treating arbitrary data (numpy ndarray) as a Raw instance by `Eric Larson`_
+- Add support for treating arbitrary data (numpy ndarray) as a Raw instance by `Eric Larson`_
 
-   - Support for parsing the EDF+ annotation channel by `Martin Billinger`_
+- Support for parsing the EDF+ annotation channel by `Martin Billinger`_
 
-   - Add EpochsArray constructor for creating epochs from numpy arrays by `Denis Engemann`_ and `Federico Raimondo`_
+- Add EpochsArray constructor for creating epochs from numpy arrays by `Denis Engemann`_ and `Federico Raimondo`_
 
-   - Add connector to FieldTrip realtime client by `Mainak Jas`_
+- Add connector to FieldTrip realtime client by `Mainak Jas`_
 
-   - Add color and event_id with legend options in plot_events in viz.py by `Cathy Nangini`_
+- Add color and event_id with legend options in plot_events in viz.py by `Cathy Nangini`_
 
-   - Add ``events_list`` parameter to ``mne.concatenate_raws`` to concatenate events corresponding to runs by `Denis Engemann`_
+- Add ``events_list`` parameter to ``mne.concatenate_raws`` to concatenate events corresponding to runs by `Denis Engemann`_
 
-   - Add ``read_ch_connectivity`` function to read FieldTrip neighbor template .mat files and obtain sensor adjacency matrices by `Denis Engemann`_
+- Add ``read_ch_connectivity`` function to read FieldTrip neighbor template .mat files and obtain sensor adjacency matrices by `Denis Engemann`_
 
-   - Add display of head in helmet from -trans.fif file to check coregistration quality by `Mainak Jas`_
+- Add display of head in helmet from -trans.fif file to check coregistration quality by `Mainak Jas`_
 
-   - Add ``raw.add_events`` to allow adding events to a raw file by `Eric Larson`_
+- Add ``raw.add_events`` to allow adding events to a raw file by `Eric Larson`_
 
-   - Add ``plot_image`` method to Evoked object to display data as images by `Jean-Remi King`_ and `Alex Gramfort`_ and `Denis Engemann`_
+- Add ``plot_image`` method to Evoked object to display data as images by `Jean-Remi King`_ and `Alex Gramfort`_ and `Denis Engemann`_
 
-   - Add BCI demo with CSP on motor imagery by `Martin Billinger`_
+- Add BCI demo with CSP on motor imagery by `Martin Billinger`_
 
-   - New ICA API with unified methods for processing ``Raw``, ``Epochs`` and ``Evoked`` objects by `Denis Engemann`_
+- New ICA API with unified methods for processing ``Raw``, ``Epochs`` and ``Evoked`` objects by `Denis Engemann`_
 
-   - Apply ICA at the evoked stage by `Denis Engemann`_
+- Apply ICA at the evoked stage by `Denis Engemann`_
 
-   - New ICA methods for visualizing unmixing quality, artifact detection and rejection by `Denis Engemann`_
+- New ICA methods for visualizing unmixing quality, artifact detection and rejection by `Denis Engemann`_
 
-   - Add ``pick_channels`` and ``drop_channels`` mixin class to pick and drop channels from ``Raw``, ``Epochs``, and ``Evoked`` objects by `Andrew Dykstra`_ and `Denis Engemann`_
+- Add ``pick_channels`` and ``drop_channels`` mixin class to pick and drop channels from ``Raw``, ``Epochs``, and ``Evoked`` objects by `Andrew Dykstra`_ and `Denis Engemann`_
 
-   - Add ``EvokedArray`` class to create an Evoked object from an array by `Andrew Dykstra`_
+- Add ``EvokedArray`` class to create an Evoked object from an array by `Andrew Dykstra`_
 
-   - Add ``plot_bem`` method to visualize BEM contours on MRI anatomical images by `Mainak Jas`_ and `Alex Gramfort`_
+- Add ``plot_bem`` method to visualize BEM contours on MRI anatomical images by `Mainak Jas`_ and `Alex Gramfort`_
 
-   - Add automated ECG detection using cross-trial phase statistics by `Denis Engemann`_ and `Juergen Dammers`_
+- Add automated ECG detection using cross-trial phase statistics by `Denis Engemann`_ and `Juergen Dammers`_
 
-   - Add Forward class to succintly display gain matrix info by `Andrew Dykstra`_
+- Add Forward class to succintly display gain matrix info by `Andrew Dykstra`_
 
-   - Add reading and writing of split raw files by `Martin Luessi`_
+- Add reading and writing of split raw files by `Martin Luessi`_
 
-   - Add OLS regression function by `Tal Linzen`_, `Teon Brooks`_ and `Denis Engemann`_
+- Add OLS regression function by `Tal Linzen`_, `Teon Brooks`_ and `Denis Engemann`_
 
-   - Add computation of point spread and cross-talk functions for MNE type solutions by `Alex Gramfort`_ and `Olaf Hauk`_
+- Add computation of point spread and cross-talk functions for MNE type solutions by `Alex Gramfort`_ and `Olaf Hauk`_
 
-   - Add mask parameter to `plot_evoked_topomap` and ``evoked.plot_topomap`` by `Denis Engemann`_ and `Alex Gramfort`_
+- Add mask parameter to `plot_evoked_topomap` and ``evoked.plot_topomap`` by `Denis Engemann`_ and `Alex Gramfort`_
 
-   - Add infomax and extended infomax ICA by `Denis Engemann`_, `Juergen Dammers`_ and `Lukas Breuer`_ and `Federico Raimondo`_
+- Add infomax and extended infomax ICA by `Denis Engemann`_, `Juergen Dammers`_ and `Lukas Breuer`_ and `Federico Raimondo`_
 
-   - Aesthetically redesign interpolated topography plots by `Denis Engemann`_ and `Alex Gramfort`_
+- Aesthetically redesign interpolated topography plots by `Denis Engemann`_ and `Alex Gramfort`_
 
-   - Simplify sensor space time-frequency analysis API with ``tfr_morlet`` function by `Alex Gramfort`_ and `Denis Engemann`_
+- Simplify sensor space time-frequency analysis API with ``tfr_morlet`` function by `Alex Gramfort`_ and `Denis Engemann`_
 
-   - Add new somatosensory MEG dataset with nice time-frequency content by `Alex Gramfort`_
+- Add new somatosensory MEG dataset with nice time-frequency content by `Alex Gramfort`_
 
-   - Add HDF5 write/read support for SourceEstimates by `Eric Larson`_
+- Add HDF5 write/read support for SourceEstimates by `Eric Larson`_
 
-   - Add InverseOperator class to display inverse operator info by `Mainak Jas`_
+- Add InverseOperator class to display inverse operator info by `Mainak Jas`_
 
-   - Add `$ mne report` command to generate html reports of MEG/EEG data analysis pipelines by `Mainak Jas`_, `Alex Gramfort`_ and `Denis Engemann`_
+- Add `$ mne report` command to generate html reports of MEG/EEG data analysis pipelines by `Mainak Jas`_, `Alex Gramfort`_ and `Denis Engemann`_
 
-   - Improve ICA verbosity with regard to rank reduction by `Denis Engemann`_
+- Improve ICA verbosity with regard to rank reduction by `Denis Engemann`_
 
 BUG
 ~~~
 
-   - Fix incorrect ``times`` attribute when stc was computed using ``apply_inverse`` after decimation at epochs stage for certain, arbitrary sample frequencies by `Denis Engemann`_
+- Fix incorrect ``times`` attribute when stc was computed using ``apply_inverse`` after decimation at epochs stage for certain, arbitrary sample frequencies by `Denis Engemann`_
 
-   - Fix corner case error for step-down-in-jumps permutation test (when step-down threshold was high enough to include all clusters) by `Eric Larson`_
+- Fix corner case error for step-down-in-jumps permutation test (when step-down threshold was high enough to include all clusters) by `Eric Larson`_
 
-   - Fix selection of total number of components via float when picking ICA sources by `Denis Engemann`_ and `Qunxi Dong`_
+- Fix selection of total number of components via float when picking ICA sources by `Denis Engemann`_ and `Qunxi Dong`_
 
-   - Fix writing and reading transforms after modification in measurment info by `Denis Engemann`_ and `Martin Luessi`_ and `Eric Larson`_
+- Fix writing and reading transforms after modification in measurment info by `Denis Engemann`_ and `Martin Luessi`_ and `Eric Larson`_
 
-   - Fix pre-whitening / rescaling when estimating ICA on multiple channels without covariance by `Denis Engemann`_
+- Fix pre-whitening / rescaling when estimating ICA on multiple channels without covariance by `Denis Engemann`_
 
-   - Fix ICA pre-whitening, avoid recomputation when applying ICA to new data by `Denis Engemann`_
+- Fix ICA pre-whitening, avoid recomputation when applying ICA to new data by `Denis Engemann`_
 
 API
 ~~~
 
-   - The minimum numpy version has been increased to 1.6 from 1.4.
+- The minimum numpy version has been increased to 1.6 from 1.4.
 
-   - Epochs object now has a selection attribute to track provenance of selected Epochs. The length of the drop_log attribute is now the same as the length of the original events passed to Epochs. In earlier versions it had the length of the events filtered by event_id. Epochs has also now a plot_drop_log method.
+- Epochs object now has a selection attribute to track provenance of selected Epochs. The length of the drop_log attribute is now the same as the length of the original events passed to Epochs. In earlier versions it had the length of the events filtered by event_id. Epochs has also now a plot_drop_log method.
 
-   - Deprecate Epochs.drop_picks in favor of a new method called drop_channels
+- Deprecate Epochs.drop_picks in favor of a new method called drop_channels
 
-   - Deprecate ``labels_from_parc`` and ``parc_from_labels`` in favor of ``read_labels_from_annot`` and ``write_labels_to_annot``
+- Deprecate ``labels_from_parc`` and ``parc_from_labels`` in favor of ``read_labels_from_annot`` and ``write_labels_to_annot``
 
-   - The default of the new add_dist option of ``setup_source_space`` to add patch information will change from False to True in MNE-Python 0.9
+- The default of the new add_dist option of ``setup_source_space`` to add patch information will change from False to True in MNE-Python 0.9
 
-   - Deprecate ``read_evoked`` and ``write_evoked`` in favor of ``read_evokeds`` and ``write_evokeds``. read_evokeds will return all `Evoked` instances in a file by default.
+- Deprecate ``read_evoked`` and ``write_evoked`` in favor of ``read_evokeds`` and ``write_evokeds``. read_evokeds will return all `Evoked` instances in a file by default.
 
-   - Deprecate ``setno`` in favor of ``condition`` in the initialization of an Evoked instance. This affects ``mne.fiff.Evoked`` and ``read_evokeds``, but not ``read_evoked``.
+- Deprecate ``setno`` in favor of ``condition`` in the initialization of an Evoked instance. This affects ``mne.fiff.Evoked`` and ``read_evokeds``, but not ``read_evoked``.
 
-   - Deprecate ``mne.fiff`` module, use ``mne.io`` instead e.g. ``mne.io.Raw`` instead of ``mne.fiff.Raw``.
+- Deprecate ``mne.fiff`` module, use ``mne.io`` instead e.g. ``mne.io.Raw`` instead of ``mne.fiff.Raw``.
 
-   - Pick functions (e.g., ``pick_types``) are now in the mne namespace (e.g. use ``mne.pick_types``).
+- Pick functions (e.g., ``pick_types``) are now in the mne namespace (e.g. use ``mne.pick_types``).
 
-   - Deprecated ICA methods specific to one container type. Use ICA.fit, ICA.get_sources ICA.apply and ``ICA.plot_*`` for processing Raw, Epochs and Evoked objects.
+- Deprecated ICA methods specific to one container type. Use ICA.fit, ICA.get_sources ICA.apply and ``ICA.plot_*`` for processing Raw, Epochs and Evoked objects.
 
-   - The default smoothing method for ``mne.stc_to_label`` will change in v0.9, and the old method is deprecated.
+- The default smoothing method for ``mne.stc_to_label`` will change in v0.9, and the old method is deprecated.
 
-   - As default, for ICA the maximum number of PCA components equals the number of channels passed. The number of PCA components used to reconstruct the sensor space signals now defaults to the maximum number of PCA components estimated.
+- As default, for ICA the maximum number of PCA components equals the number of channels passed. The number of PCA components used to reconstruct the sensor space signals now defaults to the maximum number of PCA components estimated.
 
 Authors
 ~~~~~~~
 
 The committer list for this release is the following (preceded by number of commits):
 
-   * 418  Denis A. Engemann
-   * 284  Alexandre Gramfort
-   * 242  Eric Larson
-   * 155  Christian Brodbeck
-   * 144  Mainak Jas
-   * 49  Martin Billinger
-   * 49  Andrew Dykstra
-   * 44  Tal Linzen
-   * 37  Dan G. Wakeman
-   * 36  Martin Luessi
-   * 26  Teon Brooks
-   * 20  Cathy Nangini
-   * 15  Hari Bharadwaj
-   * 15  Roman Goj
-   * 10  Ross Maddox
-   * 9  Marmaduke Woodman
-   * 8  Praveen Sripad
-   * 8  Tanay
-   * 8  Roan LaPlante
-   * 5  Saket Choudhary
-   * 4  Nick Ward
-   * 4  Mads Jensen
-   * 3  Olaf Hauk
-   * 3  Brad Buran
-   * 2  Daniel Strohmeier
-   * 2  Federico Raimondo
-   * 2  Alan Leggitt
-   * 1  Jean-Remi King
-   * 1  Matti Hamalainen
+* 418  Denis A. Engemann
+* 284  Alexandre Gramfort
+* 242  Eric Larson
+* 155  Christian Brodbeck
+* 144  Mainak Jas
+* 49  Martin Billinger
+* 49  Andrew Dykstra
+* 44  Tal Linzen
+* 37  Dan G. Wakeman
+* 36  Martin Luessi
+* 26  Teon Brooks
+* 20  Cathy Nangini
+* 15  Hari Bharadwaj
+* 15  Roman Goj
+* 10  Ross Maddox
+* 9  Marmaduke Woodman
+* 8  Praveen Sripad
+* 8  Tanay
+* 8  Roan LaPlante
+* 5  Saket Choudhary
+* 4  Nick Ward
+* 4  Mads Jensen
+* 3  Olaf Hauk
+* 3  Brad Buran
+* 2  Daniel Strohmeier
+* 2  Federico Raimondo
+* 2  Alan Leggitt
+* 1  Jean-Remi King
+* 1  Matti Hamalainen
 
 
 .. _changes_0_7:
@@ -1145,106 +1755,106 @@ Version 0.7
 Changelog
 ~~~~~~~~~
 
-   - Add capability for real-time feedback via trigger codes using StimServer and StimClient classes by `Mainak Jas`_
+- Add capability for real-time feedback via trigger codes using StimServer and StimClient classes by `Mainak Jas`_
 
-   - New decoding module for MEG analysis containing sklearn compatible transformers by `Mainak Jas`_ and `Alex Gramfort`_
+- New decoding module for MEG analysis containing sklearn compatible transformers by `Mainak Jas`_ and `Alex Gramfort`_
 
-   - New realtime module containing RtEpochs, RtClient and MockRtClient class by `Martin Luessi`_, `Christopher Dinh`_, `Alex Gramfort`_, `Denis Engemann`_ and `Mainak Jas`_
+- New realtime module containing RtEpochs, RtClient and MockRtClient class by `Martin Luessi`_, `Christopher Dinh`_, `Alex Gramfort`_, `Denis Engemann`_ and `Mainak Jas`_
 
-   - Allow picking normal orientation in LCMV beamformers by `Roman Goj`_, `Alex Gramfort`_, `Denis Engemann`_ and `Martin Luessi`_
+- Allow picking normal orientation in LCMV beamformers by `Roman Goj`_, `Alex Gramfort`_, `Denis Engemann`_ and `Martin Luessi`_
 
-   - Add printing summary to terminal for measurement info by `Denis Engemann`_
+- Add printing summary to terminal for measurement info by `Denis Engemann`_
 
-   - Add read and write info attribute ICA objects by `Denis Engemann`_
+- Add read and write info attribute ICA objects by `Denis Engemann`_
 
-   - Decoding with Common Spatial Patterns (CSP) by `Romain Trachel`_ and `Alex Gramfort`_
+- Decoding with Common Spatial Patterns (CSP) by `Romain Trachel`_ and `Alex Gramfort`_
 
-   - Add ICA ``plot_topomap`` function and method for displaying the spatial sensitivity of ICA sources by `Denis Engemann`_
+- Add ICA ``plot_topomap`` function and method for displaying the spatial sensitivity of ICA sources by `Denis Engemann`_
 
-   - Plotting multiple brain views at once by `Eric Larson`_
+- Plotting multiple brain views at once by `Eric Larson`_
 
-   - Reading head positions from raw FIFF files by `Eric Larson`_
+- Reading head positions from raw FIFF files by `Eric Larson`_
 
-   - Add decimation parameter to ICA.decompose*  methods by `Denis Engemann`_ and `Alex Gramfort`_
+- Add decimation parameter to ICA.decompose*  methods by `Denis Engemann`_ and `Alex Gramfort`_
 
-   - Add rejection buffer to ICA.decompose* methods by `Denis Engemann`_ and `Alex Gramfort`_
+- Add rejection buffer to ICA.decompose* methods by `Denis Engemann`_ and `Alex Gramfort`_
 
-   - Improve ICA computation speed and memory usage by `Denis Engemann`_ and `Alex Gramfort`_
+- Improve ICA computation speed and memory usage by `Denis Engemann`_ and `Alex Gramfort`_
 
-   - Add polygonal surface decimation function to preprocess head surfaces for coregistration by `Denis Engemann`_ and `Alex Gramfort`_
+- Add polygonal surface decimation function to preprocess head surfaces for coregistration by `Denis Engemann`_ and `Alex Gramfort`_
 
-   - DICS time-frequency beamforming for epochs, evoked and for estimating source power by `Roman Goj`_, `Alex Gramfort`_ and `Denis Engemann`_
+- DICS time-frequency beamforming for epochs, evoked and for estimating source power by `Roman Goj`_, `Alex Gramfort`_ and `Denis Engemann`_
 
-   - Add method for computing cross-spectral density (CSD) from epochs and class for storing CSD data by `Roman Goj`_, `Alex Gramfort`_ and `Denis Engemann`_
+- Add method for computing cross-spectral density (CSD) from epochs and class for storing CSD data by `Roman Goj`_, `Alex Gramfort`_ and `Denis Engemann`_
 
-   - Add trellis plot function and method for visualizing single epochs by `Denis Engemann`_
+- Add trellis plot function and method for visualizing single epochs by `Denis Engemann`_
 
-   - Add fiducials read/write support by `Christian Brodbeck`_ and `Alex Gramfort`_
+- Add fiducials read/write support by `Christian Brodbeck`_ and `Alex Gramfort`_
 
-   - Add select / drop bad channels in `plot_raw` on click by `Denis Engemann`_
+- Add select / drop bad channels in `plot_raw` on click by `Denis Engemann`_
 
-   - Add `ico` and `oct` source space creation in native Python by `Eric Larson`_
+- Add `ico` and `oct` source space creation in native Python by `Eric Larson`_
 
-   - Add interactive rejection of bad trials in ``plot_epochs`` by `Denis Engemann`_
+- Add interactive rejection of bad trials in ``plot_epochs`` by `Denis Engemann`_
 
-   - Add morph map calculation by `Eric Larson`_ and `Martin Luessi`_
+- Add morph map calculation by `Eric Larson`_ and `Martin Luessi`_
 
-   - Add volume and discrete source space creation and I/O support by `Eric Larson`_
+- Add volume and discrete source space creation and I/O support by `Eric Larson`_
 
-   - Time-frequency beamforming to obtain spectrograms in source space using LCMV and DICS by `Roman Goj`_, `Alex Gramfort`_ and `Denis Engemann`_
+- Time-frequency beamforming to obtain spectrograms in source space using LCMV and DICS by `Roman Goj`_, `Alex Gramfort`_ and `Denis Engemann`_
 
-   - Compute epochs power spectral density function by `Denis Engemann`_
+- Compute epochs power spectral density function by `Denis Engemann`_
 
-   - Plot raw power spectral density by `Eric Larson`_
+- Plot raw power spectral density by `Eric Larson`_
 
-   - Computing of distances along the cortical surface by `Eric Larson`_
+- Computing of distances along the cortical surface by `Eric Larson`_
 
-   - Add reading BEM solutions by `Eric Larson`_
+- Add reading BEM solutions by `Eric Larson`_
 
-   - Add forward solution calculation in native Python by `Eric Larson`_
+- Add forward solution calculation in native Python by `Eric Larson`_
 
-   - Add (Neuro)debian license compatibility by `Eric Larson`_
+- Add (Neuro)debian license compatibility by `Eric Larson`_
 
-   - Automatic QRS threshold selection for ECG events by `Eric Larson`_
+- Automatic QRS threshold selection for ECG events by `Eric Larson`_
 
-   - Add Travis continuous integration service by `Denis Engemann`_
+- Add Travis continuous integration service by `Denis Engemann`_
 
-   - Add SPM face data set by `Denis Engemann`_ `Martin Luessi`_ and `Alex Gramfort`_
+- Add SPM face data set by `Denis Engemann`_ `Martin Luessi`_ and `Alex Gramfort`_
 
-   - Support reading of EDF+,BDF data by `Teon Brooks`_
+- Support reading of EDF+,BDF data by `Teon Brooks`_
 
-   - Tools for scaling MRIs (mne.scale_mri) by `Christian Brodbeck`_
+- Tools for scaling MRIs (mne.scale_mri) by `Christian Brodbeck`_
 
-   - GUI for head-MRI coregistration (mne.gui.coregistration) by `Christian Brodbeck`_
+- GUI for head-MRI coregistration (mne.gui.coregistration) by `Christian Brodbeck`_
 
-   - GUI for ki2fiff conversion (mne.gui.kit2fiff) by `Christian Brodbeck`_
+- GUI for ki2fiff conversion (mne.gui.kit2fiff) by `Christian Brodbeck`_
 
-   - Support reading of EEG BrainVision data by `Teon Brooks`_
+- Support reading of EEG BrainVision data by `Teon Brooks`_
 
-   - Improve CTF compensation handling by `Martin Luessi`_ and `Eric Larson`_
+- Improve CTF compensation handling by `Martin Luessi`_ and `Eric Larson`_
 
-   - Improve and extend automated layout guessing by `Denis Engemann`_
+- Improve and extend automated layout guessing by `Denis Engemann`_
 
-   - Add Continuum Analytics Anaconda support by `Denis Engemann`_
+- Add Continuum Analytics Anaconda support by `Denis Engemann`_
 
-   - Add `subtract evoked` option to beamformers by `Andrew Dykstra`_
+- Add `subtract evoked` option to beamformers by `Andrew Dykstra`_
 
-   - Add new `transform` method to SourceEstimate(s) by `Andrew Dykstra`_
+- Add new `transform` method to SourceEstimate(s) by `Andrew Dykstra`_
 
 API
 ~~~
 
-   - The pick_normal parameter for minimum norm solvers has been renamed as ``pick_ori`` and normal orientation picking is now achieved by passing the value "normal" for the `pick_ori` parameter.
+- The pick_normal parameter for minimum norm solvers has been renamed as ``pick_ori`` and normal orientation picking is now achieved by passing the value "normal" for the `pick_ori` parameter.
 
-   - ICA objects now expose the measurment info of the object fitted.
+- ICA objects now expose the measurment info of the object fitted.
 
-   - Average EEG reference is now added by default to Raw instances.
+- Average EEG reference is now added by default to Raw instances.
 
-   - Removed deprecated read/write_stc/w, use SourceEstimate methods instead
+- Removed deprecated read/write_stc/w, use SourceEstimate methods instead
 
-   - The ``chs`` argument in ``mne.layouts.find_layout`` is deprecated and will be removed in MNE-Python 0.9. Use ``info`` instead.
+- The ``chs`` argument in ``mne.layouts.find_layout`` is deprecated and will be removed in MNE-Python 0.9. Use ``info`` instead.
 
-   - ``plot_evoked`` and ``Epochs.plot`` now open a new figure by default. To plot on an existing figure please specify the `axes` parameter.
+- ``plot_evoked`` and ``Epochs.plot`` now open a new figure by default. To plot on an existing figure please specify the `axes` parameter.
 
 
 Authors
@@ -1253,26 +1863,26 @@ Authors
 The committer list for this release is the following (preceded by number
 of commits):
 
-   * 336  Denis A. Engemann
-   * 202  Eric Larson
-   * 193  Roman Goj
-   * 138  Alexandre Gramfort
-   *  99  Mainak Jas
-   *  75  Christian Brodbeck
-   *  60  Martin Luessi
-   *  40  Teon Brooks
-   *  29  Romain Trachel
-   *  28  Andrew Dykstra
-   *  12  Mark Wronkiewicz
-   *  10  Christoph Dinh
-   *   8  Alan Leggitt
-   *   3  Yaroslav Halchenko
-   *   3  Daniel Strohmeier
-   *   2  Mads Jensen
-   *   2  Praveen Sripad
-   *   1  Luke Bloy
-   *   1  Emanuele Olivetti
-   *   1  Yousra BEKHTI
+* 336  Denis A. Engemann
+* 202  Eric Larson
+* 193  Roman Goj
+* 138  Alexandre Gramfort
+*  99  Mainak Jas
+*  75  Christian Brodbeck
+*  60  Martin Luessi
+*  40  Teon Brooks
+*  29  Romain Trachel
+*  28  Andrew Dykstra
+*  12  Mark Wronkiewicz
+*  10  Christoph Dinh
+*   8  Alan Leggitt
+*   3  Yaroslav Halchenko
+*   3  Daniel Strohmeier
+*   2  Mads Jensen
+*   2  Praveen Sripad
+*   1  Luke Bloy
+*   1  Emanuele Olivetti
+*   1  Yousra BEKHTI
 
 
 .. _changes_0_6:
@@ -1283,132 +1893,132 @@ Version 0.6
 Changelog
 ~~~~~~~~~
 
-   - Linear (and zeroth-order) detrending for Epochs and Evoked by `Eric Larson`_
+- Linear (and zeroth-order) detrending for Epochs and Evoked by `Eric Larson`_
 
-   - Label morphing between subjects by `Eric Larson`_
+- Label morphing between subjects by `Eric Larson`_
 
-   - Define events based on time lag between reference and target event by `Denis Engemann`_
+- Define events based on time lag between reference and target event by `Denis Engemann`_
 
-   - ICA convenience function implementing an automated artifact removal workflow by `Denis Engemann`_
+- ICA convenience function implementing an automated artifact removal workflow by `Denis Engemann`_
 
-   - Bad channels no longer included in epochs by default by `Eric Larson`_
+- Bad channels no longer included in epochs by default by `Eric Larson`_
 
-   - Support for diagonal noise covariances in inverse methods and rank computation by `Eric Larson`_
+- Support for diagonal noise covariances in inverse methods and rank computation by `Eric Larson`_
 
-   - Support for using CUDA in FFT-based FIR filtering (method='fft') and resampling by `Eric Larson`_
+- Support for using CUDA in FFT-based FIR filtering (method='fft') and resampling by `Eric Larson`_
 
-   - Optimized FFT length selection for faster overlap-add filtering by `Martin Luessi`_
+- Optimized FFT length selection for faster overlap-add filtering by `Martin Luessi`_
 
-   - Ability to exclude bad channels from evoked plots or shown them in red by `Martin Luessi`_
+- Ability to exclude bad channels from evoked plots or shown them in red by `Martin Luessi`_
 
-   - Option to show both hemispheres when plotting SourceEstimate with PySurfer by `Martin Luessi`_
+- Option to show both hemispheres when plotting SourceEstimate with PySurfer by `Martin Luessi`_
 
-   - Optimized Raw reading and epoching routines to limit memory copies by `Eric Larson`_
+- Optimized Raw reading and epoching routines to limit memory copies by `Eric Larson`_
 
-   - Advanced options to save raw files in short or double precision by `Eric Larson`_
+- Advanced options to save raw files in short or double precision by `Eric Larson`_
 
-   - Option to detect decreasing events using find_events by `Simon Kornblith`_
+- Option to detect decreasing events using find_events by `Simon Kornblith`_
 
-   - Option to change default stim_channel used for finding events by `Eric Larson`_
+- Option to change default stim_channel used for finding events by `Eric Larson`_
 
-   - Use average patch normal from surface-oriented forward solution in inverse calculation when possible by `Eric Larson`_
+- Use average patch normal from surface-oriented forward solution in inverse calculation when possible by `Eric Larson`_
 
-   - Function to plot drop_log from Epochs instance by `Eric Larson`_
+- Function to plot drop_log from Epochs instance by `Eric Larson`_
 
-   - Estimate rank of Raw data by `Eric Larson`_
+- Estimate rank of Raw data by `Eric Larson`_
 
-   - Support reading of BTi/4D data by `Denis Engemann`_
+- Support reading of BTi/4D data by `Denis Engemann`_
 
-   - Wrapper for generating forward solutions by `Eric Larson`_
+- Wrapper for generating forward solutions by `Eric Larson`_
 
-   - Averaging forward solutions by `Eric Larson`_
+- Averaging forward solutions by `Eric Larson`_
 
-   - Events now contain the pre-event stim channel value in the middle column, by `Christian Brodbeck`_
+- Events now contain the pre-event stim channel value in the middle column, by `Christian Brodbeck`_
 
-   - New function ``mne.find_stim_steps`` for finding all steps in a stim channel by `Christian Brodbeck`_
+- New function ``mne.find_stim_steps`` for finding all steps in a stim channel by `Christian Brodbeck`_
 
-   - Get information about FIFF files using mne.fiff.show_fiff() by `Eric Larson`_
+- Get information about FIFF files using mne.fiff.show_fiff() by `Eric Larson`_
 
-   - Compute forward fields sensitivity maps by `Alex Gramfort`_ and `Eric Larson`_
+- Compute forward fields sensitivity maps by `Alex Gramfort`_ and `Eric Larson`_
 
-   - Support reading of KIT data by `Teon Brooks`_ and `Christian Brodbeck`_
+- Support reading of KIT data by `Teon Brooks`_ and `Christian Brodbeck`_
 
-   - Raw data visualization by `Eric Larson`_
+- Raw data visualization by `Eric Larson`_
 
-   - Smarter SourceEstimate object that contains linear inverse kernel and sensor space data for fast time-frequency transforms in source space by `Martin Luessi`_
+- Smarter SourceEstimate object that contains linear inverse kernel and sensor space data for fast time-frequency transforms in source space by `Martin Luessi`_
 
-   - Add example of decoding/MVPA on MEG sensor data by `Alex Gramfort`_
+- Add example of decoding/MVPA on MEG sensor data by `Alex Gramfort`_
 
-   - Add support for non-paired tests in spatiotemporal cluster stats by `Alex Gramfort`_
+- Add support for non-paired tests in spatiotemporal cluster stats by `Alex Gramfort`_
 
-   - Add unified SSP-projector API for Raw, Epochs and Evoked objects by `Denis Engemann`_, `Alex Gramfort`_ `Eric Larson`_ and `Martin Luessi`_
+- Add unified SSP-projector API for Raw, Epochs and Evoked objects by `Denis Engemann`_, `Alex Gramfort`_ `Eric Larson`_ and `Martin Luessi`_
 
-   - Add support for delayed SSP application at evoked stage `Denis Engemann`_, `Alex Gramfort`_, `Eric Larson`_ and `Martin Luessi`_
+- Add support for delayed SSP application at evoked stage `Denis Engemann`_, `Alex Gramfort`_, `Eric Larson`_ and `Martin Luessi`_
 
-   - Support selective parameter updating in functions taking dicts as arguments by `Denis Engemann`_
+- Support selective parameter updating in functions taking dicts as arguments by `Denis Engemann`_
 
-   - New ICA method ``sources_as_epochs`` to create Epochs in ICA space by `Denis Engemann`_
+- New ICA method ``sources_as_epochs`` to create Epochs in ICA space by `Denis Engemann`_
 
-   - New method in Evoked and Epoch classes to shift time scale by `Mainak Jas`_
+- New method in Evoked and Epoch classes to shift time scale by `Mainak Jas`_
 
-   - Added option to specify EOG channel(s) when computing PCA/SSP projections for EOG artifacts by `Mainak Jas`_
+- Added option to specify EOG channel(s) when computing PCA/SSP projections for EOG artifacts by `Mainak Jas`_
 
-   - Improved connectivity interface to allow combinations of signals, e.g., seed time series and source estimates, by `Martin Luessi`_
+- Improved connectivity interface to allow combinations of signals, e.g., seed time series and source estimates, by `Martin Luessi`_
 
-   - Effective connectivity estimation using Phase Slope Index (PSI) by `Martin Luessi`_
+- Effective connectivity estimation using Phase Slope Index (PSI) by `Martin Luessi`_
 
-   - Support for threshold-free cluster enhancement (TFCE) by `Eric Larson`_
+- Support for threshold-free cluster enhancement (TFCE) by `Eric Larson`_
 
-   - Support for "hat" variance regularization by `Eric Larson`_
+- Support for "hat" variance regularization by `Eric Larson`_
 
-   - Access source estimates as Pandas DataFrame by `Denis Engemann`_.
+- Access source estimates as Pandas DataFrame by `Denis Engemann`_.
 
-   - Add example of decoding/MVPA on MEG source space data by `Denis Engemann`_
+- Add example of decoding/MVPA on MEG source space data by `Denis Engemann`_
 
-   - Add support for --tstart option in mne_compute_proj_eog.py by `Alex Gramfort`_
+- Add support for --tstart option in mne_compute_proj_eog.py by `Alex Gramfort`_
 
-   - Add two-way repeated measures ANOVA for mass-univariate statistics by `Denis Engemann`_, `Eric Larson`_ and `Alex Gramfort`_
+- Add two-way repeated measures ANOVA for mass-univariate statistics by `Denis Engemann`_, `Eric Larson`_ and `Alex Gramfort`_
 
-   - Add function for summarizing clusters from spatio-temporal-cluster permutation tests by `Denis Engemann`_ and `Eric Larson`_
+- Add function for summarizing clusters from spatio-temporal-cluster permutation tests by `Denis Engemann`_ and `Eric Larson`_
 
-   - Add generator support for lcmv_epochs by `Denis Engemann`_
+- Add generator support for lcmv_epochs by `Denis Engemann`_
 
-   - Gamma-MAP sparse source localization method by `Martin Luessi`_ and `Alex Gramfort`_
+- Gamma-MAP sparse source localization method by `Martin Luessi`_ and `Alex Gramfort`_
 
-   - Add regular expression and substring support for selecting parcellation labels by `Denis Engemann`_
+- Add regular expression and substring support for selecting parcellation labels by `Denis Engemann`_
 
-   - New plot_evoked option for interactive and reversible selection of SSP projection vectors by `Denis Engemann`_
+- New plot_evoked option for interactive and reversible selection of SSP projection vectors by `Denis Engemann`_
 
-   - Plot 2D flat topographies with interpolation for evoked and SSPs by `Christian Brodbeck`_ and `Alex Gramfort`_
+- Plot 2D flat topographies with interpolation for evoked and SSPs by `Christian Brodbeck`_ and `Alex Gramfort`_
 
-   - Support delayed SSP applicationon for 2D flat topographies by `Denis Engemann`_ and `Christian Brodbeck`_ and `Alex Gramfort`_
+- Support delayed SSP applicationon for 2D flat topographies by `Denis Engemann`_ and `Christian Brodbeck`_ and `Alex Gramfort`_
 
-   - Allow picking maximum power source, a.k.a. "optimal", orientation in LCMV beamformers by `Roman Goj`_, `Alex Gramfort`_, `Denis Engemann`_ and `Martin Luessi`_
+- Allow picking maximum power source, a.k.a. "optimal", orientation in LCMV beamformers by `Roman Goj`_, `Alex Gramfort`_, `Denis Engemann`_ and `Martin Luessi`_
 
-   - Add sensor type scaling parameter to plot_topo by `Andrew Dykstra`_, `Denis Engemann`_  and `Eric Larson`_
+- Add sensor type scaling parameter to plot_topo by `Andrew Dykstra`_, `Denis Engemann`_  and `Eric Larson`_
 
-   - Support delayed SSP application in plot_topo by `Denis Engemann`_
+- Support delayed SSP application in plot_topo by `Denis Engemann`_
 
 API
 ~~~
 
-   - Deprecated use of fiff.pick_types without specifying exclude -- use either [] (none), ``bads`` (bad channels), or a list of string (channel names).
+- Deprecated use of fiff.pick_types without specifying exclude -- use either [] (none), ``bads`` (bad channels), or a list of string (channel names).
 
-   - Depth bias correction in dSPM/MNE/sLORETA make_inverse_operator is now done like in the C code using only gradiometers if present, else magnetometers, and EEG if no MEG channels are present.
+- Depth bias correction in dSPM/MNE/sLORETA make_inverse_operator is now done like in the C code using only gradiometers if present, else magnetometers, and EEG if no MEG channels are present.
 
-   - Fixed-orientation inverse solutions need to be made using `fixed=True` option (using non-surface-oriented forward solutions if no depth weighting is used) to maintain compatibility with MNE C code.
+- Fixed-orientation inverse solutions need to be made using `fixed=True` option (using non-surface-oriented forward solutions if no depth weighting is used) to maintain compatibility with MNE C code.
 
-   - Raw.save() will only overwrite the destination file, if it exists, if option overwrite=True is set.
+- Raw.save() will only overwrite the destination file, if it exists, if option overwrite=True is set.
 
-   - mne.utils.set_config(), get_config(), get_config_path() moved to mne namespace.
+- mne.utils.set_config(), get_config(), get_config_path() moved to mne namespace.
 
-   - Raw constructor argument proj_active deprecated -- use proj argument instead.
+- Raw constructor argument proj_active deprecated -- use proj argument instead.
 
-   - Functions from the mne.mixed_norm module have been moved to the mne.inverse_sparse module.
+- Functions from the mne.mixed_norm module have been moved to the mne.inverse_sparse module.
 
-   - Deprecate CTF compensation (keep_comp and dest_comp) in Epochs and move it to Raw with a single compensation parameter.
+- Deprecate CTF compensation (keep_comp and dest_comp) in Epochs and move it to Raw with a single compensation parameter.
 
-   - Remove artifacts module. Artifacts- and preprocessing related functions can now be found in mne.preprocessing.
+- Remove artifacts module. Artifacts- and preprocessing related functions can now be found in mne.preprocessing.
 
 Authors
 ~~~~~~~
@@ -1416,21 +2026,21 @@ Authors
 The committer list for this release is the following (preceded by number
 of commits):
 
-   * 340  Eric Larson
-   * 330  Denis A. Engemann
-   * 204  Alexandre Gramfort
-   *  72  Christian Brodbeck
-   *  66  Roman Goj
-   *  65  Martin Luessi
-   *  37  Teon Brooks
-   *  18  Mainak Jas
-   *   9  Simon Kornblith
-   *   7  Daniel Strohmeier
-   *   6  Romain Trachel
-   *   5  Yousra BEKHTI
-   *   5  Brad Buran
-   *   1  Andrew Dykstra
-   *   1  Christoph Dinh
+* 340  Eric Larson
+* 330  Denis A. Engemann
+* 204  Alexandre Gramfort
+*  72  Christian Brodbeck
+*  66  Roman Goj
+*  65  Martin Luessi
+*  37  Teon Brooks
+*  18  Mainak Jas
+*   9  Simon Kornblith
+*   7  Daniel Strohmeier
+*   6  Romain Trachel
+*   5  Yousra BEKHTI
+*   5  Brad Buran
+*   1  Andrew Dykstra
+*   1  Christoph Dinh
 
 .. _changes_0_5:
 
@@ -1440,109 +2050,108 @@ Version 0.5
 Changelog
 ~~~~~~~~~
 
-   - Multi-taper PSD estimation for single epochs in source space using minimum norm by `Martin Luessi`_
+- Multi-taper PSD estimation for single epochs in source space using minimum norm by `Martin Luessi`_
 
-   - Read and visualize .dip files obtained with xfit or mne_dipole_fit by `Alex Gramfort`_
+- Read and visualize .dip files obtained with xfit or mne_dipole_fit by `Alex Gramfort`_
 
-   - Make EEG layout by `Eric Larson`_
+- Make EEG layout by `Eric Larson`_
 
-   - Ability to specify SSP projectors when computing covariance from raw by `Eric Larson`_
+- Ability to specify SSP projectors when computing covariance from raw by `Eric Larson`_
 
-   - Read and write txt based event files (.eve or .txt) by `Eric Larson`_
+- Read and write txt based event files (.eve or .txt) by `Eric Larson`_
 
-   - Pass qrs threshold to preprocessing functions by `Eric Larson`_
+- Pass qrs threshold to preprocessing functions by `Eric Larson`_
 
-   - Compute SSP projections from continuous raw data by `Eric Larson`_
+- Compute SSP projections from continuous raw data by `Eric Larson`_
 
-   - Support for applied SSP projections when loading Raw by `Eric Larson`_ and `Alex Gramfort`_
+- Support for applied SSP projections when loading Raw by `Eric Larson`_ and `Alex Gramfort`_
 
-   - Support for loading Raw stored in different fif files by `Eric Larson`_
+- Support for loading Raw stored in different fif files by `Eric Larson`_
 
-   - IO of many Evoked in a single fif file + compute Epochs.standard_error by `Eric Larson`_ and `Alex Gramfort`_
+- IO of many Evoked in a single fif file + compute Epochs.standard_error by `Eric Larson`_ and `Alex Gramfort`_
 
-   - ICA computation on Raw and Epochs with automatic component selection by `Denis Engemann`_ and `Alex Gramfort`_
+- ICA computation on Raw and Epochs with automatic component selection by `Denis Engemann`_ and `Alex Gramfort`_
 
-   - Saving ICA sources to fif files and creating ICA topography layouts by
-     `Denis Engemann`_
+- Saving ICA sources to fif files and creating ICA topography layouts by `Denis Engemann`_
 
-   - Save and restore ICA session to and from fif by `Denis Engemann`_
+- Save and restore ICA session to and from fif by `Denis Engemann`_
 
-   - Export raw, epochs and evoked data as data frame to the pandas library by `Denis Engemann`_
+- Export raw, epochs and evoked data as data frame to the pandas library by `Denis Engemann`_
 
-   - Export raw, epochs and evoked data to the nitime library by `Denis Engemann`_
+- Export raw, epochs and evoked data to the nitime library by `Denis Engemann`_
 
-   - Copy methods for raw and epochs objects by `Denis Engemann`_, `Martin Luessi`_ and `Alex Gramfort`_
+- Copy methods for raw and epochs objects by `Denis Engemann`_, `Martin Luessi`_ and `Alex Gramfort`_
 
-   - New raw objects method to get the time at certain indices by `Denis Engemann`_ and `Alex Gramfort`_
+- New raw objects method to get the time at certain indices by `Denis Engemann`_ and `Alex Gramfort`_
 
-   - Plot method for evoked objects by `Denis Engemann`_
+- Plot method for evoked objects by `Denis Engemann`_
 
-   - Enhancement of cluster-level stats (speed and memory efficiency) by `Eric Larson`_ and `Martin Luessi`_
+- Enhancement of cluster-level stats (speed and memory efficiency) by `Eric Larson`_ and `Martin Luessi`_
 
-   - Reading of source space distances by `Eric Larson`_
+- Reading of source space distances by `Eric Larson`_
 
-   - Support for filling / smoothing labels and speedup of morphing by `Eric Larson`_
+- Support for filling / smoothing labels and speedup of morphing by `Eric Larson`_
 
-   - Adding options for morphing by `Eric Larson`_
+- Adding options for morphing by `Eric Larson`_
 
-   - Plotting functions for time frequency and epochs image topographies by `Denis Engemann`_ and `Alex Gramfort`_
+- Plotting functions for time frequency and epochs image topographies by `Denis Engemann`_ and `Alex Gramfort`_
 
-   - Plotting ERP/ERF images by `Alex Gramfort`_
+- Plotting ERP/ERF images by `Alex Gramfort`_
 
-   - See detailed subplot when cliking on a channel inside a topography plot by `Martin Luessi`_, `Eric Larson`_ and `Denis Engemann`_
+- See detailed subplot when cliking on a channel inside a topography plot by `Martin Luessi`_, `Eric Larson`_ and `Denis Engemann`_
 
-   - Misc channel type support plotting functions by `Denis Engemann`_
+- Misc channel type support plotting functions by `Denis Engemann`_
 
-   - Improved logging support by `Eric Larson`_
+- Improved logging support by `Eric Larson`_
 
-   - Whitening of evoked data for plotting and quality checking by `Alex Gramfort`_
+- Whitening of evoked data for plotting and quality checking by `Alex Gramfort`_
 
-   - Transparent I/O of gzipped fif files (as .fif.gz) by `Eric Larson`_
+- Transparent I/O of gzipped fif files (as .fif.gz) by `Eric Larson`_
 
-   - Spectral connectivity estimation in sensor and source space by `Martin Luessi`_
+- Spectral connectivity estimation in sensor and source space by `Martin Luessi`_
 
-   - Read and write Epochs in FIF files by `Alex Gramfort`_
+- Read and write Epochs in FIF files by `Alex Gramfort`_
 
-   - Resampling of Raw, Epochs, and Evoked by `Eric Larson`_
+- Resampling of Raw, Epochs, and Evoked by `Eric Larson`_
 
-   - Creating epochs objects for different conditions and accessing conditions via user-defined name by `Denis Engemann`_ , `Eric Larson`_, `Alex Gramfort`_ and `Christian Brodbeck`_
+- Creating epochs objects for different conditions and accessing conditions via user-defined name by `Denis Engemann`_ , `Eric Larson`_, `Alex Gramfort`_ and `Christian Brodbeck`_
 
-   - Visualizing evoked responses from different conditions in one topography plot by `Denis Engemann`_ and `Alex Gramfort`_
+- Visualizing evoked responses from different conditions in one topography plot by `Denis Engemann`_ and `Alex Gramfort`_
 
-   - Support for L21 MxNE solver using coordinate descent using scikit-learn by `Alex Gramfort`_ and `Daniel Strohmeier`_
+- Support for L21 MxNE solver using coordinate descent using scikit-learn by `Alex Gramfort`_ and `Daniel Strohmeier`_
 
-   - Support IIR filters (butterworth, chebyshev, bessel, etc.) by `Eric Larson`_
+- Support IIR filters (butterworth, chebyshev, bessel, etc.) by `Eric Larson`_
 
-   - Read labels from FreeSurfer parcellation by  `Martin Luessi`_
+- Read labels from FreeSurfer parcellation by  `Martin Luessi`_
 
-   - Combining labels in source space by `Christian Brodbeck`_
+- Combining labels in source space by `Christian Brodbeck`_
 
-   - Read and write source spaces, surfaces and coordinate transforms to and from files by `Christian Brodbeck`_
+- Read and write source spaces, surfaces and coordinate transforms to and from files by `Christian Brodbeck`_
 
-   - Downsample epochs by `Christian Brodbeck`_ and `Eric Larson`_
+- Downsample epochs by `Christian Brodbeck`_ and `Eric Larson`_
 
-   - New labels class for handling source estimates by `Christian Brodbeck`_, `Martin Luessi`_  and `Alex Gramfort`_
+- New labels class for handling source estimates by `Christian Brodbeck`_, `Martin Luessi`_  and `Alex Gramfort`_
 
-   - New plotting routines to easily display SourceEstimates using PySurfer by `Alex Gramfort`_
+- New plotting routines to easily display SourceEstimates using PySurfer by `Alex Gramfort`_
 
-   - Function to extract label time courses from SourceEstimate(s) by `Martin Luessi`_
+- Function to extract label time courses from SourceEstimate(s) by `Martin Luessi`_
 
-   - Function to visualize connectivity as circular graph by `Martin Luessi`_ and `Alex Gramfort`_
+- Function to visualize connectivity as circular graph by `Martin Luessi`_ and `Alex Gramfort`_
 
-   - Time-frequency Mixed Norm Estimates (TF-MxNE) by `Alex Gramfort`_ and `Daniel Strohmeier`_
+- Time-frequency Mixed Norm Estimates (TF-MxNE) by `Alex Gramfort`_ and `Daniel Strohmeier`_
 
 
 API
 ~~~
-   - Added nave parameter to source_induced_power() and source_band_induced_power(), use nave=1 by default (wrong nave was used before).
+- Added nave parameter to source_induced_power() and source_band_induced_power(), use nave=1 by default (wrong nave was used before).
 
-   - Use mne.layout.read_layout instead of mne.layout.Layout to read a layout file (.lout)
+- Use mne.layout.read_layout instead of mne.layout.Layout to read a layout file (.lout)
 
-   - Use raw.time_as_index instead of time_to_index (still works but is deprecated).
+- Use raw.time_as_index instead of time_to_index (still works but is deprecated).
 
-   - The artifacts module (mne.artifacts) is now merged into mne.preprocessing
+- The artifacts module (mne.artifacts) is now merged into mne.preprocessing
 
-   - Epochs objects now also take dicts as values for the event_id argument. They now can represent multiple conditions.
+- Epochs objects now also take dicts as values for the event_id argument. They now can represent multiple conditions.
 
 Authors
 ~~~~~~~
@@ -1550,14 +2159,14 @@ Authors
 The committer list for this release is the following (preceded by number
 of commits):
 
-   * 313  Eric Larson
-   * 226  Alexandre Gramfort
-   * 219  Denis A. Engemann
-   * 104  Christian Brodbeck
-   *  85  Martin Luessi
-   *   6  Daniel Strohmeier
-   *   4  Teon Brooks
-   *   1  Dan G. Wakeman
+* 313  Eric Larson
+* 226  Alexandre Gramfort
+* 219  Denis A. Engemann
+* 104  Christian Brodbeck
+*  85  Martin Luessi
+*   6  Daniel Strohmeier
+*   4  Teon Brooks
+*   1  Dan G. Wakeman
 
 
 .. _changes_0_4:
@@ -1568,37 +2177,37 @@ Version 0.4
 Changelog
 ~~~~~~~~~
 
-   - Add function to compute source PSD using minimum norm by `Alex Gramfort`_
+- Add function to compute source PSD using minimum norm by `Alex Gramfort`_
 
-   - L21 Mixed Norm Estimates (MxNE) by `Alex Gramfort`_ and `Daniel Strohmeier`_
+- L21 Mixed Norm Estimates (MxNE) by `Alex Gramfort`_ and `Daniel Strohmeier`_
 
-   - Generation of simulated evoked responses by `Alex Gramfort`_, `Daniel Strohmeier`_, and `Martin Luessi`_
+- Generation of simulated evoked responses by `Alex Gramfort`_, `Daniel Strohmeier`_, and `Martin Luessi`_
 
-   - Fit AR models to raw data for temporal whitening by `Alex Gramfort`_.
+- Fit AR models to raw data for temporal whitening by `Alex Gramfort`_.
 
-   - speedup + reduce memory of mne.morph_data by `Alex Gramfort`_.
+- speedup + reduce memory of mne.morph_data by `Alex Gramfort`_.
 
-   - Backporting scipy.signal.firwin2 so filtering works with old scipy by `Alex Gramfort`_.
+- Backporting scipy.signal.firwin2 so filtering works with old scipy by `Alex Gramfort`_.
 
-   - LCMV Beamformer for evoked data, single trials, and raw data by `Alex Gramfort`_ and `Martin Luessi`_.
+- LCMV Beamformer for evoked data, single trials, and raw data by `Alex Gramfort`_ and `Martin Luessi`_.
 
-   - Add support for reading named channel selections by `Martin Luessi`_.
+- Add support for reading named channel selections by `Martin Luessi`_.
 
-   - Add Raw.filter method to more easily band pass data by `Alex Gramfort`_.
+- Add Raw.filter method to more easily band pass data by `Alex Gramfort`_.
 
-   - Add tmin + tmax parameters in mne.compute_covariance to estimate noise covariance in epochs baseline without creating new epochs by `Alex Gramfort`_.
+- Add tmin + tmax parameters in mne.compute_covariance to estimate noise covariance in epochs baseline without creating new epochs by `Alex Gramfort`_.
 
-   - Add support for sLORETA in apply_inverse, apply_inverse_raw, apply_inverse_epochs (API Change) by `Alex Gramfort`_.
+- Add support for sLORETA in apply_inverse, apply_inverse_raw, apply_inverse_epochs (API Change) by `Alex Gramfort`_.
 
-   - Add method to regularize a noise covariance by `Alex Gramfort`_.
+- Add method to regularize a noise covariance by `Alex Gramfort`_.
 
-   - Read and write measurement info in forward and inverse operators for interactive visualization in mne_analyze by `Alex Gramfort`_.
+- Read and write measurement info in forward and inverse operators for interactive visualization in mne_analyze by `Alex Gramfort`_.
 
-   - New mne_compute_proj_ecg.py and mne_compute_proj_eog.py scripts to estimate ECG/EOG PCA/SSP vectors by `Alex Gramfort`_ and `Martin Luessi`_.
+- New mne_compute_proj_ecg.py and mne_compute_proj_eog.py scripts to estimate ECG/EOG PCA/SSP vectors by `Alex Gramfort`_ and `Martin Luessi`_.
 
-   - Wrapper function and script (mne_maxfilter.py) for Elekta Neuromag MaxFilter(TM) by `Martin Luessi`_
+- Wrapper function and script (mne_maxfilter.py) for Elekta Neuromag MaxFilter(TM) by `Martin Luessi`_
 
-   - Add method to eliminate stimulation artifacts from raw data by linear interpolation or windowing by `Daniel Strohmeier`_.
+- Add method to eliminate stimulation artifacts from raw data by linear interpolation or windowing by `Daniel Strohmeier`_.
 
 Authors
 ~~~~~~~
@@ -1606,12 +2215,12 @@ Authors
 The committer list for this release is the following (preceded by number
 of commits):
 
-   * 118 Alexandre Gramfort
-   * 81  Martin Luessi
-   * 15  Daniel Strohmeier
-   *  4  Christian Brodbeck
-   *  4  Louis Thibault
-   *  2  Brad Buran
+* 118 Alexandre Gramfort
+* 81  Martin Luessi
+* 15  Daniel Strohmeier
+*  4  Christian Brodbeck
+*  4  Louis Thibault
+*  2  Brad Buran
 
 .. _changes_0_3:
 
@@ -1621,29 +2230,29 @@ Version 0.3
 Changelog
 ~~~~~~~~~
 
-   - Sign flip computation for robust label average of signed values by `Alex Gramfort`_.
+- Sign flip computation for robust label average of signed values by `Alex Gramfort`_.
 
-   - Reading and writing of .w files by `Martin Luessi`_.
+- Reading and writing of .w files by `Martin Luessi`_.
 
-   - Support for modifying Raw object and allow raw data preloading with memory mapping by `Martin Luessi`_ and `Alex Gramfort`_.
+- Support for modifying Raw object and allow raw data preloading with memory mapping by `Martin Luessi`_ and `Alex Gramfort`_.
 
-   - Support of arithmetic of Evoked data (useful to concatenate between runs and compute contrasts) by `Alex Gramfort`_.
+- Support of arithmetic of Evoked data (useful to concatenate between runs and compute contrasts) by `Alex Gramfort`_.
 
-   - Support for computing sensor space data from a source estimate using an MNE forward solution by `Martin Luessi`_.
+- Support for computing sensor space data from a source estimate using an MNE forward solution by `Martin Luessi`_.
 
-   - Support of arithmetic of Covariance by `Alex Gramfort`_.
+- Support of arithmetic of Covariance by `Alex Gramfort`_.
 
-   - Write BEM surfaces in Python  by `Alex Gramfort`_.
+- Write BEM surfaces in Python  by `Alex Gramfort`_.
 
-   - Filtering operations and apply_function interface for Raw object by `Martin Luessi`_.
+- Filtering operations and apply_function interface for Raw object by `Martin Luessi`_.
 
-   - Support for complex valued raw fiff files and computation of analytic signal for Raw object by `Martin Luessi`_.
+- Support for complex valued raw fiff files and computation of analytic signal for Raw object by `Martin Luessi`_.
 
-   - Write inverse operators (surface and volume) by `Alex Gramfort`_.
+- Write inverse operators (surface and volume) by `Alex Gramfort`_.
 
-   - Covariance matrix computation with multiple event types by `Martin Luessi`_.
+- Covariance matrix computation with multiple event types by `Martin Luessi`_.
 
-   - New tutorial in the documentation and new classes and functions reference page by `Alex Gramfort`_.
+- New tutorial in the documentation and new classes and functions reference page by `Alex Gramfort`_.
 
 Authors
 ~~~~~~~
@@ -1651,8 +2260,8 @@ Authors
 The committer list for this release is the following (preceded by number
 of commits):
 
-   * 80  Alexandre Gramfort
-   * 51  Martin Luessi
+* 80  Alexandre Gramfort
+* 51  Martin Luessi
 
 Version 0.2
 -----------
@@ -1660,19 +2269,19 @@ Version 0.2
 Changelog
 ~~~~~~~~~
 
-   - New stats functions for FDR correction and Bonferroni by `Alex Gramfort`_.
+- New stats functions for FDR correction and Bonferroni by `Alex Gramfort`_.
 
-   - Faster time-frequency using downsampling trick by `Alex Gramfort`_.
+- Faster time-frequency using downsampling trick by `Alex Gramfort`_.
 
-   - Support for volume source spaces by `Alex Gramfort`_ (requires next MNE release or nightly).
+- Support for volume source spaces by `Alex Gramfort`_ (requires next MNE release or nightly).
 
-   - Improved Epochs handling by `Martin Luessi`_ (slicing, drop_bad_epochs).
+- Improved Epochs handling by `Martin Luessi`_ (slicing, drop_bad_epochs).
 
-   - Bug fix in Epochs + ECG detection by Manfred Kitzbichler.
+- Bug fix in Epochs + ECG detection by Manfred Kitzbichler.
 
-   - New pick_types_evoked function by `Alex Gramfort`_.
+- New pick_types_evoked function by `Alex Gramfort`_.
 
-   - SourceEstimate now supports algebra by `Alex Gramfort`_.
+- SourceEstimate now supports algebra by `Alex Gramfort`_.
 
 API changes summary
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -1680,7 +2289,7 @@ API changes summary
 Here are the code migration instructions when upgrading from mne-python
 version 0.1:
 
-  - New return values for the function find_ecg_events
+- New return values for the function find_ecg_events
 
 Authors
 ~~~~~~~
@@ -1688,10 +2297,10 @@ Authors
 The committer list for this release is the following (preceded by number
 of commits):
 
-   * 33  Alexandre Gramfort
-   * 12  Martin Luessi
-   *  2  Yaroslav Halchenko
-   *  1  Manfred Kitzbichler
+* 33  Alexandre Gramfort
+* 12  Martin Luessi
+*  2  Yaroslav Halchenko
+*  1  Manfred Kitzbichler
 
 .. _Alex Gramfort: http://alexandre.gramfort.net
 
@@ -1703,7 +2312,7 @@ of commits):
 
 .. _Eric Larson: http://larsoner.com
 
-.. _Denis Engemann: https://github.com/dengemann
+.. _Denis Engemann: http://denis-engemann.de
 
 .. _Christian Brodbeck: https://github.com/christianbrodbeck
 
@@ -1771,7 +2380,7 @@ of commits):
 
 .. _Chris Bailey: https://github.com/cjayb
 
-.. _Ross Maddox: http://faculty.washington.edu/rkmaddox/
+.. _Ross Maddox: https://www.urmc.rochester.edu/labs/maddox-lab.aspx
 
 .. _Alexandre Barachant: http://alexandre.barachant.org
 
@@ -1793,7 +2402,7 @@ of commits):
 
 .. _Natalie Klein: http://www.stat.cmu.edu/people/students/neklein
 
-.. _Jon Houck: http://www.unm.edu/~jhouck/
+.. _Jon Houck: https://scholar.google.com/citations?user=DNoS05IAAAAJ&hl=en
 
 .. _Pablo-Arias: https://github.com/Pablo-Arias
 
@@ -1814,3 +2423,51 @@ of commits):
 .. _Andreas Hojlund: https://github.com/ahoejlund
 
 .. _Johannes Niediek: https://github.com/jniediek
+
+.. _Sheraz Khan: https://github.com/SherazKhan
+
+.. _Antti Rantala: https://github.com/Odingod
+
+.. _Keith Doelling: http://science.keithdoelling.com
+
+.. _Paul Pasler: https://github.com/ppasler
+
+.. _Niklas Wilming: https://github.com/nwilming
+
+.. _Annalisa Pascarella: http://www.iac.rm.cnr.it/~pasca/
+
+.. _Luke Bloy: https://scholar.google.com/citations?hl=en&user=Ad_slYcAAAAJ&view_op=list_works&sortby=pubdate
+
+.. _Leonardo Barbosa: https://github.com/noreun
+
+.. _Erkka Heinila: https://github.com/Teekuningas
+
+.. _Andrea Brovelli: http://www.int.univ-amu.fr/_BROVELLI-Andrea_?lang=en
+
+.. _Richard Höchenberger: http://hoechenberger.name
+
+.. _Matt Boggess: https://github.com/mattboggess
+
+.. _Jean-Baptiste Schiratti: https://github.com/jbschiratti
+
+.. _Laura Gwilliams: http://lauragwilliams.github.io
+
+.. _Jesper Duemose Nielsen: https://github.com/jdue
+
+.. _Mathurin Massias: https://mathurinm.github.io/
+
+.. _ramonapariciog: https://github.com/ramonapariciog
+
+.. _Britta Westner: https://github.com/britta-wstnr
+
+.. _Lukáš Hejtmánek: https://github.com/hejtmy
+
+.. _Stefan Repplinger: https://github.com/stfnrpplngr
+
+.. _Okba Bekhelifi: https://github.com/okbalefthanded
+
+.. _Nicolas Barascud: https://github.com/nbara
+
+.. _Alejandro Weinstein: http://ocam.cl
+
+.. _Emily Stephen: http://github.com/emilyps14
diff --git a/environment.yml b/environment.yml
new file mode 100644
index 0000000..f6af0fe
--- /dev/null
+++ b/environment.yml
@@ -0,0 +1,44 @@
+name: mne
+channels:
+- defaults
+- clinicalgraphics  # Needed for VTK
+dependencies:
+- python=3.6.1
+- pip
+- mkl
+- numpy
+- scipy
+- matplotlib
+- pyqt=5
+- vtk>=7
+- pandas
+- scikit-learn
+- h5py
+- pillow
+- statsmodels
+- jupyter
+- nose
+- pytest
+- pytest-cov
+- sphinx
+- joblib
+- psutil
+- numpydoc
+- flake8
+- spyder
+- pip:
+  - mne
+  - "git+https://github.com/enthought/traits.git@a7a83182048c08923953e302658b51b68c802132"
+  - "git+https://github.com/enthought/pyface.git@13a064de48adda3c880350545717d8cf8929afad"
+  - "git+https://github.com/enthought/traitsui.git@ee8ef0a34dfc1db18a8e2c0301cc18d96b7a3e2f"
+  - "git+https://github.com/enthought/mayavi.git"
+  - "git+https://github.com/nipy/PySurfer.git"
+  - nitime
+  - nibabel
+  - nilearn
+  - neo
+  - pytest-sugar
+  - pytest-faulthandler
+  - pydocstyle
+  - sphinx_bootstrap_theme
+  - git+https://github.com/sphinx-gallery/sphinx-gallery.git
diff --git a/environment2.yml b/environment2.yml
new file mode 100644
index 0000000..363eca9
--- /dev/null
+++ b/environment2.yml
@@ -0,0 +1,38 @@
+name: mne27
+channels:
+- defaults
+dependencies:
+- numpy
+- scipy
+- mkl
+- matplotlib
+- pandas
+- scikit-learn
+- h5py
+- pillow
+- statsmodels
+- mayavi
+- jupyter
+- nose
+- pytest
+- pytest-cov
+- sphinx
+- wxpython
+- faulthandler
+- joblib
+- psutil
+- numpydoc
+- flake8
+- spyder
+- pip:
+  - mne
+  - pysurfer
+  - nitime
+  - nibabel
+  - nilearn
+  - neo
+  - pytest-sugar
+  - pytest-faulthandler
+  - pydocstyle
+  - sphinx_bootstrap_theme
+  - git+https://github.com/sphinx-gallery/sphinx-gallery.git
diff --git a/examples/connectivity/plot_cwt_sensor_connectivity.py b/examples/connectivity/plot_cwt_sensor_connectivity.py
index 6a381cd..047a4b5 100644
--- a/examples/connectivity/plot_cwt_sensor_connectivity.py
+++ b/examples/connectivity/plot_cwt_sensor_connectivity.py
@@ -1,16 +1,16 @@
 """
 ==============================================================
-Compute seed based time-frequency connectivity in sensor space
+Compute seed-based time-frequency connectivity in sensor space
 ==============================================================
 
 Computes the connectivity between a seed-gradiometer close to the visual cortex
 and all other gradiometers. The connectivity is computed in the time-frequency
 domain using Morlet wavelets and the debiased Squared Weighted Phase Lag Index
-[1] is used as connectivity metric.
+[1]_ is used as connectivity metric.
 
-[1] Vinck et al. "An improved index of phase-synchronization for electro-
-    physiological data in the presence of volume-conduction, noise and
-    sample-size bias" NeuroImage, vol. 55, no. 4, pp. 1548-1565, Apr. 2011.
+.. [1] Vinck et al. "An improved index of phase-synchronization for electro-
+       physiological data in the presence of volume-conduction, noise and
+       sample-size bias" NeuroImage, vol. 55, no. 4, pp. 1548-1565, Apr. 2011.
 """
 # Author: Martin Luessi <mluessi at nmr.mgh.harvard.edu>
 #
@@ -59,15 +59,15 @@ targets = np.arange(len(picks))
 indices = seed_target_indices(seed, targets)
 
 # Define wavelet frequencies and number of cycles
-cwt_frequencies = np.arange(7, 30, 2)
-cwt_n_cycles = cwt_frequencies / 7.
+cwt_freqs = np.arange(7, 30, 2)
+cwt_n_cycles = cwt_freqs / 7.
 
 # Run the connectivity analysis using 2 parallel jobs
 sfreq = raw.info['sfreq']  # the sampling frequency
 con, freqs, times, _, _ = spectral_connectivity(
     epochs, indices=indices,
     method='wpli2_debiased', mode='cwt_morlet', sfreq=sfreq,
-    cwt_frequencies=cwt_frequencies, cwt_n_cycles=cwt_n_cycles, n_jobs=1)
+    cwt_freqs=cwt_freqs, cwt_n_cycles=cwt_n_cycles, n_jobs=1)
 
 # Mark the seed channel with a value of 1.0, so we can see it in the plot
 con[np.where(indices[1] == seed)] = 1.0
diff --git a/examples/connectivity/plot_mixed_source_space_connectivity.py b/examples/connectivity/plot_mixed_source_space_connectivity.py
new file mode 100644
index 0000000..1abbf7b
--- /dev/null
+++ b/examples/connectivity/plot_mixed_source_space_connectivity.py
@@ -0,0 +1,179 @@
+"""
+===============================================================================
+Compute mixed source space connectivity and visualize it using a circular graph
+===============================================================================
+
+This example computes the all-to-all connectivity between 75 regions in
+a mixed source space based on dSPM inverse solutions and a FreeSurfer cortical
+parcellation. The connectivity is visualized using a circular graph which
+is ordered based on the locations of the regions.
+"""
+# Author: Annalisa Pascarella <a.pascarella at iac.cnr.it>
+#
+# License: BSD (3-clause)
+
+import os.path as op
+import numpy as np
+import mne
+
+from mne.datasets import sample
+from mne import setup_volume_source_space, setup_source_space
+from mne import make_forward_solution
+from mne.io import read_raw_fif
+from mne.minimum_norm import make_inverse_operator, apply_inverse_epochs
+from mne.connectivity import spectral_connectivity
+from mne.viz import circular_layout, plot_connectivity_circle
+
+# Set dir
+data_path = sample.data_path()
+subject = 'sample'
+data_dir = op.join(data_path, 'MEG', subject)
+subjects_dir = op.join(data_path, 'subjects')
+bem_dir = op.join(subjects_dir, subject, 'bem')
+
+# Set file names
+fname_aseg = op.join(subjects_dir, subject, 'mri', 'aseg.mgz')
+
+fname_model = op.join(bem_dir, '%s-5120-bem.fif' % subject)
+fname_bem = op.join(bem_dir, '%s-5120-bem-sol.fif' % subject)
+
+fname_raw = data_dir + '/sample_audvis_filt-0-40_raw.fif'
+fname_trans = data_dir + '/sample_audvis_raw-trans.fif'
+fname_cov = data_dir + '/ernoise-cov.fif'
+fname_event = data_dir + '/sample_audvis_filt-0-40_raw-eve.fif'
+
+# List of sub structures we are interested in. We select only the
+# sub structures we want to include in the source space
+labels_vol = ['Left-Amygdala',
+              'Left-Thalamus-Proper',
+              'Left-Cerebellum-Cortex',
+              'Brain-Stem',
+              'Right-Amygdala',
+              'Right-Thalamus-Proper',
+              'Right-Cerebellum-Cortex']
+
+# Setup a surface-based source space
+src = setup_source_space(subject, subjects_dir=subjects_dir,
+                         spacing='oct6', add_dist=False)
+
+# Setup a volume source space
+# set pos=7.0 for speed issue
+vol_src = setup_volume_source_space(subject, mri=fname_aseg,
+                                    pos=7.0,
+                                    bem=fname_model,
+                                    volume_label=labels_vol,
+                                    subjects_dir=subjects_dir)
+# Generate the mixed source space
+src += vol_src
+
+# compute the fwd matrix
+fwd = make_forward_solution(fname_raw, fname_trans, src, fname_bem,
+                            mindist=5.0,  # ignore sources<=5mm from innerskull
+                            meg=True, eeg=False,
+                            n_jobs=1)
+
+# Load data
+raw = read_raw_fif(fname_raw, preload=True)
+noise_cov = mne.read_cov(fname_cov)
+events = mne.read_events(fname_event)
+
+# Add a bad channel
+raw.info['bads'] += ['MEG 2443']
+
+# Pick MEG channels
+picks = mne.pick_types(raw.info, meg=True, eeg=False, stim=False, eog=True,
+                       exclude='bads')
+
+# Define epochs for left-auditory condition
+event_id, tmin, tmax = 1, -0.2, 0.5
+epochs = mne.Epochs(raw, events, event_id, tmin, tmax, picks=picks,
+                    baseline=(None, 0), reject=dict(mag=4e-12, grad=4000e-13,
+                                                    eog=150e-6))
+
+# Compute inverse solution and for each epoch
+snr = 1.0           # use smaller SNR for raw data
+inv_method = 'dSPM'  # sLORETA, MNE, dSPM
+parc = 'aparc'      # the parcellation to use, e.g., 'aparc' 'aparc.a2009s'
+
+lambda2 = 1.0 / snr ** 2
+
+# Compute inverse operator
+inverse_operator = make_inverse_operator(raw.info, fwd, noise_cov,
+                                         depth=None, fixed=False)
+
+
+stcs = apply_inverse_epochs(epochs, inverse_operator, lambda2, inv_method,
+                            pick_ori=None, return_generator=True)
+
+# Get labels for FreeSurfer 'aparc' cortical parcellation with 34 labels/hemi
+labels_parc = mne.read_labels_from_annot(subject, parc=parc,
+                                         subjects_dir=subjects_dir)
+
+# Average the source estimates within each label of the cortical parcellation
+# and each sub structures contained in the src space
+# If mode = 'mean_flip' this option is used only for the cortical label
+src = inverse_operator['src']
+label_ts = mne.extract_label_time_course(stcs, labels_parc, src,
+                                         mode='mean_flip',
+                                         allow_empty=True,
+                                         return_generator=False)
+
+# We compute the connectivity in the alpha band and plot it using a circular
+# graph layout
+fmin = 8.
+fmax = 13.
+sfreq = raw.info['sfreq']  # the sampling frequency
+con, freqs, times, n_epochs, n_tapers = spectral_connectivity(
+    label_ts, method='pli', mode='multitaper', sfreq=sfreq, fmin=fmin,
+    fmax=fmax, faverage=True, mt_adaptive=True, n_jobs=1)
+
+# We create a list of Label containing also the sub structures
+labels_aseg = mne.get_volume_labels_from_src(src, subject, subjects_dir)
+labels = labels_parc + labels_aseg
+
+# read colors
+node_colors = [label.color for label in labels]
+
+# We reorder the labels based on their location in the left hemi
+label_names = [label.name for label in labels]
+lh_labels = [name for name in label_names if name.endswith('lh')]
+rh_labels = [name for name in label_names if name.endswith('rh')]
+
+# Get the y-location of the label
+label_ypos_lh = list()
+for name in lh_labels:
+    idx = label_names.index(name)
+    ypos = np.mean(labels[idx].pos[:, 1])
+    label_ypos_lh.append(ypos)
+try:
+    idx = label_names.index('Brain-Stem')
+except ValueError:
+    pass
+else:
+    ypos = np.mean(labels[idx].pos[:, 1])
+    lh_labels.append('Brain-Stem')
+    label_ypos_lh.append(ypos)
+
+
+# Reorder the labels based on their location
+lh_labels = [label for (yp, label) in sorted(zip(label_ypos_lh, lh_labels))]
+
+# For the right hemi
+rh_labels = [label[:-2] + 'rh' for label in lh_labels
+             if label != 'Brain-Stem' and label[:-2] + 'rh' in rh_labels]
+
+# Save the plot order
+node_order = list()
+node_order = lh_labels[::-1] + rh_labels
+
+node_angles = circular_layout(label_names, node_order, start_pos=90,
+                              group_boundaries=[0, len(label_names) // 2])
+
+
+# Plot the graph using node colors from the FreeSurfer parcellation. We only
+# show the 300 strongest connections.
+conmat = con[:, :, 0]
+plot_connectivity_circle(conmat, label_names, n_lines=300,
+                         node_angles=node_angles, node_colors=node_colors,
+                         title='All-to-All Connectivity left-Auditory '
+                               'Condition (PLI)')
diff --git a/examples/connectivity/plot_mne_inverse_coherence_epochs.py b/examples/connectivity/plot_mne_inverse_coherence_epochs.py
index cbf10a1..75ba5ad 100644
--- a/examples/connectivity/plot_mne_inverse_coherence_epochs.py
+++ b/examples/connectivity/plot_mne_inverse_coherence_epochs.py
@@ -3,7 +3,7 @@
 Compute coherence in source space using a MNE inverse solution
 ==============================================================
 
-This examples computes the coherence between a seed in the left
+This example computes the coherence between a seed in the left
 auditory cortex and the rest of the brain based on single-trial
 MNE-dSPM inverse solutions.
 
@@ -22,6 +22,14 @@ from mne.connectivity import seed_target_indices, spectral_connectivity
 
 print(__doc__)
 
+###############################################################################
+# Read the data
+# -------------
+#
+# First we'll read in the sample MEG data that we'll use for computing
+# coherence between channels. We'll convert this into epochs in order to
+# compute the event-related coherence.
+
 data_path = sample.data_path()
 subjects_dir = data_path + '/subjects'
 fname_inv = data_path + '/MEG/sample/sample_audvis-meg-oct-6-meg-inv.fif'
@@ -33,45 +41,52 @@ fname_label_lh = data_path + '/MEG/sample/labels/%s.label' % label_name_lh
 event_id, tmin, tmax = 1, -0.2, 0.5
 method = "dSPM"  # use dSPM method (could also be MNE or sLORETA)
 
-# Load data
+# Load data.
 inverse_operator = read_inverse_operator(fname_inv)
 label_lh = mne.read_label(fname_label_lh)
-raw = mne.io.read_raw_fif(fname_raw, add_eeg_ref=False)
+raw = mne.io.read_raw_fif(fname_raw)
 events = mne.read_events(fname_event)
 
-# Add a bad channel
+# Add a bad channel.
 raw.info['bads'] += ['MEG 2443']
 
-# pick MEG channels
+# pick MEG channels.
 picks = mne.pick_types(raw.info, meg=True, eeg=False, stim=False, eog=True,
                        exclude='bads')
 
-# Read epochs
+# Read epochs.
 epochs = mne.Epochs(raw, events, event_id, tmin, tmax, picks=picks,
-                    add_eeg_ref=False, baseline=(None, 0),
+                    baseline=(None, 0),
                     reject=dict(mag=4e-12, grad=4000e-13, eog=150e-6))
 
-# First, we find the most active vertex in the left auditory cortex, which
-# we will later use as seed for the connectivity computation
+###############################################################################
+# Choose channels for coherence estimation
+# ----------------------------------------
+#
+# Next we'll calculate our channel sources. Then we'll find the most active
+# vertex in the left auditory cortex, which we will later use as seed for the
+# connectivity computation.
+
 snr = 3.0
 lambda2 = 1.0 / snr ** 2
 evoked = epochs.average()
 stc = apply_inverse(evoked, inverse_operator, lambda2, method,
                     pick_ori="normal")
 
-# Restrict the source estimate to the label in the left auditory cortex
+# Restrict the source estimate to the label in the left auditory cortex.
 stc_label = stc.in_label(label_lh)
 
-# Find number and index of vertex with most power
+# Find number and index of vertex with most power.
 src_pow = np.sum(stc_label.data ** 2, axis=1)
 seed_vertno = stc_label.vertices[0][np.argmax(src_pow)]
 seed_idx = np.searchsorted(stc.vertices[0], seed_vertno)  # index in orig stc
 
-# Generate index parameter for seed-based connectivity analysis
+# Generate index parameter for seed-based connectivity analysis.
 n_sources = stc.data.shape[0]
 indices = seed_target_indices([seed_idx], np.arange(n_sources))
 
-# Compute inverse solution and for each epoch. By using "return_generator=True"
+###############################################################################
+# Compute the inverse solution for each epoch. By using "return_generator=True"
 # stcs will be a generator object instead of a list. This allows us so to
 # compute the coherence without having to keep all source estimates in memory.
 
@@ -80,18 +95,23 @@ lambda2 = 1.0 / snr ** 2
 stcs = apply_inverse_epochs(epochs, inverse_operator, lambda2, method,
                             pick_ori="normal", return_generator=True)
 
+###############################################################################
+# Compute the coherence between sources
+# -------------------------------------
+#
 # Now we are ready to compute the coherence in the alpha and beta band.
-# fmin and fmax specify the lower and upper freq. for each band, resp.
+# fmin and fmax specify the lower and upper freq. for each band, respectively.
+#
+# To speed things up, we use 2 parallel jobs and use mode='fourier', which
+# uses a FFT with a Hanning window to compute the spectra (instead of
+# a multitaper estimation, which has a lower variance but is slower).
+# By using faverage=True, we directly average the coherence in the alpha and
+# beta band, i.e., we will only get 2 frequency bins.
+
 fmin = (8., 13.)
 fmax = (13., 30.)
 sfreq = raw.info['sfreq']  # the sampling frequency
 
-# Now we compute connectivity. To speed things up, we use 2 parallel jobs
-# and use mode='fourier', which uses a FFT with a Hanning window
-# to compute the spectra (instead of multitaper estimation, which has a
-# lower variance but is slower). By using faverage=True, we directly
-# average the coherence in the alpha and beta band, i.e., we will only
-# get 2 frequency bins
 coh, freqs, times, n_epochs, n_tapers = spectral_connectivity(
     stcs, method='coh', mode='fourier', indices=indices,
     sfreq=sfreq, fmin=fmin, fmax=fmax, faverage=True, n_jobs=1)
@@ -101,15 +121,24 @@ print(freqs[0])
 print('Frequencies in Hz over which coherence was averaged for beta: ')
 print(freqs[1])
 
-# Generate a SourceEstimate with the coherence. This is simple since we
-# used a single seed. For more than one seeds we would have to split coh.
-# Note: We use a hack to save the frequency axis as time
+###############################################################################
+# Generate coherence sources and plot
+# -----------------------------------
+#
+# Finally, we'll generate a SourceEstimate with the coherence. This is simple
+# since we used a single seed. For more than one seed we would have to choose
+# one of the slices within `coh`.
+#
+# .. note:: We use a hack to save the frequency axis as time.
+#
+# Finally, we'll plot this source estimate on the brain.
+
 tmin = np.mean(freqs[0])
 tstep = np.mean(freqs[1]) - tmin
 coh_stc = mne.SourceEstimate(coh, vertices=stc.vertices, tmin=1e-3 * tmin,
                              tstep=1e-3 * tstep, subject='sample')
 
-# Now we can visualize the coherence using the plot method
+# Now we can visualize the coherence using the plot method.
 brain = coh_stc.plot('sample', 'inflated', 'both',
                      time_label='Coherence %0.1f Hz',
                      subjects_dir=subjects_dir,
diff --git a/examples/connectivity/plot_mne_inverse_connectivity_spectrum.py b/examples/connectivity/plot_mne_inverse_connectivity_spectrum.py
index ded2d53..b7d4482 100644
--- a/examples/connectivity/plot_mne_inverse_connectivity_spectrum.py
+++ b/examples/connectivity/plot_mne_inverse_connectivity_spectrum.py
@@ -4,7 +4,7 @@ Compute full spectrum source space connectivity between labels
 ==============================================================
 
 The connectivity is computed between 4 labels across the spectrum
-between 5 and 40 Hz.
+between 7.5 and 40 Hz.
 """
 # Authors: Alexandre Gramfort <alexandre.gramfort at telecom-paristech.fr>
 #
@@ -62,7 +62,7 @@ src = inverse_operator['src']
 label_ts = mne.extract_label_time_course(stcs, labels, src, mode='mean_flip',
                                          return_generator=True)
 
-fmin, fmax = 5., 40.
+fmin, fmax = 7.5, 40.
 sfreq = raw.info['sfreq']  # the sampling frequency
 
 con, freqs, times, n_epochs, n_tapers = spectral_connectivity(
@@ -71,7 +71,6 @@ con, freqs, times, n_epochs, n_tapers = spectral_connectivity(
 
 n_rows, n_cols = con.shape[:2]
 fig, axes = plt.subplots(n_rows, n_cols, sharex=True, sharey=True)
-plt.suptitle('Between labels connectivity')
 for i in range(n_rows):
     for j in range(i + 1):
         if i == j:
@@ -86,11 +85,12 @@ for i in range(n_rows):
             axes[0, i].set_title(names[i])
         if i == (n_rows - 1):
             axes[i, j].set_xlabel(names[j])
-        axes[i, j].set_xlim([fmin, fmax])
-        axes[j, i].set_xlim([fmin, fmax])
+        axes[i, j].set(xlim=[fmin, fmax], ylim=[-0.2, 1])
+        axes[j, i].set(xlim=[fmin, fmax], ylim=[-0.2, 1])
 
         # Show band limits
         for f in [8, 12, 18, 35]:
             axes[i, j].axvline(f, color='k')
             axes[j, i].axvline(f, color='k')
+plt.tight_layout()
 plt.show()
diff --git a/examples/connectivity/plot_mne_inverse_label_connectivity.py b/examples/connectivity/plot_mne_inverse_label_connectivity.py
index fe54314..601a002 100644
--- a/examples/connectivity/plot_mne_inverse_label_connectivity.py
+++ b/examples/connectivity/plot_mne_inverse_label_connectivity.py
@@ -25,6 +25,13 @@ from mne.viz import circular_layout, plot_connectivity_circle
 
 print(__doc__)
 
+###############################################################################
+# Load our data
+# -------------
+#
+# First we'll load the data we'll use in connectivity estimation. We'll use
+# the sample MEG data provided with MNE.
+
 data_path = sample.data_path()
 subjects_dir = data_path + '/subjects'
 fname_inv = data_path + '/MEG/sample/sample_audvis-meg-oct-6-meg-inv.fif'
@@ -49,6 +56,28 @@ epochs = mne.Epochs(raw, events, event_id, tmin, tmax, picks=picks,
                     baseline=(None, 0), reject=dict(mag=4e-12, grad=4000e-13,
                                                     eog=150e-6))
 
+###############################################################################
+# Compute inverse solutions and their connectivity
+# ------------------------------------------------
+#
+# Next, we need to compute the inverse solution for this data. This will return
+# the sources / source activity that we'll use in computing connectivity. We'll
+# compute the connectivity in the alpha band of these sources. We can specify
+# particular frequencies to include in the connectivity with the ``fmin`` and
+# ``fmax`` flags. Notice from the status messages how mne-python:
+#
+# 1. reads an epoch from the raw file
+# 2. applies SSP and baseline correction
+# 3. computes the inverse to obtain a source estimate
+# 4. averages the source estimate to obtain a time series for each label
+# 5. includes the label time series in the connectivity computation
+# 6. moves to the next epoch.
+#
+# This behaviour is because we are using generators. Since we only need to
+# operate on the data one epoch at a time, using a generator allows us to
+# compute connectivity in a computationally efficient manner where the amount
+# of memory (RAM) needed is independent from the number of epochs.
+
 # Compute inverse solution and for each epoch. By using "return_generator=True"
 # stcs will be a generator object instead of a list.
 snr = 1.0  # use lower SNR for single epochs
@@ -68,15 +97,6 @@ src = inverse_operator['src']
 label_ts = mne.extract_label_time_course(stcs, labels, src, mode='mean_flip',
                                          return_generator=True)
 
-# Now we are ready to compute the connectivity in the alpha band. Notice
-# from the status messages, how mne-python: 1) reads an epoch from the raw
-# file, 2) applies SSP and baseline correction, 3) computes the inverse to
-# obtain a source estimate, 4) averages the source estimate to obtain a
-# time series for each label, 5) includes the label time series in the
-# connectivity computation, and then moves to the next epoch. This
-# behaviour is because we are using generators and allows us to
-# compute connectivity in computationally efficient manner where the amount
-# of memory (RAM) needed is independent from the number of epochs.
 fmin = 8.
 fmax = 13.
 sfreq = raw.info['sfreq']  # the sampling frequency
@@ -91,7 +111,11 @@ con_res = dict()
 for method, c in zip(con_methods, con):
     con_res[method] = c[:, :, 0]
 
-# Now, we visualize the connectivity using a circular graph layout
+###############################################################################
+# Make a connectivity plot
+# ------------------------
+#
+# Now, we visualize this connectivity using a circular graph layout.
 
 # First, we reorder the labels based on their location in the left hemi
 label_names = [label.name for label in labels]
@@ -125,9 +149,14 @@ plot_connectivity_circle(con_res['pli'], label_names, n_lines=300,
                          node_angles=node_angles, node_colors=label_colors,
                          title='All-to-All Connectivity left-Auditory '
                                'Condition (PLI)')
-plt.savefig('circle.png', facecolor='black')
 
-# Plot connectivity for both methods in the same plot
+###############################################################################
+# Make two connectivity plots in the same figure
+# ----------------------------------------------
+#
+# We can also assign these connectivity plots to axes in a figure. Below we'll
+# show the connectivity plot using two different connectivity methods.
+
 fig = plt.figure(num=None, figsize=(8, 4), facecolor='black')
 no_names = [''] * len(label_names)
 for ii, method in enumerate(con_methods):
diff --git a/examples/connectivity/plot_mne_inverse_psi_visual.py b/examples/connectivity/plot_mne_inverse_psi_visual.py
index 1c7d69e..9142fc8 100644
--- a/examples/connectivity/plot_mne_inverse_psi_visual.py
+++ b/examples/connectivity/plot_mne_inverse_psi_visual.py
@@ -3,7 +3,7 @@
 Compute Phase Slope Index (PSI) in source space for a visual stimulus
 =====================================================================
 
-This example demonstrates how the Phase Slope Index (PSI) [1] can be computed
+This example demonstrates how the Phase Slope Index (PSI) [1]_ can be computed
 in source space based on single trial dSPM source estimates. In addition,
 the example shows advanced usage of the connectivity estimation routines
 by first extracting a label time course for each epoch and then combining
@@ -15,9 +15,9 @@ widespread activity (a postivive PSI means the label time course is leading).
 
 References
 ----------
-[1] Nolte et al. "Robustly Estimating the Flow Direction of Information in
-Complex Physical Systems", Physical Review Letters, vol. 100, no. 23,
-pp. 1-4, Jun. 2008.
+.. [1] Nolte et al. "Robustly Estimating the Flow Direction of Information in
+       Complex Physical Systems", Physical Review Letters, vol. 100, no. 23,
+       pp. 1-4, Jun. 2008.
 """
 # Author: Martin Luessi <mluessi at nmr.mgh.harvard.edu>
 #
@@ -64,19 +64,22 @@ epochs = mne.Epochs(raw, events, event_id, tmin, tmax, picks=picks,
 snr = 1.0  # use lower SNR for single epochs
 lambda2 = 1.0 / snr ** 2
 stcs = apply_inverse_epochs(epochs, inverse_operator, lambda2, method,
-                            pick_ori="normal", return_generator=False)
+                            pick_ori="normal", return_generator=True)
 
 # Now, we generate seed time series by averaging the activity in the left
 # visual corex
 label = mne.read_label(fname_label)
 src = inverse_operator['src']  # the source space used
-seed_ts = mne.extract_label_time_course(stcs, label, src, mode='mean_flip')
+seed_ts = mne.extract_label_time_course(stcs, label, src, mode='mean_flip',
+                                        verbose='error')
 
 # Combine the seed time course with the source estimates. There will be a total
 # of 7500 signals:
 # index 0: time course extracted from label
 # index 1..7499: dSPM source space time courses
-comb_ts = zip(seed_ts, stcs)
+stcs = apply_inverse_epochs(epochs, inverse_operator, lambda2, method,
+                            pick_ori="normal", return_generator=True)
+comb_ts = list(zip(seed_ts, stcs))
 
 # Construct indices to estimate connectivity between the label time course
 # and all source space time courses
diff --git a/examples/datasets/plot_brainstorm_data.py b/examples/datasets/plot_brainstorm_data.py
index d923ed3..28e7663 100644
--- a/examples/datasets/plot_brainstorm_data.py
+++ b/examples/datasets/plot_brainstorm_data.py
@@ -34,16 +34,16 @@ data_path = bst_raw.data_path()
 
 raw_fname = data_path + '/MEG/bst_raw/' + \
                         'subj001_somatosensory_20111109_01_AUX-f_raw.fif'
-raw = mne.io.read_raw_fif(raw_fname, preload=True, add_eeg_ref=False)
+raw = mne.io.read_raw_fif(raw_fname, preload=True)
 raw.plot()
 
 # set EOG channel
 raw.set_channel_types({'EEG058': 'eog'})
-raw.add_eeg_average_proj()
+raw.set_eeg_reference('average', projection=True)
 
 # show power line interference and remove it
-raw.plot_psd()
-raw.notch_filter(np.arange(60, 181, 60))
+raw.plot_psd(tmax=60., average=False)
+raw.notch_filter(np.arange(60, 181, 60), fir_design='firwin')
 
 events = mne.find_events(raw, stim_channel='UPPT001')
 
diff --git a/examples/datasets/plot_hf_sef_data.py b/examples/datasets/plot_hf_sef_data.py
new file mode 100644
index 0000000..9e93c3c
--- /dev/null
+++ b/examples/datasets/plot_hf_sef_data.py
@@ -0,0 +1,37 @@
+"""
+==============
+HF-SEF dataset
+==============
+
+This example looks at high frequency SEF responses.
+
+"""
+# Author: Jussi Nurminen (jnu at iki.fi)
+#
+# License: BSD (3-clause)
+
+
+import mne
+import os
+from mne.datasets import hf_sef
+
+fname_evoked = os.path.join(hf_sef.data_path(),
+                            'MEG/subject_b/hf_sef_15min-ave.fif')
+
+print(__doc__)
+
+###############################################################################
+# Read evoked data
+evoked = mne.Evoked(fname_evoked)
+
+###############################################################################
+# Create a highpass filtered version
+evoked_hp = evoked.copy()
+evoked_hp.filter(l_freq=300, h_freq=None, fir_design='firwin')
+
+###############################################################################
+# Compare high-pass filtered and unfiltered data on a single channel
+ch = 'MEG0443'
+pick = evoked.ch_names.index(ch)
+edi = {'HF': evoked_hp, 'Regular': evoked}
+mne.viz.plot_compare_evokeds(edi, picks=pick)
diff --git a/examples/datasets/plot_megsim_data_single_trial.py b/examples/datasets/plot_megsim_data_single_trial.py
index c337b51..97fed75 100644
--- a/examples/datasets/plot_megsim_data_single_trial.py
+++ b/examples/datasets/plot_megsim_data_single_trial.py
@@ -29,7 +29,7 @@ epochs_fnames = load_data(condition=condition, data_format='single-trial',
 # Take only 10 trials from the same simulation setup.
 epochs_fnames = [f for f in epochs_fnames if 'sim6_trial_' in f][:10]
 
-evokeds = [read_evokeds(f)[0] for f in epochs_fnames]
+evokeds = [read_evokeds(f, verbose='error')[0] for f in epochs_fnames]
 mean_evoked = combine_evoked(evokeds, weights='nave')
 
 # Visualize the average
diff --git a/examples/datasets/plot_spm_faces_dataset.py b/examples/datasets/plot_spm_faces_dataset.py
index c0a0943..9659725 100644
--- a/examples/datasets/plot_spm_faces_dataset.py
+++ b/examples/datasets/plot_spm_faces_dataset.py
@@ -19,7 +19,6 @@ Runs a full pipeline using MNE-Python:
 #
 # License: BSD (3-clause)
 
-import os.path as op
 import matplotlib.pyplot as plt
 
 import mne
@@ -44,7 +43,7 @@ raw = io.read_raw_ctf(raw_fname % 1, preload=True)  # Take first run
 raw.resample(120., npad='auto')
 
 picks = mne.pick_types(raw.info, meg=True, exclude='bads')
-raw.filter(1, 30, method='iir')
+raw.filter(1, 30, method='fir', fir_design='firwin')
 
 events = mne.find_events(raw, stim_channel='UPPT001')
 
@@ -89,6 +88,8 @@ noise_cov = mne.compute_covariance(epochs, tmax=0, method='shrunk')
 ###############################################################################
 # Visualize fields on MEG helmet
 
+# The transformation here was aligned using the dig-montage. It's included in
+# the spm_faces dataset and is named SPM_dig_montage.fif.
 trans_fname = data_path + ('/MEG/spm/SPM_CTF_MEG_example_faces1_3D_'
                            'raw-trans.fif')
 
@@ -102,16 +103,9 @@ evoked[0].plot_field(maps, time=0.170)
 # Compute forward model
 
 # Make source space
-src_fname = data_path + '/subjects/spm/bem/spm-oct-6-src.fif'
-if not op.isfile(src_fname):
-    src = mne.setup_source_space('spm', src_fname, spacing='oct6',
-                                 subjects_dir=subjects_dir, overwrite=True)
-else:
-    src = mne.read_source_spaces(src_fname)
-
+src = data_path + '/subjects/spm/bem/spm-oct-6-src.fif'
 bem = data_path + '/subjects/spm/bem/spm-5120-5120-5120-bem-sol.fif'
 forward = mne.make_forward_solution(contrast.info, trans_fname, src, bem)
-forward = mne.convert_forward_solution(forward, surf_ori=True)
 
 ###############################################################################
 # Compute inverse solution
@@ -125,9 +119,9 @@ inverse_operator = make_inverse_operator(contrast.info, forward, noise_cov,
 
 # Compute inverse solution on contrast
 stc = apply_inverse(contrast, inverse_operator, lambda2, method, pick_ori=None)
-# stc.save('spm_%s_dSPM_inverse' % constrast.comment)
+# stc.save('spm_%s_dSPM_inverse' % contrast.comment)
 
 # Plot contrast in 3D with PySurfer if available
 brain = stc.plot(hemi='both', subjects_dir=subjects_dir, initial_time=0.170,
-                 views=['ven'])
+                 views=['ven'], clim={'kind': 'value', 'lims': [3., 5.5, 9.]})
 # brain.save_image('dSPM_map.png')
diff --git a/examples/decoding/README.txt b/examples/decoding/README.txt
index ab26871..86e0d08 100644
--- a/examples/decoding/README.txt
+++ b/examples/decoding/README.txt
@@ -1,5 +1,5 @@
 
-Decoding / MVPA
----------------
+Machine Learning (Decoding, Encoding, and MVPA)
+-----------------------------------------------
 
-Decoding, a.k.a. MVPA or machine learning examples.
+Decoding, encoding, and general machine learning examples.
diff --git a/examples/decoding/decoding_rsa.py b/examples/decoding/decoding_rsa.py
new file mode 100644
index 0000000..6ccf1ff
--- /dev/null
+++ b/examples/decoding/decoding_rsa.py
@@ -0,0 +1,185 @@
+"""
+
+.. _rsa_noplot:
+
+====================================
+Representational Similarity Analysis
+====================================
+
+Representational Similarity Analysis is used to perform summary statistics
+on supervised classifications where the number of classes is relatively high.
+It consists in characterizing the structure of the confusion matrix to infer
+the similarity between brain responses and serves as a proxy for characterizing
+the space of mental representations [1]_ [2]_ [3]_.
+
+In this example, we perform RSA on responses to 24 object images (among
+a list of 92 images). Subjects were presented with images of human, animal
+and inanimate objects [4]_. Here we use the 24 unique images of faces
+and body parts.
+
+.. note:: this example will download a very large (~6GB) file, so we will not
+          build the images below.
+
+References
+----------
+
+.. [1] Shepard, R. "Multidimensional scaling, tree-fitting, and clustering."
+       Science 210.4468 (1980): 390-398.
+.. [2] Laakso, A. & Cottrell, G.. "Content and cluster analysis:
+       assessing representational similarity in neural systems." Philosophical
+       psychology 13.1 (2000): 47-76.
+.. [3] Kriegeskorte, N., Marieke, M., & Bandettini.  P. "Representational
+       similarity analysis-connecting the branches of systems neuroscience."
+       Frontiers in systems neuroscience 2 (2008): 4.
+.. [4] Cichy, R. M., Pantazis, D., & Oliva, A. "Resolving human object
+       recognition in space and time." Nature neuroscience (2014): 17(3),
+       455-462.
+"""
+
+# Authors: Jean-Remi King <jeanremi.king at gmail.com>
+#          Jaakko Leppakangas <jaeilepp at student.jyu.fi>
+#          Alexandre Gramfort <alexandre.gramfort at telecom-paristech.fr>
+#
+# License: BSD (3-clause)
+
+import os.path as op
+import numpy as np
+from pandas import read_csv
+import matplotlib.pyplot as plt
+
+from sklearn.model_selection import StratifiedKFold
+from sklearn.pipeline import make_pipeline
+from sklearn.preprocessing import StandardScaler
+from sklearn.linear_model import LogisticRegression
+from sklearn.metrics import roc_auc_score
+from sklearn.manifold import MDS
+
+import mne
+from mne.io import read_raw_fif, concatenate_raws
+from mne.datasets import visual_92_categories
+
+print(__doc__)
+
+data_path = visual_92_categories.data_path()
+
+# Define stimulus - trigger mapping
+fname = op.join(data_path, 'visual_stimuli.csv')
+conds = read_csv(fname)
+print(conds.head(5))
+
+##############################################################################
+# Let's restrict the number of conditions to speed up computation
+max_trigger = 24
+conds = conds[:max_trigger]  # take only the first 24 rows
+
+##############################################################################
+# Define stimulus - trigger mapping
+conditions = []
+for c in conds.values:
+    cond_tags = list(c[:2])
+    cond_tags += [('not-' if i == 0 else '') + conds.columns[k]
+                  for k, i in enumerate(c[2:], 2)]
+    conditions.append('/'.join(map(str, cond_tags)))
+print(conditions[:10])
+
+##############################################################################
+# Let's make the event_id dictionary
+event_id = dict(zip(conditions, conds.trigger + 1))
+event_id['0/human bodypart/human/not-face/animal/natural']
+
+##############################################################################
+# Read MEG data
+n_runs = 4  # 4 for full data (use less to speed up computations)
+fname = op.join(data_path, 'sample_subject_%i_tsss_mc.fif')
+raws = [read_raw_fif(fname % block) for block in range(n_runs)]
+raw = concatenate_raws(raws)
+
+events = mne.find_events(raw, min_duration=.002)
+
+events = events[events[:, 2] <= max_trigger]
+
+##############################################################################
+# Epoch data
+picks = mne.pick_types(raw.info, meg=True)
+epochs = mne.Epochs(raw, events=events, event_id=event_id, baseline=None,
+                    picks=picks, tmin=-.1, tmax=.500, preload=True)
+
+##############################################################################
+# Let's plot some conditions
+epochs['face'].average().plot()
+epochs['not-face'].average().plot()
+
+##############################################################################
+# Representational Similarity Analysis (RSA) is a neuroimaging-specific
+# appelation to refer to statistics applied to the confusion matrix
+# also referred to as the representational dissimilarity matrices (RDM).
+#
+# Compared to the approach from Cichy et al. we'll use a multiclass
+# classifier (Multinomial Logistic Regression) while the paper uses
+# all pairwise binary classification task to make the RDM.
+# Also we use here the ROC-AUC as performance metric while the
+# paper uses accuracy. Finally here for the sake of time we use
+# RSA on a window of data while Cichy et al. did it for all time
+# instants separately.
+
+# Classify using the average signal in the window 50ms to 300ms
+# to focus the classifier on the time interval with best SNR.
+clf = make_pipeline(StandardScaler(),
+                    LogisticRegression(C=1, solver='lbfgs'))
+X = epochs.copy().crop(0.05, 0.3).get_data().mean(axis=2)
+y = epochs.events[:, 2]
+
+classes = set(y)
+cv = StratifiedKFold(n_splits=5, random_state=0, shuffle=True)
+
+# Compute confusion matrix for each cross-validation fold
+y_pred = np.zeros((len(y), len(classes)))
+for train, test in cv.split(X, y):
+    # Fit
+    clf.fit(X[train], y[train])
+    # Probabilistic prediction (necessary for ROC-AUC scoring metric)
+    y_pred[test] = clf.predict_proba(X[test])
+
+##############################################################################
+# Compute confusion matrix using ROC-AUC
+confusion = np.zeros((len(classes), len(classes)))
+for ii, train_class in enumerate(classes):
+    for jj in range(ii, len(classes)):
+        confusion[ii, jj] = roc_auc_score(y == train_class, y_pred[:, jj])
+        confusion[jj, ii] = confusion[ii, jj]
+
+##############################################################################
+# Plot
+labels = [''] * 5 + ['face'] + [''] * 11 + ['bodypart'] + [''] * 6
+fig, ax = plt.subplots(1)
+im = ax.matshow(confusion, cmap='RdBu_r', clim=[0.3, 0.7])
+ax.set_yticks(range(len(classes)))
+ax.set_yticklabels(labels)
+ax.set_xticks(range(len(classes)))
+ax.set_xticklabels(labels, rotation=40, ha='left')
+ax.axhline(11.5, color='k')
+ax.axvline(11.5, color='k')
+plt.colorbar(im)
+plt.tight_layout()
+plt.show()
+
+##############################################################################
+# Confusion matrix related to mental representations have been historically
+# summarized with dimensionality reduction using multi-dimensional scaling [1].
+# See how the face samples cluster together.
+fig, ax = plt.subplots(1)
+mds = MDS(2, random_state=0, dissimilarity='precomputed')
+chance = 0.5
+summary = mds.fit_transform(chance - confusion)
+cmap = plt.get_cmap('rainbow')
+colors = ['r', 'b']
+names = list(conds['condition'].values)
+for color, name in zip(colors, set(names)):
+    sel = np.where([this_name == name for this_name in names])[0]
+    size = 500 if name == 'human face' else 100
+    ax.scatter(summary[sel, 0], summary[sel, 1], s=size,
+               facecolors=color, label=name, edgecolors='k')
+ax.axis('off')
+ax.legend(loc='lower right', scatterpoints=1, ncol=2)
+plt.tight_layout()
+plt.show()
diff --git a/examples/decoding/plot_decoding_csp_eeg.py b/examples/decoding/plot_decoding_csp_eeg.py
index 97509dc..37263a1 100644
--- a/examples/decoding/plot_decoding_csp_eeg.py
+++ b/examples/decoding/plot_decoding_csp_eeg.py
@@ -6,23 +6,22 @@ Motor imagery decoding from EEG data using the Common Spatial Pattern (CSP)
 Decoding of motor imagery applied to EEG data decomposed using CSP.
 Here the classifier is applied to features extracted on CSP filtered signals.
 
-See http://en.wikipedia.org/wiki/Common_spatial_pattern and [1]
-
-The EEGBCI dataset is documented in [2]
-The data set is available at PhysioNet [3]
-
-[1] Zoltan J. Koles. The quantitative extraction and topographic mapping
-    of the abnormal components in the clinical EEG. Electroencephalography
-    and Clinical Neurophysiology, 79(6):440--447, December 1991.
-
-[2] Schalk, G., McFarland, D.J., Hinterberger, T., Birbaumer, N.,
-    Wolpaw, J.R. (2004) BCI2000: A General-Purpose Brain-Computer Interface
-    (BCI) System. IEEE TBME 51(6):1034-1043
-
-[3] Goldberger AL, Amaral LAN, Glass L, Hausdorff JM, Ivanov PCh, Mark RG,
-    Mietus JE, Moody GB, Peng C-K, Stanley HE. (2000) PhysioBank,
-    PhysioToolkit, and PhysioNet: Components of a New Research Resource for
-    Complex Physiologic Signals. Circulation 101(23):e215-e220
+See http://en.wikipedia.org/wiki/Common_spatial_pattern and [1]_. The EEGBCI
+dataset is documented in [2]_. The data set is available at PhysioNet [3]_.
+
+References
+----------
+
+.. [1] Zoltan J. Koles. The quantitative extraction and topographic mapping
+       of the abnormal components in the clinical EEG. Electroencephalography
+       and Clinical Neurophysiology, 79(6):440--447, December 1991.
+.. [2] Schalk, G., McFarland, D.J., Hinterberger, T., Birbaumer, N.,
+       Wolpaw, J.R. (2004) BCI2000: A General-Purpose Brain-Computer Interface
+       (BCI) System. IEEE TBME 51(6):1034-1043.
+.. [3] Goldberger AL, Amaral LAN, Glass L, Hausdorff JM, Ivanov PCh, Mark RG,
+       Mietus JE, Moody GB, Peng C-K, Stanley HE. (2000) PhysioBank,
+       PhysioToolkit, and PhysioNet: Components of a New Research Resource for
+       Complex Physiologic Signals. Circulation 101(23):e215-e220.
 """
 # Authors: Martin Billinger <martin.billinger at tugraz.at>
 #
@@ -31,6 +30,10 @@ The data set is available at PhysioNet [3]
 import numpy as np
 import matplotlib.pyplot as plt
 
+from sklearn.pipeline import Pipeline
+from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
+from sklearn.model_selection import ShuffleSplit, cross_val_score
+
 from mne import Epochs, pick_types, find_events
 from mne.channels import read_layout
 from mne.io import concatenate_raws, read_raw_edf
@@ -50,14 +53,15 @@ subject = 1
 runs = [6, 10, 14]  # motor imagery: hands vs feet
 
 raw_fnames = eegbci.load_data(subject, runs)
-raw_files = [read_raw_edf(f, preload=True) for f in raw_fnames]
+raw_files = [read_raw_edf(f, preload=True, stim_channel='auto') for f in
+             raw_fnames]
 raw = concatenate_raws(raw_files)
 
 # strip channel names of "." characters
 raw.rename_channels(lambda x: x.strip('.'))
 
 # Apply band-pass filter
-raw.filter(7., 30., method='iir')
+raw.filter(7., 30., fir_design='firwin', skip_by_annotation='edge')
 
 events = find_events(raw, shortest_event=0, stim_channel='STI 014')
 
@@ -67,30 +71,26 @@ picks = pick_types(raw.info, meg=False, eeg=True, stim=False, eog=False,
 # Read epochs (train will be done only between 1 and 2s)
 # Testing will be done with a running classifier
 epochs = Epochs(raw, events, event_id, tmin, tmax, proj=True, picks=picks,
-                baseline=None, preload=True, add_eeg_ref=False)
+                baseline=None, preload=True)
 epochs_train = epochs.copy().crop(tmin=1., tmax=2.)
 labels = epochs.events[:, -1] - 2
 
 ###############################################################################
 # Classification with linear discrimant analysis
 
-from sklearn.lda import LDA  # noqa
-from sklearn.cross_validation import ShuffleSplit  # noqa
-
-# Assemble a classifier
-svc = LDA()
-csp = CSP(n_components=4, reg=None, log=True)
-
 # Define a monte-carlo cross-validation generator (reduce variance):
-cv = ShuffleSplit(len(labels), 10, test_size=0.2, random_state=42)
 scores = []
 epochs_data = epochs.get_data()
 epochs_data_train = epochs_train.get_data()
+cv = ShuffleSplit(10, test_size=0.2, random_state=42)
+cv_split = cv.split(epochs_data_train)
+
+# Assemble a classifier
+lda = LinearDiscriminantAnalysis()
+csp = CSP(n_components=4, reg=None, log=True, norm_trace=False)
 
 # Use scikit-learn Pipeline with cross_val_score function
-from sklearn.pipeline import Pipeline  # noqa
-from sklearn.cross_validation import cross_val_score  # noqa
-clf = Pipeline([('CSP', csp), ('SVC', svc)])
+clf = Pipeline([('CSP', csp), ('LDA', lda)])
 scores = cross_val_score(clf, epochs_data_train, labels, cv=cv, n_jobs=1)
 
 # Printing the results
@@ -102,14 +102,9 @@ print("Classification accuracy: %f / Chance level: %f" % (np.mean(scores),
 # plot CSP patterns estimated on full data for visualization
 csp.fit_transform(epochs_data, labels)
 
-evoked = epochs.average()
-evoked.data = csp.patterns_.T
-evoked.times = np.arange(evoked.data.shape[0])
-
 layout = read_layout('EEG1005')
-evoked.plot_topomap(times=[0, 1, 2, 3, 4, 5], ch_type='eeg', layout=layout,
-                    scale_time=1, time_format='%i', scale=1,
-                    unit='Patterns (AU)', size=1.5)
+csp.plot_patterns(epochs.info, layout=layout, ch_type='eeg',
+                  units='Patterns (AU)', size=1.5)
 
 ###############################################################################
 # Look at performance over time
@@ -121,20 +116,20 @@ w_start = np.arange(0, epochs_data.shape[2] - w_length, w_step)
 
 scores_windows = []
 
-for train_idx, test_idx in cv:
+for train_idx, test_idx in cv_split:
     y_train, y_test = labels[train_idx], labels[test_idx]
 
     X_train = csp.fit_transform(epochs_data_train[train_idx], y_train)
     X_test = csp.transform(epochs_data_train[test_idx])
 
     # fit classifier
-    svc.fit(X_train, y_train)
+    lda.fit(X_train, y_train)
 
     # running classifier: test classifier on sliding window
     score_this_window = []
     for n in w_start:
         X_test = csp.transform(epochs_data[test_idx][:, :, n:(n + w_length)])
-        score_this_window.append(svc.score(X_test, y_test))
+        score_this_window.append(lda.score(X_test, y_test))
     scores_windows.append(score_this_window)
 
 # Plot scores over time
diff --git a/examples/decoding/plot_decoding_csp_space.py b/examples/decoding/plot_decoding_csp_space.py
index fb339d0..a1ed2c0 100644
--- a/examples/decoding/plot_decoding_csp_space.py
+++ b/examples/decoding/plot_decoding_csp_space.py
@@ -6,12 +6,14 @@ Decoding in sensor space data using the Common Spatial Pattern (CSP)
 Decoding applied to MEG data in sensor space decomposed using CSP.
 Here the classifier is applied to features extracted on CSP filtered signals.
 
-See http://en.wikipedia.org/wiki/Common_spatial_pattern and [1]
+See http://en.wikipedia.org/wiki/Common_spatial_pattern and [1]_.
 
-[1] Zoltan J. Koles. The quantitative extraction and topographic mapping
-    of the abnormal components in the clinical EEG. Electroencephalography
-    and Clinical Neurophysiology, 79(6):440--447, December 1991.
+References
+----------
 
+.. [1] Zoltan J. Koles. The quantitative extraction and topographic mapping
+       of the abnormal components in the clinical EEG. Electroencephalography
+       and Clinical Neurophysiology, 79(6):440--447, December 1991.
 """
 # Authors: Alexandre Gramfort <alexandre.gramfort at telecom-paristech.fr>
 #          Romain Trachel <romain.trachel at inria.fr>
@@ -38,7 +40,7 @@ event_id = dict(aud_l=1, vis_l=3)
 
 # Setup for reading the raw data
 raw = io.read_raw_fif(raw_fname, preload=True)
-raw.filter(2, None, method='iir')  # replace baselining with high-pass
+raw.filter(2, None, fir_design='firwin')  # replace baselining with high-pass
 events = mne.read_events(event_fname)
 
 raw.info['bads'] = ['MEG 2443']  # set bad channels
@@ -56,19 +58,19 @@ evoked = epochs.average()
 # Decoding in sensor space using a linear SVM
 
 from sklearn.svm import SVC  # noqa
-from sklearn.cross_validation import ShuffleSplit  # noqa
+from sklearn.model_selection import ShuffleSplit  # noqa
 from mne.decoding import CSP  # noqa
 
 n_components = 3  # pick some components
 svc = SVC(C=1, kernel='linear')
-csp = CSP(n_components=n_components)
+csp = CSP(n_components=n_components, norm_trace=False)
 
 # Define a monte-carlo cross-validation generator (reduce variance):
-cv = ShuffleSplit(len(labels), 10, test_size=0.2, random_state=42)
+cv = ShuffleSplit(n_splits=10, test_size=0.2, random_state=42)
 scores = []
 epochs_data = epochs.get_data()
 
-for train_idx, test_idx in cv:
+for train_idx, test_idx in cv.split(labels):
     y_train, y_test = labels[train_idx], labels[test_idx]
 
     X_train = csp.fit_transform(epochs_data[train_idx], y_train)
@@ -88,14 +90,14 @@ print("Classification accuracy: %f / Chance level: %f" % (np.mean(scores),
 # Or use much more convenient scikit-learn cross_val_score function using
 # a Pipeline
 from sklearn.pipeline import Pipeline  # noqa
-from sklearn.cross_validation import cross_val_score  # noqa
-cv = ShuffleSplit(len(labels), 10, test_size=0.2, random_state=42)
+from sklearn.model_selection import cross_val_score  # noqa
+cv = ShuffleSplit(n_splits=10, test_size=0.2, random_state=42)
 clf = Pipeline([('CSP', csp), ('SVC', svc)])
 scores = cross_val_score(clf, epochs_data, labels, cv=cv, n_jobs=1)
 print(scores.mean())  # should match results above
 
 # And using reuglarized csp with Ledoit-Wolf estimator
-csp = CSP(n_components=n_components, reg='ledoit_wolf')
+csp = CSP(n_components=n_components, reg='ledoit_wolf', norm_trace=False)
 clf = Pipeline([('CSP', csp), ('SVC', svc)])
 scores = cross_val_score(clf, epochs_data, labels, cv=cv, n_jobs=1)
 print(scores.mean())  # should get better results than above
diff --git a/examples/decoding/plot_decoding_csp_timefreq.py b/examples/decoding/plot_decoding_csp_timefreq.py
new file mode 100644
index 0000000..1a50583
--- /dev/null
+++ b/examples/decoding/plot_decoding_csp_timefreq.py
@@ -0,0 +1,163 @@
+"""
+============================================================================
+Decoding in time-frequency space data using the Common Spatial Pattern (CSP)
+============================================================================
+
+
+The time-frequency decomposition is estimated by iterating over raw data that
+has been band-passed at different frequencies. This is used to compute a
+covariance matrix over each epoch or a rolling time-window and extract the CSP
+filtered signals. A linear discriminant classifier is then applied to these
+signals.
+"""
+# Authors: Laura Gwilliams <laura.gwilliams at nyu.edu>
+#          Jean-Remi King <jeanremi.king at gmail.com>
+#          Alex Barachant <alexandre.barachant at gmail.com>
+#          Alexandre Gramfort <alexandre.gramfort at telecom-paristech.fr>
+#
+# License: BSD (3-clause)
+
+import numpy as np
+import matplotlib.pyplot as plt
+
+from mne import Epochs, find_events, create_info
+from mne.io import concatenate_raws, read_raw_edf
+from mne.datasets import eegbci
+from mne.decoding import CSP
+from mne.time_frequency import AverageTFR
+
+from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
+from sklearn.model_selection import StratifiedKFold, cross_val_score
+from sklearn.pipeline import make_pipeline
+from sklearn.preprocessing import LabelEncoder
+
+###############################################################################
+# Set parameters and read data
+event_id = dict(hands=2, feet=3)  # motor imagery: hands vs feet
+subject = 1
+runs = [6, 10, 14]
+raw_fnames = eegbci.load_data(subject, runs)
+raw_files = [read_raw_edf(f, stim_channel='auto', preload=True)
+             for f in raw_fnames]
+raw = concatenate_raws(raw_files)
+
+# Extract information from the raw file
+sfreq = raw.info['sfreq']
+events = find_events(raw, shortest_event=0, stim_channel='STI 014')
+raw.pick_types(meg=False, eeg=True, stim=False, eog=False, exclude='bads')
+
+# Assemble the classifier using scikit-learn pipeline
+clf = make_pipeline(CSP(n_components=4, reg=None, log=True, norm_trace=False),
+                    LinearDiscriminantAnalysis())
+n_splits = 5  # how many folds to use for cross-validation
+cv = StratifiedKFold(n_splits=n_splits, shuffle=True)
+
+# Classification & Time-frequency parameters
+tmin, tmax = -.200, 2.000
+n_cycles = 10.  # how many complete cycles: used to define window size
+min_freq = 5.
+max_freq = 25.
+n_freqs = 8  # how many frequency bins to use
+
+# Assemble list of frequency range tuples
+freqs = np.linspace(min_freq, max_freq, n_freqs)  # assemble frequencies
+freq_ranges = list(zip(freqs[:-1], freqs[1:]))  # make freqs list of tuples
+
+# Infer window spacing from the max freq and number of cycles to avoid gaps
+window_spacing = (n_cycles / np.max(freqs) / 2.)
+centered_w_times = np.arange(tmin, tmax, window_spacing)[1:]
+n_windows = len(centered_w_times)
+
+# Instantiate label encoder
+le = LabelEncoder()
+
+###############################################################################
+# Loop through frequencies, apply classifier and save scores
+
+# init scores
+freq_scores = np.zeros((n_freqs - 1,))
+
+# Loop through each frequency range of interest
+for freq, (fmin, fmax) in enumerate(freq_ranges):
+
+    # Infer window size based on the frequency being used
+    w_size = n_cycles / ((fmax + fmin) / 2.)  # in seconds
+
+    # Apply band-pass filter to isolate the specified frequencies
+    raw_filter = raw.copy().filter(fmin, fmax, n_jobs=1, fir_design='firwin',
+                                   skip_by_annotation='edge')
+
+    # Extract epochs from filtered data, padded by window size
+    epochs = Epochs(raw_filter, events, event_id, tmin - w_size, tmax + w_size,
+                    proj=False, baseline=None, preload=True)
+    epochs.drop_bad()
+    y = le.fit_transform(epochs.events[:, 2])
+
+    X = epochs.get_data()
+
+    # Save mean scores over folds for each frequency and time window
+    freq_scores[freq] = np.mean(cross_val_score(estimator=clf, X=X, y=y,
+                                                scoring='roc_auc', cv=cv,
+                                                n_jobs=1), axis=0)
+
+###############################################################################
+# Plot frequency results
+
+plt.bar(left=freqs[:-1], height=freq_scores, width=np.diff(freqs)[0],
+        align='edge', edgecolor='black')
+plt.xticks(freqs)
+plt.ylim([0, 1])
+plt.axhline(len(epochs['feet']) / len(epochs), color='k', linestyle='--',
+            label='chance level')
+plt.legend()
+plt.xlabel('Frequency (Hz)')
+plt.ylabel('Decoding Scores')
+plt.title('Frequency Decoding Scores')
+
+###############################################################################
+# Loop through frequencies and time, apply classifier and save scores
+
+# init scores
+tf_scores = np.zeros((n_freqs - 1, n_windows))
+
+# Loop through each frequency range of interest
+for freq, (fmin, fmax) in enumerate(freq_ranges):
+
+    # Infer window size based on the frequency being used
+    w_size = n_cycles / ((fmax + fmin) / 2.)  # in seconds
+
+    # Apply band-pass filter to isolate the specified frequencies
+    raw_filter = raw.copy().filter(fmin, fmax, n_jobs=1, fir_design='firwin',
+                                   skip_by_annotation='edge')
+
+    # Extract epochs from filtered data, padded by window size
+    epochs = Epochs(raw_filter, events, event_id, tmin - w_size, tmax + w_size,
+                    proj=False, baseline=None, preload=True)
+    epochs.drop_bad()
+    y = le.fit_transform(epochs.events[:, 2])
+
+    # Roll covariance, csp and lda over time
+    for t, w_time in enumerate(centered_w_times):
+
+        # Center the min and max of the window
+        w_tmin = w_time - w_size / 2.
+        w_tmax = w_time + w_size / 2.
+
+        # Crop data into time-window of interest
+        X = epochs.copy().crop(w_tmin, w_tmax).get_data()
+
+        # Save mean scores over folds for each frequency and time window
+        tf_scores[freq, t] = np.mean(cross_val_score(estimator=clf, X=X, y=y,
+                                                     scoring='roc_auc', cv=cv,
+                                                     n_jobs=1), axis=0)
+
+###############################################################################
+# Plot time-frequency results
+
+# Set up time frequency object
+av_tfr = AverageTFR(create_info(['freq'], sfreq), tf_scores[np.newaxis, :],
+                    centered_w_times, freqs[1:], 1)
+
+chance = np.mean(y)  # set chance level to white in the plot
+av_tfr.plot([0], vmin=chance, title="Time-Frequency Decoding Scores",
+            cmap=plt.cm.Reds)
diff --git a/examples/decoding/plot_decoding_spatio_temporal_source.py b/examples/decoding/plot_decoding_spatio_temporal_source.py
index 28cd1a7..333b229 100644
--- a/examples/decoding/plot_decoding_spatio_temporal_source.py
+++ b/examples/decoding/plot_decoding_spatio_temporal_source.py
@@ -3,23 +3,29 @@
 Decoding source space data
 ==========================
 
-Decoding, a.k.a MVPA or supervised machine learning applied to MEG
-data in source space on the left cortical surface. Here f-test feature
-selection is employed to confine the classification to the potentially
-relevant features. The classifier then is trained to selected features of
-epochs in source space.
+Decoding to MEG data in source space on the left cortical surface. Here
+univariate feature selection is employed for speed purposes to confine the
+classification to a small number of potentially relevant features. The
+classifier then is trained to selected features of epochs in source space.
 """
 # Author: Denis A. Engemann <denis.engemann at gmail.com>
 #         Alexandre Gramfort <alexandre.gramfort at telecom-paristech.fr>
+#         Jean-Remi King <jeanremi.king at gmail.com>
 #
 # License: BSD (3-clause)
-
-import mne
 import os
 import numpy as np
+import matplotlib.pyplot as plt
+from sklearn.pipeline import make_pipeline
+from sklearn.preprocessing import StandardScaler
+from sklearn.feature_selection import SelectKBest, f_classif
+from sklearn.linear_model import LogisticRegression
+import mne
 from mne import io
 from mne.datasets import sample
 from mne.minimum_norm import apply_inverse_epochs, read_inverse_operator
+from mne.decoding import (cross_val_multiscore, LinearModel, SlidingEstimator,
+                          get_coef)
 
 print(__doc__)
 
@@ -32,7 +38,6 @@ os.environ['SUBJECTS_DIR'] = subjects_dir
 
 ###############################################################################
 # Set parameters
-
 raw_fname = data_path + '/MEG/sample/sample_audvis_filt-0-40_raw.fif'
 event_fname = data_path + '/MEG/sample/sample_audvis_filt-0-40_raw-eve.fif'
 fname_cov = data_path + '/MEG/sample/sample_audvis-cov.fif'
@@ -44,7 +49,7 @@ event_id = dict(aud_r=2, vis_r=4)  # load contra-lateral conditions
 
 # Setup for reading the raw data
 raw = io.read_raw_fif(raw_fname, preload=True)
-raw.filter(2, None, method='iir')  # replace baselining with high-pass
+raw.filter(0.1, None, fir_design='firwin')
 events = mne.read_events(event_fname)
 
 # Set up pick list: MEG - bad channels (modify to your needs)
@@ -58,95 +63,53 @@ epochs = mne.Epochs(raw, events, event_id, tmin, tmax, proj=True,
                     reject=dict(grad=4000e-13, eog=150e-6),
                     decim=5)  # decimate to save memory and increase speed
 
-epochs.equalize_event_counts(list(event_id.keys()), 'mintime', copy=False)
-epochs_list = [epochs[k] for k in event_id]
-
+###############################################################################
 # Compute inverse solution
 snr = 3.0
-lambda2 = 1.0 / snr ** 2
-method = "dSPM"  # use dSPM method (could also be MNE or sLORETA)
-n_times = len(epochs.times)
-n_vertices = 3732
-n_epochs = len(epochs.events)
-
-# Load data and compute inverse solution and stcs for each epoch.
-
 noise_cov = mne.read_cov(fname_cov)
 inverse_operator = read_inverse_operator(fname_inv)
-X = np.zeros([n_epochs, n_vertices, n_times])
 
-# to save memory, we'll load and transform our epochs step by step.
-for condition_count, ep in zip([0, n_epochs / 2], epochs_list):
-    stcs = apply_inverse_epochs(ep, inverse_operator, lambda2,
-                                method, pick_ori="normal",  # saves us memory
-                                return_generator=True)
-    for jj, stc in enumerate(stcs):
-        X[condition_count + jj] = stc.lh_data
+stcs = apply_inverse_epochs(epochs, inverse_operator,
+                            lambda2=1.0 / snr ** 2, verbose=False,
+                            method="dSPM", pick_ori="normal")
 
 ###############################################################################
-# Decoding in sensor space using a linear SVM
-
-# Make arrays X and y such that :
-# X is 3d with X.shape[0] is the total number of epochs to classify
-# y is filled with integers coding for the class to predict
-# We must have X.shape[0] equal to y.shape[0]
-
-# we know the first half belongs to the first class, the second one
-y = np.repeat([0, 1], len(X) / 2)   # belongs to the second class
-X = X.reshape(n_epochs, n_vertices * n_times)
-# we have to normalize the data before supplying them to our classifier
-X -= X.mean(axis=0)
-X /= X.std(axis=0)
-
-# prepare classifier
-from sklearn.svm import SVC  # noqa
-from sklearn.cross_validation import ShuffleSplit  # noqa
-
-# Define a monte-carlo cross-validation generator (reduce variance):
-n_splits = 10
-clf = SVC(C=1, kernel='linear')
-cv = ShuffleSplit(len(X), n_splits, test_size=0.2)
-
-# setup feature selection and classification pipeline
-from sklearn.feature_selection import SelectKBest, f_classif  # noqa
-from sklearn.pipeline import Pipeline  # noqa
-
-# we will use an ANOVA f-test to preselect relevant spatio-temporal units
-feature_selection = SelectKBest(f_classif, k=500)  # take the best 500
-# to make life easier we will create a pipeline object
-anova_svc = Pipeline([('anova', feature_selection), ('svc', clf)])
-
-# initialize score and feature weights result arrays
-scores = np.zeros(n_splits)
-feature_weights = np.zeros([n_vertices, n_times])
-
-# hold on, this may take a moment
-for ii, (train, test) in enumerate(cv):
-    anova_svc.fit(X[train], y[train])
-    y_pred = anova_svc.predict(X[test])
-    y_test = y[test]
-    scores[ii] = np.sum(y_pred == y_test) / float(len(y_test))
-    feature_weights += feature_selection.inverse_transform(clf.coef_) \
-        .reshape(n_vertices, n_times)
-
-print('Average prediction accuracy: %0.3f | standard deviation:  %0.3f'
-      % (scores.mean(), scores.std()))
-
-# prepare feature weights for visualization
-feature_weights /= (ii + 1)  # create average weights
-# create mask to avoid division error
-feature_weights = np.ma.masked_array(feature_weights, feature_weights == 0)
-# normalize scores for visualization purposes
-feature_weights /= feature_weights.std(axis=1)[:, None]
-feature_weights -= feature_weights.mean(axis=1)[:, None]
-
-# unmask, take absolute values, emulate f-value scale
-feature_weights = np.abs(feature_weights.data) * 10
+# Decoding in sensor space using a logistic regression
+
+# Retrieve source space data into an array
+X = np.array([stc.lh_data for stc in stcs])  # only keep left hemisphere
+y = epochs.events[:, 2]
+
+# prepare a series of classifier applied at each time sample
+clf = make_pipeline(StandardScaler(),  # z-score normalization
+                    SelectKBest(f_classif, k=500),  # select features for speed
+                    LinearModel(LogisticRegression(C=1)))
+time_decod = SlidingEstimator(clf, scoring='roc_auc')
+
+# Run cross-validated decoding analyses:
+scores = cross_val_multiscore(time_decod, X, y, cv=5, n_jobs=1)
+
+# Plot average decoding scores of 5 splits
+fig, ax = plt.subplots(1)
+ax.plot(epochs.times, scores.mean(0), label='score')
+ax.axhline(.5, color='k', linestyle='--', label='chance')
+ax.axvline(0, color='k')
+plt.legend()
+
+###############################################################################
+# To investigate weights, we need to retrieve the patterns of a fitted model
+
+# The fitting needs not be cross validated because the weights are based on
+# the training sets
+time_decod.fit(X, y)
+
+# Retrieve patterns after inversing the z-score normalization step:
+patterns = get_coef(time_decod, 'patterns_', inverse_transform=True)
 
+stc = stcs[0]  # for convenience, lookup parameters from first stc
 vertices = [stc.lh_vertno, np.array([], int)]  # empty array for right hemi
-stc_feat = mne.SourceEstimate(feature_weights, vertices=vertices,
-                              tmin=stc.tmin, tstep=stc.tstep,
-                              subject='sample')
+stc_feat = mne.SourceEstimate(np.abs(patterns), vertices=vertices,
+                              tmin=stc.tmin, tstep=stc.tstep, subject='sample')
 
 brain = stc_feat.plot(views=['lat'], transparent=True,
                       initial_time=0.1, time_unit='s')
diff --git a/examples/decoding/plot_decoding_spoc_CMC.py b/examples/decoding/plot_decoding_spoc_CMC.py
new file mode 100644
index 0000000..5f61fe6
--- /dev/null
+++ b/examples/decoding/plot_decoding_spoc_CMC.py
@@ -0,0 +1,84 @@
+"""
+====================================
+Continuous Target Decoding with SPoC
+====================================
+
+Source Power Comodulation (SPoC) [1]_ allows to identify the composition of
+orthogonal spatial filters that maximally correlate with a continuous target.
+
+SPoC can be seen as an extension of the CSP for continuous variables.
+
+Here, SPoC is applied to decode the (continuous) fluctuation of an
+electromyogram from MEG beta activity using data from `Cortico-Muscular
+Coherence example of fieldtrip
+<http://www.fieldtriptoolbox.org/tutorial/coherence>`_
+
+References
+----------
+
+.. [1] Dahne, S., et al (2014). SPoC: a novel framework for relating the
+       amplitude of neuronal oscillations to behaviorally relevant parameters.
+       NeuroImage, 86, 111-122.
+"""
+
+# Author: Alexandre Barachant <alexandre.barachant at gmail.com>
+#         Jean-Remi King <jeanremi.king at gmail.com>
+#
+# License: BSD (3-clause)
+
+import matplotlib.pyplot as plt
+
+import mne
+from mne import Epochs
+from mne.decoding import SPoC
+from mne.datasets.fieldtrip_cmc import data_path
+
+from sklearn.pipeline import make_pipeline
+from sklearn.linear_model import Ridge
+from sklearn.model_selection import KFold, cross_val_predict
+
+# define parameters
+fname = data_path() + '/SubjectCMC.ds'
+raw = mne.io.read_raw_ctf(fname)
+raw.crop(50., 250.).load_data()  # crop for memory purposes
+
+# Filter muscular activity to only keep high frequencies
+emg = raw.copy().pick_channels(['EMGlft'])
+emg.filter(20., None, fir_design='firwin')
+
+# Filter MEG data to focus on alpha band
+raw.pick_types(meg=True, ref_meg=True, eeg=False, eog=False)
+raw.filter(15., 30., fir_design='firwin')
+
+# Build epochs as sliding windows over the continuous raw file
+events = mne.make_fixed_length_events(raw, id=1, duration=.250)
+
+# Epoch length is 1.5 second
+meg_epochs = Epochs(raw, events, tmin=0., tmax=1.500, baseline=None,
+                    detrend=1, decim=8)
+emg_epochs = Epochs(emg, events, tmin=0., tmax=1.500, baseline=None)
+
+# Prepare classification
+X = meg_epochs.get_data()
+y = emg_epochs.get_data().var(axis=2)[:, 0]  # target is EMG power
+
+# Classification pipeline with SPoC spatial filtering and Ridge Regression
+clf = make_pipeline(SPoC(n_components=2, log=True, reg='oas'), Ridge())
+
+# Define a two fold cross-validation
+cv = KFold(n_splits=2, shuffle=False)
+
+# Run cross validaton
+y_preds = cross_val_predict(clf, X, y, cv=cv)
+
+# plot the True EMG power and the EMG power predicted from MEG data
+fig, ax = plt.subplots(1, 1, figsize=[10, 4])
+times = raw.times[meg_epochs.events[:, 0] - raw.first_samp]
+ax.plot(times, y_preds, color='b', label='Predicted EMG')
+ax.plot(times, y, color='r', label='True EMG')
+ax.set_xlabel('Time (s)')
+ax.set_ylabel('EMG Power')
+ax.set_title('SPoC MEG Predictions')
+plt.legend()
+mne.viz.tight_layout()
+plt.show()
diff --git a/examples/decoding/plot_decoding_time_generalization_conditions.py b/examples/decoding/plot_decoding_time_generalization_conditions.py
index 77643c1..26f771c 100644
--- a/examples/decoding/plot_decoding_time_generalization_conditions.py
+++ b/examples/decoding/plot_decoding_time_generalization_conditions.py
@@ -3,15 +3,17 @@
 Decoding sensor space data with generalization across time and conditions
 =========================================================================
 
-This example runs the analysis computed in:
+This example runs the analysis described in [1]_. It illustrates how one can
+fit a linear classifier to identify a discriminatory topography at a given time
+instant and subsequently assess whether this linear model can accurately
+predict all of the time samples of a second set of conditions.
 
-King & Dehaene (2014) 'Characterizing the dynamics of mental
-representations: the temporal generalization method', Trends In Cognitive
-Sciences, 18(4), 203-210.
-http://www.ncbi.nlm.nih.gov/pubmed/24593982
+References
+----------
 
-The idea is to learn at one time instant and assess if the decoder
-can predict accurately over time and on a second set of conditions.
+.. [1] King & Dehaene (2014) 'Characterizing the dynamics of mental
+       representations: the Temporal Generalization method', Trends In
+       Cognitive Sciences, 18(4), 203-210. doi: 10.1016/j.tics.2014.01.002.
 """
 # Authors: Jean-Remi King <jeanremi.king at gmail.com>
 #          Alexandre Gramfort <alexandre.gramfort at telecom-paristech.fr>
@@ -19,11 +21,15 @@ can predict accurately over time and on a second set of conditions.
 #
 # License: BSD (3-clause)
 
-import numpy as np
+import matplotlib.pyplot as plt
+
+from sklearn.pipeline import make_pipeline
+from sklearn.preprocessing import StandardScaler
+from sklearn.linear_model import LogisticRegression
 
 import mne
 from mne.datasets import sample
-from mne.decoding import GeneralizationAcrossTime
+from mne.decoding import GeneralizingEstimator
 
 print(__doc__)
 
@@ -34,36 +40,40 @@ raw_fname = data_path + '/MEG/sample/sample_audvis_filt-0-40_raw.fif'
 events_fname = data_path + '/MEG/sample/sample_audvis_filt-0-40_raw-eve.fif'
 raw = mne.io.read_raw_fif(raw_fname, preload=True)
 picks = mne.pick_types(raw.info, meg=True, exclude='bads')  # Pick MEG channels
-raw.filter(1, 30, method='fft')  # Band pass filtering signals
+raw.filter(1., 30., fir_design='firwin')  # Band pass filtering signals
 events = mne.read_events(events_fname)
-event_id = {'AudL': 1, 'AudR': 2, 'VisL': 3, 'VisR': 4}
+event_id = {'Auditory/Left': 1, 'Auditory/Right': 2,
+            'Visual/Left': 3, 'Visual/Right': 4}
+tmin = -0.050
+tmax = 0.400
 decim = 2  # decimate to make the example faster to run
-epochs = mne.Epochs(raw, events, event_id, -0.050, 0.400, proj=True,
-                    picks=picks, baseline=None, preload=True,
-                    reject=dict(mag=5e-12), decim=decim, verbose=False)
+epochs = mne.Epochs(raw, events, event_id=event_id, tmin=tmin, tmax=tmax,
+                    proj=True, picks=picks, baseline=None, preload=True,
+                    reject=dict(mag=5e-12), decim=decim)
 
 # We will train the classifier on all left visual vs auditory trials
 # and test on all right visual vs auditory trials.
+clf = make_pipeline(StandardScaler(), LogisticRegression())
+time_gen = GeneralizingEstimator(clf, scoring='roc_auc', n_jobs=1)
 
-# In this case, because the test data is independent from the train data,
-# we test the classifier of each fold and average the respective predictions.
-
-# Define events of interest
-triggers = epochs.events[:, 2]
-viz_vs_auditory = np.in1d(triggers, (1, 2)).astype(int)
-
-gat = GeneralizationAcrossTime(predict_mode='mean-prediction', n_jobs=1)
-
-# For our left events, which ones are visual?
-viz_vs_auditory_l = (triggers[np.in1d(triggers, (1, 3))] == 3).astype(int)
-# To make scikit-learn happy, we converted the bool array to integers
-# in the same line. This results in an array of zeros and ones:
-print("The unique classes' labels are: %s" % np.unique(viz_vs_auditory_l))
-
-gat.fit(epochs[('AudL', 'VisL')], y=viz_vs_auditory_l)
+# Fit classifiers on the epochs where the stimulus was presented to the left.
+# Note that the experimental condition y indicates auditory or visual
+time_gen.fit(X=epochs['Left'].get_data(),
+             y=epochs['Left'].events[:, 2] > 2)
 
-# For our right events, which ones are visual?
-viz_vs_auditory_r = (triggers[np.in1d(triggers, (2, 4))] == 4).astype(int)
+# Score on the epochs where the stimulus was presented to the right.
+scores = time_gen.score(X=epochs['Right'].get_data(),
+                        y=epochs['Right'].events[:, 2] > 2)
 
-gat.score(epochs[('AudR', 'VisR')], y=viz_vs_auditory_r)
-gat.plot(title="Temporal Generalization (visual vs auditory): left to right")
+# Plot
+fig, ax = plt.subplots(1)
+im = ax.matshow(scores, vmin=0, vmax=1., cmap='RdBu_r', origin='lower',
+                extent=epochs.times[[0, -1, 0, -1]])
+ax.axhline(0., color='k')
+ax.axvline(0., color='k')
+ax.xaxis.set_ticks_position('bottom')
+ax.set_xlabel('Testing Time (s)')
+ax.set_ylabel('Training Time (s)')
+ax.set_title('Generalization across time and condition')
+plt.colorbar(im, ax=ax)
+plt.show()
diff --git a/examples/decoding/plot_decoding_unsupervised_spatial_filter.py b/examples/decoding/plot_decoding_unsupervised_spatial_filter.py
index 3c9c71f..2946092 100644
--- a/examples/decoding/plot_decoding_unsupervised_spatial_filter.py
+++ b/examples/decoding/plot_decoding_unsupervised_spatial_filter.py
@@ -34,7 +34,7 @@ tmin, tmax = -0.1, 0.3
 event_id = dict(aud_l=1, aud_r=2, vis_l=3, vis_r=4)
 
 raw = mne.io.read_raw_fif(raw_fname, preload=True)
-raw.filter(1, 20)
+raw.filter(1, 20, fir_design='firwin')
 events = mne.read_events(event_fname)
 
 picks = mne.pick_types(raw.info, meg=False, eeg=True, stim=False, eog=False,
@@ -42,7 +42,7 @@ picks = mne.pick_types(raw.info, meg=False, eeg=True, stim=False, eog=False,
 
 epochs = mne.Epochs(raw, events, event_id, tmin, tmax, proj=False,
                     picks=picks, baseline=None, preload=True,
-                    add_eeg_ref=False, verbose=False)
+                    verbose=False)
 
 X = epochs.get_data()
 
diff --git a/examples/decoding/plot_decoding_xdawn_eeg.py b/examples/decoding/plot_decoding_xdawn_eeg.py
index 2cc864c..d43896f 100644
--- a/examples/decoding/plot_decoding_xdawn_eeg.py
+++ b/examples/decoding/plot_decoding_xdawn_eeg.py
@@ -3,20 +3,21 @@
  XDAWN Decoding From EEG data
 =============================
 
-ERP decoding with Xdawn. For each event type, a set of spatial Xdawn filters
-are trained and applied on the signal. Channels are concatenated and rescaled
-to create features vectors that will be fed into a Logistic Regression.
+ERP decoding with Xdawn ([1]_, [2]_). For each event type, a set of
+spatial Xdawn filters are trained and applied on the signal. Channels are
+concatenated and rescaled to create features vectors that will be fed into
+a logistic regression.
 
 References
 ----------
-[1] Rivet, B., Souloumiac, A., Attina, V., & Gibert, G. (2009). xDAWN
-algorithm to enhance evoked potentials: application to brain-computer
-interface. Biomedical Engineering, IEEE Transactions on, 56(8), 2035-2043.
-
-[2] Rivet, B., Cecotti, H., Souloumiac, A., Maby, E., & Mattout, J. (2011,
-August). Theoretical analysis of xDAWN algorithm: application to an
-efficient sensor selection in a P300 BCI. In Signal Processing Conference,
-2011 19th European (pp. 1382-1386). IEEE.
+.. [1] Rivet, B., Souloumiac, A., Attina, V., & Gibert, G. (2009). xDAWN
+       algorithm to enhance evoked potentials: application to brain-computer
+       interface. Biomedical Engineering, IEEE Transactions on, 56(8),
+       2035-2043.
+.. [2] Rivet, B., Cecotti, H., Souloumiac, A., Maby, E., & Mattout, J. (2011,
+       August). Theoretical analysis of xDAWN algorithm: application to an
+       efficient sensor selection in a P300 BCI. In Signal Processing
+       Conference, 2011 19th European (pp. 1382-1386). IEEE.
 """
 # Authors: Alexandre Barachant <alexandre.barachant at gmail.com>
 #
@@ -25,7 +26,7 @@ efficient sensor selection in a P300 BCI. In Signal Processing Conference,
 import numpy as np
 import matplotlib.pyplot as plt
 
-from sklearn.cross_validation import StratifiedKFold
+from sklearn.model_selection import StratifiedKFold
 from sklearn.pipeline import make_pipeline
 from sklearn.linear_model import LogisticRegression
 from sklearn.metrics import classification_report, confusion_matrix
@@ -51,7 +52,7 @@ event_id = dict(aud_l=1, aud_r=2, vis_l=3, vis_r=4)
 
 # Setup for reading the raw data
 raw = io.read_raw_fif(raw_fname, preload=True)
-raw.filter(1, 20, method='iir')
+raw.filter(1, 20, fir_design='firwin')
 events = read_events(event_fname)
 
 picks = pick_types(raw.info, meg=False, eeg=True, stim=False, eog=False,
@@ -59,7 +60,7 @@ picks = pick_types(raw.info, meg=False, eeg=True, stim=False, eog=False,
 
 epochs = Epochs(raw, events, event_id, tmin, tmax, proj=False,
                 picks=picks, baseline=None, preload=True,
-                add_eeg_ref=False, verbose=False)
+                verbose=False)
 
 # Create classification pipeline
 clf = make_pipeline(Xdawn(n_components=3),
@@ -71,11 +72,11 @@ clf = make_pipeline(Xdawn(n_components=3),
 labels = epochs.events[:, -1]
 
 # Cross validator
-cv = StratifiedKFold(y=labels, n_folds=10, shuffle=True, random_state=42)
+cv = StratifiedKFold(n_splits=10, shuffle=True, random_state=42)
 
 # Do cross-validation
 preds = np.empty(len(labels))
-for train, test in cv:
+for train, test in cv.split(epochs, labels):
     clf.fit(epochs[train], labels[train])
     preds[test] = clf.predict(epochs[test])
 
diff --git a/examples/decoding/plot_ems_filtering.py b/examples/decoding/plot_ems_filtering.py
index f449892..34674d6 100644
--- a/examples/decoding/plot_ems_filtering.py
+++ b/examples/decoding/plot_ems_filtering.py
@@ -3,24 +3,25 @@
 Compute effect-matched-spatial filtering (EMS)
 ==============================================
 
-This example computes the EMS to reconstruct the time course of
-the experimental effect as described in:
+This example computes the EMS to reconstruct the time course of the
+experimental effect as described in [1]_.
 
-Aaron Schurger, Sebastien Marti, and Stanislas Dehaene, "Reducing multi-sensor
-data to a single time course that reveals experimental effects",
-BMC Neuroscience 2013, 14:122
-
-
-This technique is used to create spatial filters based on the
-difference between two conditions. By projecting the trial onto the
-corresponding spatial filters, surrogate single trials are created
-in which multi-sensor activity is reduced to one time series which
-exposes experimental effects, if present.
+This technique is used to create spatial filters based on the difference
+between two conditions. By projecting the trial onto the corresponding spatial
+filters, surrogate single trials are created in which multi-sensor activity is
+reduced to one time series which exposes experimental effects, if present.
 
 We will first plot a trials x times image of the single trials and order the
 trials by condition. A second plot shows the average time series for each
-condition. Finally a topographic plot is created which exhibits the
-temporal evolution of the spatial filters.
+condition. Finally a topographic plot is created which exhibits the temporal
+evolution of the spatial filters.
+
+References
+----------
+
+.. [1] Aaron Schurger, Sebastien Marti, and Stanislas Dehaene, "Reducing
+       multi-sensor data to a single time course that reveals experimental
+       effects", BMC Neuroscience 2013, 14:122.
 """
 # Author: Denis Engemann <denis.engemann at gmail.com>
 #         Jean-Remi King <jeanremi.king at gmail.com>
@@ -34,7 +35,7 @@ import mne
 from mne import io, EvokedArray
 from mne.datasets import sample
 from mne.decoding import EMS, compute_ems
-from sklearn.cross_validation import StratifiedKFold
+from sklearn.model_selection import StratifiedKFold
 
 print(__doc__)
 
@@ -47,8 +48,7 @@ event_ids = {'AudL': 1, 'VisL': 3}
 
 # Read data and create epochs
 raw = io.read_raw_fif(raw_fname, preload=True)
-raw.filter(0.5, 45, l_trans_bandwidth='auto', h_trans_bandwidth='auto',
-           filter_length='auto', phase='zero')
+raw.filter(0.5, 45, fir_design='firwin')
 events = mne.read_events(event_fname)
 
 picks = mne.pick_types(raw.info, meg='grad', eeg=False, stim=False, eog=True,
@@ -79,7 +79,7 @@ filters = list()  # Spatial filters at each time point
 # to overfit and cannot be used to estimate the variance of the
 # prediction within a given fold.
 
-for train, test in StratifiedKFold(y):
+for train, test in StratifiedKFold().split(X, y):
     # In the original paper, the z-scoring is applied outside the CV.
     # However, we recommend to apply this preprocessing inside the CV.
     # Note that such scaling should be done separately for each channels if the
diff --git a/examples/decoding/plot_linear_model_patterns.py b/examples/decoding/plot_linear_model_patterns.py
index c4467d0..a6bcac6 100644
--- a/examples/decoding/plot_linear_model_patterns.py
+++ b/examples/decoding/plot_linear_model_patterns.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
 """
 ===============================================================
 Linear classifier on sensor data with plot patterns and filters
@@ -6,28 +7,34 @@ Linear classifier on sensor data with plot patterns and filters
 Decoding, a.k.a MVPA or supervised machine learning applied to MEG and EEG
 data in sensor space. Fit a linear classifier with the LinearModel object
 providing topographical patterns which are more neurophysiologically
-interpretable [1] than the classifier filters (weight vectors).
+interpretable [1]_ than the classifier filters (weight vectors).
 The patterns explain how the MEG and EEG data were generated from the
 discriminant neural sources which are extracted by the filters.
 Note patterns/filters in MEG data are more similar than EEG data
 because the noise is less spatially correlated in MEG than EEG.
 
-[1] Haufe, S., Meinecke, F., Görgen, K., Dähne, S., Haynes, J.-D.,
-Blankertz, B., & Bießmann, F. (2014). On the interpretation of
-weight vectors of linear models in multivariate neuroimaging.
-NeuroImage, 87, 96–110. doi:10.1016/j.neuroimage.2013.10.067
+References
+----------
+
+.. [1] Haufe, S., Meinecke, F., Görgen, K., Dähne, S., Haynes, J.-D.,
+       Blankertz, B., & Bießmann, F. (2014). On the interpretation of
+       weight vectors of linear models in multivariate neuroimaging.
+       NeuroImage, 87, 96–110. doi:10.1016/j.neuroimage.2013.10.067
 """
 # Authors: Alexandre Gramfort <alexandre.gramfort at telecom-paristech.fr>
 #          Romain Trachel <trachelr at gmail.com>
+#          Jean-Remi King <jeanremi.king at gmail.com>
 #
 # License: BSD (3-clause)
 
 import mne
-from mne import io
+from mne import io, EvokedArray
 from mne.datasets import sample
+from mne.decoding import Vectorizer, get_coef
 
 from sklearn.preprocessing import StandardScaler
 from sklearn.linear_model import LogisticRegression
+from sklearn.pipeline import make_pipeline
 
 # import a linear classifier from mne.decoding
 from mne.decoding import LinearModel
@@ -40,12 +47,12 @@ data_path = sample.data_path()
 # Set parameters
 raw_fname = data_path + '/MEG/sample/sample_audvis_filt-0-40_raw.fif'
 event_fname = data_path + '/MEG/sample/sample_audvis_filt-0-40_raw-eve.fif'
-tmin, tmax = -0.2, 0.5
+tmin, tmax = -0.1, 0.4
 event_id = dict(aud_l=1, vis_l=3)
 
 # Setup for reading the raw data
 raw = io.read_raw_fif(raw_fname, preload=True)
-raw.filter(2, None, method='iir')  # replace baselining with high-pass
+raw.filter(.5, 25, fir_design='firwin')
 events = mne.read_events(event_fname)
 
 # Read epochs
@@ -57,28 +64,51 @@ labels = epochs.events[:, -1]
 # get MEG and EEG data
 meg_epochs = epochs.copy().pick_types(meg=True, eeg=False)
 meg_data = meg_epochs.get_data().reshape(len(labels), -1)
-eeg_epochs = epochs.copy().pick_types(meg=False, eeg=True)
-eeg_data = eeg_epochs.get_data().reshape(len(labels), -1)
 
 ###############################################################################
 # Decoding in sensor space using a LogisticRegression classifier
 
 clf = LogisticRegression()
-sc = StandardScaler()
+scaler = StandardScaler()
 
 # create a linear model with LogisticRegression
 model = LinearModel(clf)
 
 # fit the classifier on MEG data
-X = sc.fit_transform(meg_data)
+X = scaler.fit_transform(meg_data)
 model.fit(X, labels)
-# plot patterns and filters
-model.plot_patterns(meg_epochs.info, title='MEG Patterns')
-model.plot_filters(meg_epochs.info, title='MEG Filters')
 
-# fit the classifier on EEG data
-X = sc.fit_transform(eeg_data)
-model.fit(X, labels)
-# plot patterns and filters
-model.plot_patterns(eeg_epochs.info, title='EEG Patterns')
-model.plot_filters(eeg_epochs.info, title='EEG Filters')
+# Extract and plot spatial filters and spatial patterns
+for name, coef in (('patterns', model.patterns_), ('filters', model.filters_)):
+    # We fitted the linear model onto Z-scored data. To make the filters
+    # interpretable, we must reverse this normalization step
+    coef = scaler.inverse_transform([coef])[0]
+
+    # The data was vectorized to fit a single model across all time points and
+    # all channels. We thus reshape it:
+    coef = coef.reshape(len(meg_epochs.ch_names), -1)
+
+    # Plot
+    evoked = EvokedArray(coef, meg_epochs.info, tmin=epochs.tmin)
+    evoked.plot_topomap(title='MEG %s' % name)
+
+###############################################################################
+# Let's do the same on EEG data using a scikit-learn pipeline
+
+X = epochs.pick_types(meg=False, eeg=True)
+y = epochs.events[:, 2]
+
+# Define a unique pipeline to sequentially:
+clf = make_pipeline(
+    Vectorizer(),                       # 1) vectorize across time and channels
+    StandardScaler(),                   # 2) normalize features across trials
+    LinearModel(LogisticRegression()))  # 3) fits a logistic regression
+clf.fit(X, y)
+
+# Extract and plot patterns and filters
+for name in ('patterns_', 'filters_'):
+    # The `inverse_transform` parameter will call this method on any estimator
+    # contained in the pipeline, in reverse order.
+    coef = get_coef(clf, name, inverse_transform=True)
+    evoked = EvokedArray(coef, epochs.info, tmin=epochs.tmin)
+    evoked.plot_topomap(title='EEG %s' % name[:-1])
diff --git a/examples/decoding/plot_receptive_field.py b/examples/decoding/plot_receptive_field.py
new file mode 100644
index 0000000..bc691c0
--- /dev/null
+++ b/examples/decoding/plot_receptive_field.py
@@ -0,0 +1,265 @@
+"""
+=========================================
+Receptive Field Estimation and Prediction
+=========================================
+
+This example reproduces figures from Lalor et al's mTRF toolbox in
+matlab [1]_. We will show how the :class:`mne.decoding.ReceptiveField` class
+can perform a similar function along with scikit-learn. We will first fit a
+linear encoding model using the continuously-varying speech envelope to predict
+activity of a 128 channel EEG system. Then, we will take the reverse approach
+and try to predict the speech envelope from the EEG (known in the litterature
+as a decoding model, or simply stimulus reconstruction).
+
+References
+----------
+.. [1] Crosse, M. J., Di Liberto, G. M., Bednar, A. & Lalor, E. C. (2016).
+       The Multivariate Temporal Response Function (mTRF) Toolbox:
+       A MATLAB Toolbox for Relating Neural Signals to Continuous Stimuli.
+       Frontiers in Human Neuroscience 10, 604. doi:10.3389/fnhum.2016.00604
+
+.. [2] Haufe, S., Meinecke, F., Goergen, K., Daehne, S., Haynes, J.-D.,
+       Blankertz, B., & Biessmann, F. (2014). On the interpretation of weight
+       vectors of linear models in multivariate neuroimaging. NeuroImage, 87,
+       96-110. doi:10.1016/j.neuroimage.2013.10.067
+
+.. _figure 1: http://journal.frontiersin.org/article/10.3389/fnhum.2016.00604/full#F1
+.. _figure 2: http://journal.frontiersin.org/article/10.3389/fnhum.2016.00604/full#F2
+.. _figure 5: http://journal.frontiersin.org/article/10.3389/fnhum.2016.00604/full#F5
+"""  # noqa: E501
+
+# Authors: Chris Holdgraf <choldgraf at gmail.com>
+#          Eric Larson <larson.eric.d at gmail.com>
+#          Nicolas Barascud <nicolas.barascud at ens.fr>
+#
+# License: BSD (3-clause)
+# sphinx_gallery_thumbnail_number = 3
+
+import numpy as np
+import matplotlib.pyplot as plt
+from scipy.io import loadmat
+from os.path import join
+
+import mne
+from mne.decoding import ReceptiveField
+from sklearn.model_selection import KFold
+from sklearn.preprocessing import scale
+
+
+###############################################################################
+# Load the data from the publication
+# ----------------------------------
+#
+# First we will load the data collected in [1]_. In this experiment subjects
+# listened to natural speech. Raw EEG and the speech stimulus are provided.
+# We will load these below, downsampling the data in order to speed up
+# computation since we know that our features are primarily low-frequency in
+# nature. Then we'll visualize both the EEG and speech envelope.
+
+path = mne.datasets.mtrf.data_path()
+decim = 2
+data = loadmat(join(path, 'speech_data.mat'))
+raw = data['EEG'].T
+speech = data['envelope'].T
+sfreq = float(data['Fs'])
+sfreq /= decim
+speech = mne.filter.resample(speech, down=decim, npad='auto')
+raw = mne.filter.resample(raw, down=decim, npad='auto')
+
+# Read in channel positions and create our MNE objects from the raw data
+montage = mne.channels.read_montage('biosemi128')
+montage.selection = montage.selection[:128]
+info = mne.create_info(montage.ch_names[:128], sfreq, 'eeg', montage=montage)
+raw = mne.io.RawArray(raw, info)
+n_channels = len(raw.ch_names)
+
+# Plot a sample of brain and stimulus activity
+fig, ax = plt.subplots()
+lns = ax.plot(scale(raw[:, :800][0].T), color='k', alpha=.1)
+ln1 = ax.plot(scale(speech[0, :800]), color='r', lw=2)
+ax.legend([lns[0], ln1[0]], ['EEG', 'Speech Envelope'], frameon=False)
+ax.set(title="Sample activity", xlabel="Time (s)")
+mne.viz.tight_layout()
+
+###############################################################################
+# Create and fit a receptive field model
+# --------------------------------------
+#
+# We will construct an encoding model to find the linear relationship between
+# a time-delayed version of the speech envelope and the EEG signal. This allows
+# us to make predictions about the response to new stimuli.
+
+# Define the delays that we will use in the receptive field
+tmin, tmax = -.2, .4
+
+# Initialize the model
+rf = ReceptiveField(tmin, tmax, sfreq, feature_names=['envelope'],
+                    estimator=1., scoring='corrcoef')
+# We'll have (tmax - tmin) * sfreq delays
+# and an extra 2 delays since we are inclusive on the beginning / end index
+n_delays = int((tmax - tmin) * sfreq) + 2
+
+n_splits = 3
+cv = KFold(n_splits)
+
+# Prepare model data (make time the first dimension)
+speech = speech.T
+Y, _ = raw[:]  # Outputs for the model
+Y = Y.T
+
+# Iterate through splits, fit the model, and predict/test on held-out data
+coefs = np.zeros((n_splits, n_channels, n_delays))
+scores = np.zeros((n_splits, n_channels))
+for ii, (train, test) in enumerate(cv.split(speech)):
+    print('split %s / %s' % (ii + 1, n_splits))
+    rf.fit(speech[train], Y[train])
+    scores[ii] = rf.score(speech[test], Y[test])
+    # coef_ is shape (n_outputs, n_features, n_delays). we only have 1 feature
+    coefs[ii] = rf.coef_[:, 0, :]
+times = rf.delays_ / float(rf.sfreq)
+
+# Average scores and coefficients across CV splits
+mean_coefs = coefs.mean(axis=0)
+mean_scores = scores.mean(axis=0)
+
+# Plot mean prediction scores across all channels
+fig, ax = plt.subplots()
+ix_chs = np.arange(n_channels)
+ax.plot(ix_chs, mean_scores)
+ax.axhline(0, ls='--', color='r')
+ax.set(title="Mean prediction score", xlabel="Channel", ylabel="Score ($r$)")
+mne.viz.tight_layout()
+
+###############################################################################
+# Investigate model coefficients
+# ==============================
+# Finally, we will look at how the linear coefficients (sometimes
+# referred to as beta values) are distributed across time delays as well as
+# across the scalp. We will recreate `figure 1`_ and `figure 2`_ from [1]_.
+
+# Print mean coefficients across all time delays / channels (see Fig 1 in [1])
+time_plot = 0.180  # For highlighting a specific time.
+fig, ax = plt.subplots(figsize=(4, 8))
+max_coef = mean_coefs.max()
+ax.pcolormesh(times, ix_chs, mean_coefs, cmap='RdBu_r',
+              vmin=-max_coef, vmax=max_coef, shading='gouraud')
+ax.axvline(time_plot, ls='--', color='k', lw=2)
+ax.set(xlabel='Delay (s)', ylabel='Channel', title="Mean Model\nCoefficients",
+       xlim=times[[0, -1]], ylim=[len(ix_chs) - 1, 0],
+       xticks=np.arange(tmin, tmax + .2, .2))
+plt.setp(ax.get_xticklabels(), rotation=45)
+mne.viz.tight_layout()
+
+# Make a topographic map of coefficients for a given delay (see Fig 2C in [1])
+ix_plot = np.argmin(np.abs(time_plot - times))
+fig, ax = plt.subplots()
+mne.viz.plot_topomap(mean_coefs[:, ix_plot], pos=info, axes=ax, show=False,
+                     vmin=-max_coef, vmax=max_coef)
+ax.set(title="Topomap of model coefficients\nfor delay %s" % time_plot)
+mne.viz.tight_layout()
+
+
+###############################################################################
+# Create and fit a stimulus reconstruction model
+# ----------------------------------------------
+#
+# We will now demonstrate another use case for the for the
+# :class:`mne.decoding.ReceptiveField` class as we try to predict the stimulus
+# activity from the EEG data. This is known in the literature as a decoding, or
+# stimulus reconstruction model [1]_. A decoding model aims to find the
+# relationship between the speech signal and a time-delayed version of the EEG.
+# This can be useful as we exploit all of the available neural data in a
+# multivariate context, compared to the encoding case which treats each M/EEG
+# channel as an independent feature. Therefore, decoding models might provide a
+# better quality of fit (at the expense of not controlling for stimulus
+# covariance), especially for low SNR stimuli such as speech.
+
+# We use the same lags as in [1]. Negative lags now index the relationship
+# between the neural response and the speech envelope earlier in time, whereas
+# positive lags would index how a unit change in the amplitude of the EEG would
+# affect later stimulus activity (obviously this should have an amplitude of
+# zero).
+tmin, tmax = -.2, 0.
+
+# Initialize the model. Here the features are the EEG data. We also specify
+# ``patterns=True`` to compute inverse-transformed coefficients during model
+# fitting (cf. next section). We'll use a ridge regression estimator with an
+# alpha value similar to [1].
+sr = ReceptiveField(tmin, tmax, sfreq, feature_names=raw.ch_names,
+                    estimator=1e4, scoring='corrcoef', patterns=True)
+# We'll have (tmax - tmin) * sfreq delays
+# and an extra 2 delays since we are inclusive on the beginning / end index
+n_delays = int((tmax - tmin) * sfreq) + 2
+
+n_splits = 3
+cv = KFold(n_splits)
+
+# Iterate through splits, fit the model, and predict/test on held-out data
+coefs = np.zeros((n_splits, n_channels, n_delays))
+patterns = coefs.copy()
+scores = np.zeros((n_splits,))
+for ii, (train, test) in enumerate(cv.split(speech)):
+    print('split %s / %s' % (ii + 1, n_splits))
+    sr.fit(Y[train], speech[train])
+    scores[ii] = sr.score(Y[test], speech[test])[0]
+    # coef_ is shape (n_outputs, n_features, n_delays). We have 128 features
+    coefs[ii] = sr.coef_[0, :, :]
+    patterns[ii] = sr.patterns_[0, :, :]
+times = sr.delays_ / float(sr.sfreq)
+
+# Average scores and coefficients across CV splits
+mean_coefs = coefs.mean(axis=0)
+mean_patterns = patterns.mean(axis=0)
+mean_scores = scores.mean(axis=0)
+max_coef = np.abs(mean_coefs).max()
+max_patterns = np.abs(mean_patterns).max()
+
+###############################################################################
+# Visualize stimulus reconstruction
+# =================================
+#
+# To get a sense of our model performance, we can plot the actual and predicted
+# stimulus envelopes side by side.
+
+y_pred = sr.predict(Y[test])
+time = np.linspace(0, 2., 5 * int(sfreq))
+fig, ax = plt.subplots(figsize=(8, 4))
+ax.plot(time, speech[test][sr.valid_samples_][:int(5 * sfreq)],
+        color='grey', lw=2, ls='--')
+ax.plot(time, y_pred[sr.valid_samples_][:int(5 * sfreq)], color='r', lw=2)
+ax.legend([lns[0], ln1[0]], ['Envelope', 'Reconstruction'], frameon=False)
+ax.set(title="Stimulus reconstruction")
+ax.set_xlabel('Time (s)')
+mne.viz.tight_layout()
+
+###############################################################################
+# Investigate model coefficients
+# ==============================
+#
+# Finally, we will look at how the decoding model coefficients are distributed
+# across the scalp. We will attempt to recreate `figure 5`_ from [1]_. The
+# decoding model weights reflect the channels that contribute most toward
+# reconstructing the stimulus signal, but are not directly interpretable in a
+# neurophysiological sense. Here we also look at the coefficients obtained
+# via an inversion procedure [2]_, which have a more straightforward
+# interpretation as their value (and sign) directly relates to the stimulus
+# signal's strength (and effect direction).
+
+time_plot = (-.140, -.125)  # To average between two timepoints.
+ix_plot = np.arange(np.argmin(np.abs(time_plot[0] - times)),
+                    np.argmin(np.abs(time_plot[1] - times)))
+fig, ax = plt.subplots(1, 2)
+mne.viz.plot_topomap(np.mean(mean_coefs[:, ix_plot], axis=1),
+                     pos=info, axes=ax[0], show=False,
+                     vmin=-max_coef, vmax=max_coef)
+ax[0].set(title="Model coefficients\nbetween delays %s and %s"
+          % (time_plot[0], time_plot[1]))
+
+mne.viz.plot_topomap(np.mean(mean_patterns[:, ix_plot], axis=1),
+                     pos=info, axes=ax[1],
+                     show=False, vmin=-max_patterns, vmax=max_patterns)
+ax[1].set(title="Inverse-transformed coefficients\nbetween delays %s and %s"
+          % (time_plot[0], time_plot[1]))
+mne.viz.tight_layout()
+
+plt.show()
diff --git a/examples/forward/plot_decimate_head_surface.py b/examples/forward/plot_decimate_head_surface.py
index 90a0cdf..793b06d 100644
--- a/examples/forward/plot_decimate_head_surface.py
+++ b/examples/forward/plot_decimate_head_surface.py
@@ -23,14 +23,14 @@ surf = mne.read_bem_surfaces(path + '/subjects/sample/bem/sample-head.fif')[0]
 
 points, triangles = surf['rr'], surf['tris']
 
-# # reduce to 30000 meshes equaling ${SUBJECT}-head-medium.fif output from
-# # mne_make_scalp_surfaces.py and mne_make_scalp_surfaces
-# points_dec, triangles_dec = decimate_surface(points, triangles,
-#                                              n_triangles=30000)
+# reduce to 30000 meshes equaling ${SUBJECT}-head-medium.fif output from
+# mne_make_scalp_surfaces.py and mne_make_scalp_surfaces
+points_dec, triangles_dec = decimate_surface(points, triangles,
+                                             n_triangles=30000)
 
-# from mayavi import mlab  # noqa
+from mayavi import mlab  # noqa
 
-# head_col = (0.95, 0.83, 0.83)  # light pink
+head_col = (0.95, 0.83, 0.83)  # light pink
 
-# p, t = points_dec, triangles_dec
-# mlab.triangular_mesh(p[:, 0], p[:, 1], p[:, 2], t, color=head_col)
+p, t = points_dec, triangles_dec
+mlab.triangular_mesh(p[:, 0], p[:, 1], p[:, 2], t, color=head_col)
diff --git a/examples/forward/plot_forward_sensitivity_maps.py b/examples/forward/plot_forward_sensitivity_maps.py
index 5a9991a..c3ccc51 100644
--- a/examples/forward/plot_forward_sensitivity_maps.py
+++ b/examples/forward/plot_forward_sensitivity_maps.py
@@ -7,7 +7,7 @@ Sensitivity maps can be produced from forward operators that
 indicate how well different sensor types will be able to detect
 neural currents from different regions of the brain.
 
-To get started with forward modeling see ref:`tut_forward`.
+To get started with forward modeling see :ref:`tut_forward`.
 
 """
 # Author: Eric Larson <larson.eric.d at gmail.com>
@@ -28,7 +28,8 @@ fwd_fname = data_path + '/MEG/sample/sample_audvis-meg-eeg-oct-6-fwd.fif'
 subjects_dir = data_path + '/subjects'
 
 # Read the forward solutions with surface orientation
-fwd = mne.read_forward_solution(fwd_fname, surf_ori=True)
+fwd = mne.read_forward_solution(fwd_fname)
+mne.convert_forward_solution(fwd, surf_ori=True, copy=False)
 leadfield = fwd['sol']['data']
 print("Leadfield size : %d x %d" % leadfield.shape)
 
diff --git a/examples/forward/plot_left_cerebellum_volume_source.py b/examples/forward/plot_left_cerebellum_volume_source.py
index 6a8b3c3..636e992 100644
--- a/examples/forward/plot_left_cerebellum_volume_source.py
+++ b/examples/forward/plot_left_cerebellum_volume_source.py
@@ -30,8 +30,7 @@ aseg_fname = subjects_dir + '/sample/mri/aseg.mgz'
 # Setup the source spaces
 
 # setup a cortical surface source space and extract left hemisphere
-surf = setup_source_space(subj, subjects_dir=subjects_dir,
-                          add_dist=False, overwrite=True)
+surf = setup_source_space(subj, subjects_dir=subjects_dir, add_dist=False)
 lh_surf = surf[0]
 
 # setup a volume source space of the left cerebellum cortex
diff --git a/examples/forward/plot_read_bem_surfaces.py b/examples/forward/plot_read_bem_surfaces.py
deleted file mode 100644
index 46852ac..0000000
--- a/examples/forward/plot_read_bem_surfaces.py
+++ /dev/null
@@ -1,39 +0,0 @@
-"""
-============================================
-Reading BEM surfaces from a forward solution
-============================================
-
-Plot BEM surfaces used for forward solution generation.
-"""
-# Author: Alexandre Gramfort <alexandre.gramfort at telecom-paristech.fr>
-#
-# License: BSD (3-clause)
-
-import mne
-from mne.datasets import sample
-
-print(__doc__)
-
-data_path = sample.data_path()
-fname = data_path + '/subjects/sample/bem/sample-5120-5120-5120-bem-sol.fif'
-
-surfaces = mne.read_bem_surfaces(fname, patch_stats=True)
-
-print("Number of surfaces : %d" % len(surfaces))
-
-###############################################################################
-# Show result
-head_col = (0.95, 0.83, 0.83)  # light pink
-skull_col = (0.91, 0.89, 0.67)
-brain_col = (0.67, 0.89, 0.91)  # light blue
-colors = [head_col, skull_col, brain_col]
-
-# 3D source space
-from mayavi import mlab  # noqa
-
-mlab.figure(size=(600, 600), bgcolor=(0, 0, 0))
-for c, surf in zip(colors, surfaces):
-    points = surf['rr']
-    faces = surf['tris']
-    mlab.triangular_mesh(points[:, 0], points[:, 1], points[:, 2], faces,
-                         color=c, opacity=0.3)
diff --git a/examples/forward/plot_source_space_morphing.py b/examples/forward/plot_source_space_morphing.py
index c07ac0d..c87e557 100644
--- a/examples/forward/plot_source_space_morphing.py
+++ b/examples/forward/plot_source_space_morphing.py
@@ -44,7 +44,6 @@ src_morph = mne.morph_source_spaces(src_fs, subject_to='sample',
 # Compute the forward with our morphed source space
 fwd = mne.make_forward_solution(info, trans=fname_trans,
                                 src=src_morph, bem=fname_bem)
-# fwd = mne.convert_forward_solution(fwd, surf_ori=True, force_fixed=True)
 mag_map = mne.sensitivity_map(fwd, ch_type='mag')
 
 # Return this SourceEstimate (on sample's surfaces) to fsaverage's surfaces
diff --git a/examples/inverse/plot_compute_mne_inverse_epochs_in_label.py b/examples/inverse/plot_compute_mne_inverse_epochs_in_label.py
index 9a0f3e0..0556a90 100644
--- a/examples/inverse/plot_compute_mne_inverse_epochs_in_label.py
+++ b/examples/inverse/plot_compute_mne_inverse_epochs_in_label.py
@@ -5,7 +5,6 @@ Compute MNE-dSPM inverse solution on single epochs
 
 Compute dSPM inverse solution on single trial epochs restricted
 to a brain label.
-
 """
 # Author: Alexandre Gramfort <alexandre.gramfort at telecom-paristech.fr>
 #
diff --git a/examples/inverse/plot_compute_mne_inverse_raw_in_label.py b/examples/inverse/plot_compute_mne_inverse_raw_in_label.py
index 550d29b..3da2203 100644
--- a/examples/inverse/plot_compute_mne_inverse_raw_in_label.py
+++ b/examples/inverse/plot_compute_mne_inverse_raw_in_label.py
@@ -35,7 +35,7 @@ raw = mne.io.read_raw_fif(fname_raw)
 inverse_operator = read_inverse_operator(fname_inv)
 label = mne.read_label(fname_label)
 
-raw.set_eeg_reference()  # set average reference.
+raw.set_eeg_reference('average', projection=True)  # set average reference.
 start, stop = raw.time_as_index([0, 15])  # read the first 15s of data
 
 # Compute inverse solution
diff --git a/examples/inverse/plot_covariance_whitening_dspm.py b/examples/inverse/plot_covariance_whitening_dspm.py
index 23a8be3..1ab73d6 100644
--- a/examples/inverse/plot_covariance_whitening_dspm.py
+++ b/examples/inverse/plot_covariance_whitening_dspm.py
@@ -57,7 +57,7 @@ raw = io.read_raw_ctf(raw_fname % 1)  # Take first run
 raw = raw.crop(0, 150.).load_data().resample(60, npad='auto')
 
 picks = mne.pick_types(raw.info, meg=True, exclude='bads')
-raw.filter(1, None, method='iir', n_jobs=1)
+raw.filter(1, None, n_jobs=1, fir_design='firwin')
 
 events = mne.find_events(raw, stim_channel='UPPT001')
 
@@ -69,11 +69,10 @@ reject = dict(mag=3e-12)
 # Make source space
 
 trans = data_path + '/MEG/spm/SPM_CTF_MEG_example_faces1_3D_raw-trans.fif'
-src = mne.setup_source_space('spm', fname=None, spacing='oct6',
-                             subjects_dir=subjects_dir, add_dist=False)
+src = mne.setup_source_space('spm', spacing='oct6', subjects_dir=subjects_dir,
+                             add_dist=False)
 bem = data_path + '/subjects/spm/bem/spm-5120-5120-5120-bem-sol.fif'
 forward = mne.make_forward_solution(raw.info, trans, src, bem)
-forward = mne.convert_forward_solution(forward, surf_ori=True)
 del src
 
 # inverse parameters
@@ -100,7 +99,7 @@ for n_train in samples_epochs:
                               for id_ in [event_ids[k] for k in conditions]])
     epochs_train = mne.Epochs(raw, events_, event_ids, tmin, tmax, picks=picks,
                               baseline=baseline, preload=True, reject=reject)
-    epochs_train.equalize_event_counts(event_ids, copy=False)
+    epochs_train.equalize_event_counts(event_ids)
     assert len(epochs_train) == 2 * n_train
 
     noise_covs = compute_covariance(
@@ -155,8 +154,8 @@ for ni, (n_train, axes) in enumerate(zip(samples_epochs, (axes1, axes2))):
                                             methods_ordered[ni],
                                             ['best', 'worst'],
                                             colors):
-        brain = stc.plot(subjects_dir=subjects_dir, hemi='both', clim=clim)
-        brain.set_time(175)
+        brain = stc.plot(subjects_dir=subjects_dir, hemi='both', clim=clim,
+                         initial_time=0.175)
 
         im = brain_to_mpl(brain)
         brain.close()
diff --git a/examples/inverse/plot_custom_inverse_solver.py b/examples/inverse/plot_custom_inverse_solver.py
new file mode 100644
index 0000000..66406f2
--- /dev/null
+++ b/examples/inverse/plot_custom_inverse_solver.py
@@ -0,0 +1,180 @@
+# -*- coding: utf-8 -*-
+"""
+================================================
+Source localization with a custom inverse solver
+================================================
+
+The objective of this example is to show how to plug a custom inverse solver
+in MNE in order to facilate empirical comparison with the methods MNE already
+implements (wMNE, dSPM, sLORETA, LCMV, (TF-)MxNE etc.).
+
+This script is educational and shall be used for methods
+evaluations and new developments. It is not meant to be an example
+of good practice to analyse your data.
+
+The example makes use of 2 functions ``apply_solver`` and ``solver``
+so changes can be limited to the ``solver`` function (which only takes three
+parameters: the whitened data, the gain matrix, and the number of orientations)
+in order to try out another inverse algorithm.
+"""
+
+import numpy as np
+from scipy import linalg
+import mne
+from mne.datasets import sample
+from mne.viz import plot_sparse_source_estimates
+
+
+data_path = sample.data_path()
+fwd_fname = data_path + '/MEG/sample/sample_audvis-meg-eeg-oct-6-fwd.fif'
+ave_fname = data_path + '/MEG/sample/sample_audvis-ave.fif'
+cov_fname = data_path + '/MEG/sample/sample_audvis-shrunk-cov.fif'
+subjects_dir = data_path + '/subjects'
+condition = 'Left Auditory'
+
+# Read noise covariance matrix
+noise_cov = mne.read_cov(cov_fname)
+# Handling average file
+evoked = mne.read_evokeds(ave_fname, condition=condition, baseline=(None, 0))
+evoked.crop(tmin=0.04, tmax=0.18)
+
+evoked = evoked.pick_types(eeg=False, meg=True)
+# Handling forward solution
+forward = mne.read_forward_solution(fwd_fname)
+
+
+###############################################################################
+# Auxiliary function to run the solver
+
+def apply_solver(solver, evoked, forward, noise_cov, loose=0.2, depth=0.8):
+    """Function to call a custom solver on evoked data
+
+    This function does all the necessary computation:
+
+    - to select the channels in the forward given the available ones in
+      the data
+    - to take into account the noise covariance and do the spatial whitening
+    - to apply loose orientation constraint as MNE solvers
+    - to apply a weigthing of the columns of the forward operator as in the
+      weighted Minimum Norm formulation in order to limit the problem
+      of depth bias.
+
+    Parameters
+    ----------
+    solver : callable
+        The solver takes 3 parameters: data M, gain matrix G, number of
+        dipoles orientations per location (1 or 3). A solver shall return
+        2 variables: X which contains the time series of the active dipoles
+        and an active set which is a boolean mask to specify what dipoles are
+        present in X.
+    evoked : instance of mne.Evoked
+        The evoked data
+    forward : instance of Forward
+        The forward solution.
+    noise_cov : instance of Covariance
+        The noise covariance.
+    loose : float in [0, 1] | 'auto'
+        Value that weights the source variances of the dipole components
+        that are parallel (tangential) to the cortical surface. If loose
+        is 0 then the solution is computed with fixed orientation.
+        If loose is 1, it corresponds to free orientations.
+        The default value ('auto') is set to 0.2 for surface-oriented source
+        space and set to 1.0 for volumic or discrete source space.
+    depth : None | float in [0, 1]
+        Depth weighting coefficients. If None, no depth weighting is performed.
+
+    Returns
+    -------
+    stc : instance of SourceEstimate
+        The source estimates.
+    """
+    # Import the necessary private functions
+    from mne.inverse_sparse.mxne_inverse import \
+        (_prepare_gain, _check_loose_forward, is_fixed_orient,
+         _reapply_source_weighting, _make_sparse_stc)
+
+    all_ch_names = evoked.ch_names
+
+    loose, forward = _check_loose_forward(loose, forward)
+
+    # put the forward solution in fixed orientation if it's not already
+    if loose == 0. and not is_fixed_orient(forward):
+        forward = mne.convert_forward_solution(
+            forward, surf_ori=True, force_fixed=True, copy=True, use_cps=True)
+
+    # Handle depth weighting and whitening (here is no weights)
+    gain, gain_info, whitener, source_weighting, mask = _prepare_gain(
+        forward, evoked.info, noise_cov, pca=False, depth=depth,
+        loose=loose, weights=None, weights_min=None)
+
+    # Select channels of interest
+    sel = [all_ch_names.index(name) for name in gain_info['ch_names']]
+    M = evoked.data[sel]
+
+    # Whiten data
+    M = np.dot(whitener, M)
+
+    n_orient = 1 if is_fixed_orient(forward) else 3
+    X, active_set = solver(M, gain, n_orient)
+    X = _reapply_source_weighting(X, source_weighting, active_set, n_orient)
+
+    stc = _make_sparse_stc(X, active_set, forward, tmin=evoked.times[0],
+                           tstep=1. / evoked.info['sfreq'])
+
+    return stc
+
+
+###############################################################################
+# Define your solver
+
+def solver(M, G, n_orient):
+    """Dummy solver
+
+    It just runs L2 penalized regression and keep the 10 strongest locations
+
+    Parameters
+    ----------
+    M : array, shape (n_channels, n_times)
+        The whitened data.
+    G : array, shape (n_channels, n_dipoles)
+        The gain matrix a.k.a. the forward operator. The number of locations
+        is n_dipoles / n_orient. n_orient will be 1 for a fixed orientation
+        constraint or 3 when using a free orientation model.
+    n_orient : int
+        Can be 1 or 3 depending if one works with fixed or free orientations.
+        If n_orient is 3, then ``G[:, 2::3]`` corresponds to the dipoles that
+        are normal to the cortex.
+
+    Returns
+    -------
+    X : array, (n_active_dipoles, n_times)
+        The time series of the dipoles in the active set.
+    active_set : array (n_dipoles)
+        Array of bool. Entry j is True if dipole j is in the active set.
+        We have ``X_full[active_set] == X`` where X_full is the full X matrix
+        such that ``M = G X_full``.
+    """
+    K = linalg.solve(np.dot(G, G.T) + 1e15 * np.eye(G.shape[0]), G).T
+    K /= np.linalg.norm(K, axis=1)[:, None]
+    X = np.dot(K, M)
+
+    indices = np.argsort(np.sum(X ** 2, axis=1))[-10:]
+    active_set = np.zeros(G.shape[1], dtype=bool)
+    for idx in indices:
+        idx -= idx % n_orient
+        active_set[idx:idx + n_orient] = True
+    X = X[active_set]
+    return X, active_set
+
+
+###############################################################################
+# Apply your custom solver
+
+# loose, depth = 0.2, 0.8  # corresponds to loose orientation
+loose, depth = 1., 0.  # corresponds to free orientation
+stc = apply_solver(solver, evoked, forward, noise_cov, loose, depth)
+
+###############################################################################
+# View in 2D and 3D ("glass" brain like 3D plot)
+plot_sparse_source_estimates(forward['src'], stc, bgcolor=(1, 1, 1),
+                             opacity=0.1)
diff --git a/examples/inverse/plot_dics_beamformer.py b/examples/inverse/plot_dics_beamformer.py
index c2631d7..d3c33f1 100644
--- a/examples/inverse/plot_dics_beamformer.py
+++ b/examples/inverse/plot_dics_beamformer.py
@@ -3,13 +3,14 @@
 Compute DICS beamfomer on evoked data
 =====================================
 
-Compute a Dynamic Imaging of Coherent Sources (DICS) beamformer from single
-trial activity in a time-frequency window to estimate source time courses based
-on evoked data.
-
-The original reference for DICS is:
-Gross et al. Dynamic imaging of coherent sources: Studying neural interactions
-in the human brain. PNAS (2001) vol. 98 (2) pp. 694-699
+Compute a Dynamic Imaging of Coherent Sources (DICS) [1]_ beamformer from
+single-trial activity in a time-frequency window to estimate source time
+courses based on evoked data.
+
+References
+----------
+.. [1] Gross et al. Dynamic imaging of coherent sources: Studying neural
+       interactions in the human brain. PNAS (2001) vol. 98 (2) pp. 694-699
 """
 # Author: Roman Goj <roman.goj at gmail.com>
 #
@@ -52,7 +53,7 @@ epochs = mne.Epochs(raw, events, event_id, tmin, tmax, proj=True,
 evoked = epochs.average()
 
 # Read forward operator
-forward = mne.read_forward_solution(fname_fwd, surf_ori=True)
+forward = mne.read_forward_solution(fname_fwd)
 
 # Computing the data and noise cross-spectral density matrices
 # The time-frequency window was chosen on the basis of spectrograms from
@@ -65,7 +66,7 @@ noise_csd = csd_epochs(epochs, mode='multitaper', tmin=-0.11, tmax=0.0,
 evoked = epochs.average()
 
 # Compute DICS spatial filter and estimate source time courses on evoked data
-stc = dics(evoked, forward, noise_csd, data_csd)
+stc = dics(evoked, forward, noise_csd, data_csd, reg=0.05)
 
 plt.figure()
 ts_show = -30  # show the 40 largest responses
diff --git a/examples/inverse/plot_dics_source_power.py b/examples/inverse/plot_dics_source_power.py
index 1a9ab0d..9578a7f 100644
--- a/examples/inverse/plot_dics_source_power.py
+++ b/examples/inverse/plot_dics_source_power.py
@@ -3,12 +3,14 @@
 Compute source power using DICS beamfomer
 =========================================
 
-Compute a Dynamic Imaging of Coherent Sources (DICS) filter from single trial
-activity to estimate source power for two frequencies of interest.
+Compute a Dynamic Imaging of Coherent Sources (DICS) [1]_ filter from
+single-trial activity to estimate source power for two frequencies of
+interest.
 
-The original reference for DICS is:
-Gross et al. Dynamic imaging of coherent sources: Studying neural interactions
-in the human brain. PNAS (2001) vol. 98 (2) pp. 694-699
+References
+----------
+.. [1] Gross et al. Dynamic imaging of coherent sources: Studying neural
+       interactions in the human brain. PNAS (2001) vol. 98 (2) pp. 694-699
 """
 # Author: Roman Goj <roman.goj at gmail.com>
 #         Denis Engemann <denis.engemann at gmail.com>
@@ -46,7 +48,7 @@ epochs = mne.Epochs(raw, events, event_id, tmin, tmax, proj=True,
 evoked = epochs.average()
 
 # Read forward operator
-forward = mne.read_forward_solution(fname_fwd, surf_ori=True)
+forward = mne.read_forward_solution(fname_fwd)
 
 # Computing the data and noise cross-spectral density matrices
 # The time-frequency window was chosen on the basis of spectrograms from
@@ -61,12 +63,11 @@ noise_csds = csd_epochs(epochs, mode='multitaper', tmin=-0.11,
 # Compute DICS spatial filter and estimate source power
 stc = dics_source_power(epochs.info, forward, noise_csds, data_csds)
 
-clim = dict(kind='value', lims=[1.6, 1.9, 2.2])
 for i, csd in enumerate(data_csds):
-    message = 'DICS source power at %0.1f Hz' % csd.frequencies[0]
+    message = 'DICS source power at %0.1f Hz' % csd.freqs[0]
     brain = stc.plot(surface='inflated', hemi='rh', subjects_dir=subjects_dir,
-                     time_label=message, figure=i, clim=clim)
+                     time_label=message, figure=i)
     brain.set_data_time_index(i)
     brain.show_view('lateral')
     # Uncomment line below to save images
-    # brain.save_image('DICS_source_power_freq_%d.png' % csd.frequencies[0])
+    # brain.save_image('DICS_source_power_freq_%d.png' % csd.freqs[0])
diff --git a/examples/inverse/plot_gamma_map_inverse.py b/examples/inverse/plot_gamma_map_inverse.py
index 15b0e48..32fa8ac 100644
--- a/examples/inverse/plot_gamma_map_inverse.py
+++ b/examples/inverse/plot_gamma_map_inverse.py
@@ -3,10 +3,17 @@
 Compute a sparse inverse solution using the Gamma-Map empirical Bayesian method
 ===============================================================================
 
-See Wipf et al. "A unified Bayesian framework for MEG/EEG source imaging."
-NeuroImage, vol. 44, no. 3, pp. 947?66, Mar. 2009.
+See [1]_ for details.
+
+References
+----------
+.. [1] D. Wipf, S. Nagarajan
+   "A unified Bayesian framework for MEG/EEG source imaging",
+   Neuroimage, Volume 44, Number 3, pp. 947-966, Feb. 2009.
+   DOI: 10.1016/j.neuroimage.2008.02.059
 """
 # Author: Martin Luessi <mluessi at nmr.mgh.harvard.edu>
+#         Daniel Strohmeier <daniel.strohmeier at tu-ilmenau.de>
 #
 # License: BSD (3-clause)
 
@@ -14,8 +21,9 @@ import numpy as np
 
 import mne
 from mne.datasets import sample
-from mne.inverse_sparse import gamma_map
-from mne.viz import plot_sparse_source_estimates
+from mne.inverse_sparse import gamma_map, make_stc_from_dipoles
+from mne.viz import (plot_sparse_source_estimates,
+                     plot_dipole_locations, plot_dipole_amplitudes)
 
 print(__doc__)
 
@@ -32,29 +40,35 @@ evoked = mne.read_evokeds(evoked_fname, condition=condition,
 evoked.crop(tmin=-50e-3, tmax=300e-3)
 
 # Read the forward solution
-forward = mne.read_forward_solution(fwd_fname, surf_ori=True,
-                                    force_fixed=False)
+forward = mne.read_forward_solution(fwd_fname)
 
 # Read noise noise covariance matrix and regularize it
 cov = mne.read_cov(cov_fname)
 cov = mne.cov.regularize(cov, evoked.info)
 
-# Run the Gamma-MAP method
+# Run the Gamma-MAP method with dipole output
 alpha = 0.5
-stc, residual = gamma_map(evoked, forward, cov, alpha, xyz_same_gamma=True,
-                          return_residual=True)
+dipoles, residual = gamma_map(
+    evoked, forward, cov, alpha, xyz_same_gamma=True, return_residual=True,
+    return_as_dipoles=True)
 
-# View in 2D and 3D ("glass" brain like 3D plot)
+###############################################################################
+# Plot dipole activations
+plot_dipole_amplitudes(dipoles)
 
-# Show the sources as spheres scaled by their strength
-scale_factors = np.max(np.abs(stc.data), axis=1)
-scale_factors = 0.5 * (1 + scale_factors / np.max(scale_factors))
+# Plot dipole location of the strongest dipole with MRI slices
+idx = np.argmax([np.max(np.abs(dip.amplitude)) for dip in dipoles])
+plot_dipole_locations(dipoles[idx], forward['mri_head_t'], 'sample',
+                      subjects_dir=subjects_dir, mode='orthoview',
+                      idx='amplitude')
 
-plot_sparse_source_estimates(
-    forward['src'], stc, bgcolor=(1, 1, 1),
-    modes=['sphere'], opacity=0.1, scale_factors=(scale_factors, None),
-    fig_name="Gamma-MAP")
+# # Plot dipole locations of all dipoles with MRI slices
+# for dip in dipoles:
+#     plot_dipole_locations(dip, forward['mri_head_t'], 'sample',
+#                           subjects_dir=subjects_dir, mode='orthoview',
+#                           idx='amplitude')
 
+###############################################################################
 # Show the evoked response and the residual for gradiometers
 ylim = dict(grad=[-120, 120])
 evoked.pick_types(meg='grad', exclude='bads')
@@ -64,3 +78,18 @@ evoked.plot(titles=dict(grad='Evoked Response Gradiometers'), ylim=ylim,
 residual.pick_types(meg='grad', exclude='bads')
 residual.plot(titles=dict(grad='Residuals Gradiometers'), ylim=ylim,
               proj=True)
+
+###############################################################################
+# Generate stc from dipoles
+stc = make_stc_from_dipoles(dipoles, forward['src'])
+
+###############################################################################
+# View in 2D and 3D ("glass" brain like 3D plot)
+# Show the sources as spheres scaled by their strength
+scale_factors = np.max(np.abs(stc.data), axis=1)
+scale_factors = 0.5 * (1 + scale_factors / np.max(scale_factors))
+
+plot_sparse_source_estimates(
+    forward['src'], stc, bgcolor=(1, 1, 1),
+    modes=['sphere'], opacity=0.1, scale_factors=(scale_factors, None),
+    fig_name="Gamma-MAP")
diff --git a/examples/inverse/plot_label_from_stc.py b/examples/inverse/plot_label_from_stc.py
index 7ecd1ec..1283ae7 100644
--- a/examples/inverse/plot_label_from_stc.py
+++ b/examples/inverse/plot_label_from_stc.py
@@ -9,7 +9,6 @@ Here we compare the average time course in the anatomical label obtained
 by FreeSurfer segmentation and the average time course from the
 functional label. As expected the time course in the functional
 label yields higher values.
-
 """
 # Author: Luke Bloy <luke.bloy at gmail.com>
 #         Alex Gramfort <alexandre.gramfort at telecom-paristech.fr>
diff --git a/examples/inverse/plot_label_source_activations.py b/examples/inverse/plot_label_source_activations.py
index 4acc5ad..ffb9216 100644
--- a/examples/inverse/plot_label_source_activations.py
+++ b/examples/inverse/plot_label_source_activations.py
@@ -3,11 +3,10 @@
 Extracting the time series of activations in a label
 ====================================================
 
-We first apply a dSPM inverse operator to get signed activations
-in a label (with positive and negative values) and we then
-compare different strategies to average the times series
-in a label. We compare a simple average, with an averaging
-using the dipoles normal (flip mode) and then a PCA,
+We first apply a dSPM inverse operator to get signed activations in a label
+(with positive and negative values) and we then compare different strategies
+to average the times series in a label. We compare a simple average, with an
+averaging using the dipoles normal (flip mode) and then a PCA,
 also using a sign flip.
 """
 # Author: Alexandre Gramfort <alexandre.gramfort at telecom-paristech.fr>
diff --git a/examples/inverse/plot_lcmv_beamformer.py b/examples/inverse/plot_lcmv_beamformer.py
index 42af4e1..23bc7ac 100644
--- a/examples/inverse/plot_lcmv_beamformer.py
+++ b/examples/inverse/plot_lcmv_beamformer.py
@@ -3,9 +3,9 @@
 Compute LCMV beamformer on evoked data
 ======================================
 
-Compute LCMV beamformer solutions on evoked dataset for three different choices
-of source orientation and stores the solutions in stc files for visualisation.
-
+Compute LCMV beamformer solutions on an evoked dataset for three different
+choices of source orientation and store the solutions in stc files for
+visualisation.
 """
 # Author: Alexandre Gramfort <alexandre.gramfort at telecom-paristech.fr>
 #
@@ -33,14 +33,13 @@ subjects_dir = data_path + '/subjects'
 event_id, tmin, tmax = 1, -0.2, 0.5
 
 # Setup for reading the raw data
-raw = mne.io.read_raw_fif(raw_fname, preload=True, proj=True)
+raw = mne.io.read_raw_fif(raw_fname, preload=True)
 raw.info['bads'] = ['MEG 2443', 'EEG 053']  # 2 bads channels
 events = mne.read_events(event_fname)
 
 # Set up pick list: EEG + MEG - bad channels (modify to your needs)
-left_temporal_channels = mne.read_selection('Left-temporal')
 picks = mne.pick_types(raw.info, meg=True, eeg=False, stim=True, eog=True,
-                       exclude='bads', selection=left_temporal_channels)
+                       exclude='bads')
 
 # Pick the channels of interest
 raw.pick_channels([raw.ch_names[pick] for pick in picks])
@@ -48,13 +47,13 @@ raw.pick_channels([raw.ch_names[pick] for pick in picks])
 raw.info.normalize_proj()
 
 # Read epochs
-proj = False  # already applied
 epochs = mne.Epochs(raw, events, event_id, tmin, tmax,
-                    baseline=(None, 0), preload=True, proj=proj,
+                    baseline=(None, 0), preload=True, proj=True,
                     reject=dict(grad=4000e-13, mag=4e-12, eog=150e-6))
 evoked = epochs.average()
 
-forward = mne.read_forward_solution(fname_fwd, surf_ori=True)
+forward = mne.read_forward_solution(fname_fwd)
+forward = mne.convert_forward_solution(forward, surf_ori=True)
 
 # Compute regularized noise and data covariances
 noise_cov = mne.compute_covariance(epochs, tmin=tmin, tmax=0, method='shrunk')
@@ -65,27 +64,34 @@ plt.close('all')
 
 pick_oris = [None, 'normal', 'max-power']
 names = ['free', 'normal', 'max-power']
-descriptions = ['Free orientation', 'Normal orientation', 'Max-power '
-                'orientation']
+descriptions = ['Free orientation, voxel: %i', 'Normal orientation, voxel: %i',
+                'Max-power orientation, voxel: %i']
 colors = ['b', 'k', 'r']
 
 for pick_ori, name, desc, color in zip(pick_oris, names, descriptions, colors):
-    stc = lcmv(evoked, forward, noise_cov, data_cov, reg=0.01,
-               pick_ori=pick_ori)
-
-    # View activation time-series
-    label = mne.read_label(fname_label)
-    stc_label = stc.in_label(label)
-    plt.plot(1e3 * stc_label.times, np.mean(stc_label.data, axis=0), color,
-             hold=True, label=desc)
+    # compute unit-noise-gain beamformer with whitening of the leadfield and
+    # data (enabled by passing a noise covariance matrix)
+    stc = lcmv(evoked, forward, noise_cov, data_cov, reg=0.05,
+               pick_ori=pick_ori, weight_norm='unit-noise-gain',
+               max_ori_out='signed')
+
+    # View activation time-series in maximum voxel at 100 ms:
+    time_idx = stc.time_as_index(0.1)
+    max_vox = np.argmax(stc.data[:, time_idx])
+    plt.plot(stc.times, stc.data[max_vox, :], color, hold=True,
+             label=desc % max_vox)
 
 plt.xlabel('Time (ms)')
 plt.ylabel('LCMV value')
 plt.ylim(-0.8, 2.2)
-plt.title('LCMV in %s' % label_name)
+plt.title('LCMV in maximum voxel')
 plt.legend()
 plt.show()
 
+
+# take absolute value for plotting
+stc.data[:, :] = np.abs(stc.data)
+
 # Plot last stc in the brain in 3D with PySurfer if available
 brain = stc.plot(hemi='lh', subjects_dir=subjects_dir,
                  initial_time=0.1, time_unit='s')
diff --git a/examples/inverse/plot_lcmv_beamformer_volume.py b/examples/inverse/plot_lcmv_beamformer_volume.py
index dc52e67..dfd597e 100644
--- a/examples/inverse/plot_lcmv_beamformer_volume.py
+++ b/examples/inverse/plot_lcmv_beamformer_volume.py
@@ -4,9 +4,8 @@ Compute LCMV inverse solution on evoked data in volume source space
 ===================================================================
 
 Compute LCMV inverse solution on an auditory evoked dataset in a volume source
-space. It stores the solution in a nifti file for visualisation e.g. with
+space. It stores the solution in a nifti file for visualisation, e.g. with
 Freeview.
-
 """
 # Author: Alexandre Gramfort <alexandre.gramfort at telecom-paristech.fr>
 #
@@ -17,7 +16,7 @@ import matplotlib.pyplot as plt
 
 import mne
 from mne.datasets import sample
-from mne.beamformer import lcmv
+from mne.beamformer import make_lcmv, apply_lcmv
 
 from nilearn.plotting import plot_stat_map
 from nilearn.image import index_img
@@ -34,7 +33,7 @@ fname_fwd = data_path + '/MEG/sample/sample_audvis-meg-vol-7-fwd.fif'
 event_id, tmin, tmax = 1, -0.2, 0.5
 
 # Setup for reading the raw data
-raw = mne.io.read_raw_fif(raw_fname, preload=True, proj=True)
+raw = mne.io.read_raw_fif(raw_fname, preload=True)
 raw.info['bads'] = ['MEG 2443', 'EEG 053']  # 2 bads channels
 events = mne.read_events(event_fname)
 
@@ -62,10 +61,26 @@ noise_cov = mne.compute_covariance(epochs, tmin=tmin, tmax=0, method='shrunk')
 data_cov = mne.compute_covariance(epochs, tmin=0.04, tmax=0.15,
                                   method='shrunk')
 
-# Run free orientation (vector) beamformer. Source orientation can be
-# restricted by setting pick_ori to 'max-power' (or 'normal' but only when
-# using a surface-based source space)
-stc = lcmv(evoked, forward, noise_cov, data_cov, reg=0.01, pick_ori=None)
+# Compute weights of free orientation (vector) beamformer with weight
+# normalization (neural activity index, NAI). Providing a noise covariance
+# matrix enables whitening of the data and forward solution. Source orientation
+# is optimized by setting pick_ori to 'max-power'.
+# weight_norm can also be set to 'unit-noise-gain'. Source orientation can also
+# be 'normal' (but only when using a surface-based source space) or None,
+# which computes a vector beamfomer. Note, however, that not all combinations
+# of orientation selection and weight normalization are implemented yet.
+filters = make_lcmv(evoked.info, forward, data_cov, reg=0.05,
+                    noise_cov=noise_cov, pick_ori='max-power',
+                    weight_norm='nai')
+
+# Apply this spatial filter to the evoked data. The output of these two steps
+# is equivalent to calling lcmv() and enables the application of the spatial
+# filter to separate data sets, e.g. when using a common spatial filter to
+# compare conditions.
+stc = apply_lcmv(evoked, filters, max_ori_out='signed')
+
+# take absolute values for plotting
+stc.data[:, :] = np.abs(stc.data)
 
 # Save result in stc files
 stc.save('lcmv-vol')
@@ -79,7 +94,7 @@ img = mne.save_stc_as_volume('lcmv_inverse.nii.gz', stc,
 t1_fname = data_path + '/subjects/sample/mri/T1.mgz'
 
 # Plotting with nilearn ######################################################
-plot_stat_map(index_img(img, 61), t1_fname, threshold=0.8,
+plot_stat_map(index_img(img, 61), t1_fname, threshold=1.35,
               title='LCMV (t=%.1f s.)' % stc.times[61])
 
 # plot source time courses with the maximum peak amplitudes
diff --git a/examples/inverse/plot_mixed_norm_inverse.py b/examples/inverse/plot_mixed_norm_inverse.py
index ebafd51..de8cb0a 100644
--- a/examples/inverse/plot_mixed_norm_inverse.py
+++ b/examples/inverse/plot_mixed_norm_inverse.py
@@ -3,32 +3,36 @@
 Compute sparse inverse solution with mixed norm: MxNE and irMxNE
 ================================================================
 
-Runs (ir)MxNE (L1/L2 or L0.5/L2 mixed norm) inverse solver.
+Runs an (ir)MxNE (L1/L2 [1]_ or L0.5/L2 [2]_ mixed norm) inverse solver.
 L0.5/L2 is done with irMxNE which allows for sparser
 source estimates with less amplitude bias due to the non-convexity
 of the L0.5/L2 mixed norm penalty.
 
-See
-Gramfort A., Kowalski M. and Hamalainen, M,
-Mixed-norm estimates for the M/EEG inverse problem using accelerated
-gradient methods, Physics in Medicine and Biology, 2012
-http://dx.doi.org/10.1088/0031-9155/57/7/1937
-
-Strohmeier D., Haueisen J., and Gramfort A.:
-Improved MEG/EEG source localization with reweighted mixed-norms,
-4th International Workshop on Pattern Recognition in Neuroimaging,
-Tuebingen, 2014
-DOI: 10.1109/PRNI.2014.6858545
+References
+----------
+.. [1] Gramfort A., Kowalski M. and Hamalainen, M.
+   "Mixed-norm estimates for the M/EEG inverse problem using accelerated
+   gradient methods", Physics in Medicine and Biology, 2012.
+   http://dx.doi.org/10.1088/0031-9155/57/7/1937.
+
+.. [2] Strohmeier D., Haueisen J., and Gramfort A.
+   "Improved MEG/EEG source localization with reweighted mixed-norms",
+   4th International Workshop on Pattern Recognition in Neuroimaging,
+   Tuebingen, 2014. 10.1109/PRNI.2014.6858545
 """
 # Author: Alexandre Gramfort <alexandre.gramfort at telecom-paristech.fr>
+#         Daniel Strohmeier <daniel.strohmeier at tu-ilmenau.de>
 #
 # License: BSD (3-clause)
 
+import numpy as np
+
 import mne
 from mne.datasets import sample
-from mne.inverse_sparse import mixed_norm
+from mne.inverse_sparse import mixed_norm, make_stc_from_dipoles
 from mne.minimum_norm import make_inverse_operator, apply_inverse
-from mne.viz import plot_sparse_source_estimates
+from mne.viz import (plot_sparse_source_estimates,
+                     plot_dipole_locations, plot_dipole_amplitudes)
 
 print(__doc__)
 
@@ -45,43 +49,70 @@ condition = 'Left Auditory'
 evoked = mne.read_evokeds(ave_fname, condition=condition, baseline=(None, 0))
 evoked.crop(tmin=0, tmax=0.3)
 # Handling forward solution
-forward = mne.read_forward_solution(fwd_fname, surf_ori=True)
-
-ylim = dict(eeg=[-10, 10], grad=[-400, 400], mag=[-600, 600])
-evoked.plot(ylim=ylim, proj=True)
+forward = mne.read_forward_solution(fwd_fname)
 
 ###############################################################################
 # Run solver
-alpha = 50  # regularization parameter between 0 and 100 (100 is high)
+alpha = 55  # regularization parameter between 0 and 100 (100 is high)
 loose, depth = 0.2, 0.9  # loose orientation & depth weighting
 n_mxne_iter = 10  # if > 1 use L0.5/L2 reweighted mixed norm solver
 # if n_mxne_iter > 1 dSPM weighting can be avoided.
 
 # Compute dSPM solution to be used as weights in MxNE
 inverse_operator = make_inverse_operator(evoked.info, forward, cov,
-                                         loose=None, depth=depth, fixed=True)
+                                         depth=depth, fixed=True,
+                                         use_cps=True)
 stc_dspm = apply_inverse(evoked, inverse_operator, lambda2=1. / 9.,
                          method='dSPM')
 
-# Compute (ir)MxNE inverse solution
-stc, residual = mixed_norm(evoked, forward, cov, alpha, loose=loose,
-                           depth=depth, maxit=3000, tol=1e-4,
-                           active_set_size=10, debias=True, weights=stc_dspm,
-                           weights_min=8., n_mxne_iter=n_mxne_iter,
-                           return_residual=True)
+# Compute (ir)MxNE inverse solution with dipole output
+dipoles, residual = mixed_norm(
+    evoked, forward, cov, alpha, loose=loose, depth=depth, maxit=3000,
+    tol=1e-4, active_set_size=10, debias=True, weights=stc_dspm,
+    weights_min=8., n_mxne_iter=n_mxne_iter, return_residual=True,
+    return_as_dipoles=True)
+
+###############################################################################
+# Plot dipole activations
+plot_dipole_amplitudes(dipoles)
+
+# Plot dipole location of the strongest dipole with MRI slices
+idx = np.argmax([np.max(np.abs(dip.amplitude)) for dip in dipoles])
+plot_dipole_locations(dipoles[idx], forward['mri_head_t'], 'sample',
+                      subjects_dir=subjects_dir, mode='orthoview',
+                      idx='amplitude')
+
+# # Plot dipole locations of all dipoles with MRI slices
+# for dip in dipoles:
+#     plot_dipole_locations(dip, forward['mri_head_t'], 'sample',
+#                           subjects_dir=subjects_dir, mode='orthoview',
+#                           idx='amplitude')
+
+###############################################################################
+# Plot residual
+ylim = dict(eeg=[-10, 10], grad=[-400, 400], mag=[-600, 600])
+evoked.pick_types(meg=True, eeg=True, exclude='bads')
+evoked.plot(ylim=ylim, proj=True)
+residual.pick_types(meg=True, eeg=True, exclude='bads')
 residual.plot(ylim=ylim, proj=True)
 
 ###############################################################################
+# Generate stc from dipoles
+stc = make_stc_from_dipoles(dipoles, forward['src'])
+
+###############################################################################
 # View in 2D and 3D ("glass" brain like 3D plot)
 plot_sparse_source_estimates(forward['src'], stc, bgcolor=(1, 1, 1),
                              fig_name="MxNE (cond %s)" % condition,
                              opacity=0.1)
 
-# and on the fsaverage brain after morphing
+###############################################################################
+# Morph onto fsaverage brain and view
 stc_fsaverage = stc.morph(subject_from='sample', subject_to='fsaverage',
                           grade=None, sparse=True, subjects_dir=subjects_dir)
 src_fsaverage_fname = subjects_dir + '/fsaverage/bem/fsaverage-ico-5-src.fif'
 src_fsaverage = mne.read_source_spaces(src_fsaverage_fname)
 
 plot_sparse_source_estimates(src_fsaverage, stc_fsaverage, bgcolor=(1, 1, 1),
+                             fig_name="Morphed MxNE (cond %s)" % condition,
                              opacity=0.1)
diff --git a/examples/inverse/plot_mixed_source_space_inverse.py b/examples/inverse/plot_mixed_source_space_inverse.py
new file mode 100644
index 0000000..af6ce75
--- /dev/null
+++ b/examples/inverse/plot_mixed_source_space_inverse.py
@@ -0,0 +1,148 @@
+"""
+=======================================================================
+Compute MNE inverse solution on evoked data in a mixed source space
+=======================================================================
+
+Create a mixed source space and compute MNE inverse solution on evoked dataset.
+"""
+# Author: Annalisa Pascarella <a.pascarella at iac.cnr.it>
+#
+# License: BSD (3-clause)
+
+import os.path as op
+import matplotlib.pyplot as plt
+import mne
+
+from mne.datasets import sample
+from mne import setup_volume_source_space
+from mne import make_forward_solution
+from mne.minimum_norm import make_inverse_operator, apply_inverse
+
+from nilearn import plotting
+
+# Set dir
+data_path = sample.data_path()
+subject = 'sample'
+data_dir = op.join(data_path, 'MEG', subject)
+subjects_dir = op.join(data_path, 'subjects')
+bem_dir = op.join(subjects_dir, subject, 'bem')
+
+# Set file names
+fname_mixed_src = op.join(bem_dir, '%s-oct-6-mixed-src.fif' % subject)
+fname_aseg = op.join(subjects_dir, subject, 'mri', 'aseg.mgz')
+
+fname_model = op.join(bem_dir, '%s-5120-bem.fif' % subject)
+fname_bem = op.join(bem_dir, '%s-5120-bem-sol.fif' % subject)
+
+fname_evoked = data_dir + '/sample_audvis-ave.fif'
+fname_trans = data_dir + '/sample_audvis_raw-trans.fif'
+fname_fwd = data_dir + '/sample_audvis-meg-oct-6-mixed-fwd.fif'
+fname_cov = data_dir + '/sample_audvis-shrunk-cov.fif'
+
+###############################################################################
+# Set up our source space.
+
+# List substructures we are interested in. We select only the
+# sub structures we want to include in the source space
+labels_vol = ['Left-Amygdala',
+              'Left-Thalamus-Proper',
+              'Left-Cerebellum-Cortex',
+              'Brain-Stem',
+              'Right-Amygdala',
+              'Right-Thalamus-Proper',
+              'Right-Cerebellum-Cortex']
+
+# Get a surface-based source space. We could set one up like this::
+#
+#     >>> src = setup_source_space(subject, fname=None, spacing='oct6',
+#                                  add_dist=False, subjects_dir=subjects_dir)
+#
+# But we already have one saved:
+
+src = mne.read_source_spaces(op.join(bem_dir, 'sample-oct-6-src.fif'))
+
+# Now we create a mixed src space by adding the volume regions specified in the
+# list labels_vol. First, read the aseg file and the source space bounds
+# using the inner skull surface (here using 10mm spacing to save time):
+
+vol_src = setup_volume_source_space(
+    subject, mri=fname_aseg, pos=7.0, bem=fname_model,
+    volume_label=labels_vol, subjects_dir=subjects_dir, verbose=True)
+
+# Generate the mixed source space
+src += vol_src
+
+# Visualize the source space.
+src.plot(subjects_dir=subjects_dir)
+
+n = sum(src[i]['nuse'] for i in range(len(src)))
+print('the src space contains %d spaces and %d points' % (len(src), n))
+
+# We could write the mixed source space with::
+#
+#    >>> write_source_spaces(fname_mixed_src, src, overwrite=True)
+#
+
+###############################################################################
+# Export source positions to nift file:
+nii_fname = op.join(bem_dir, '%s-mixed-src.nii' % subject)
+src.export_volume(nii_fname, mri_resolution=True)
+
+plotting.plot_img(nii_fname, cmap=plt.cm.spectral)
+plt.show()
+
+# Compute the fwd matrix
+fwd = make_forward_solution(fname_evoked, fname_trans, src, fname_bem,
+                            mindist=5.0,  # ignore sources<=5mm from innerskull
+                            meg=True, eeg=False, n_jobs=1)
+
+leadfield = fwd['sol']['data']
+print("Leadfield size : %d sensors x %d dipoles" % leadfield.shape)
+
+src_fwd = fwd['src']
+n = sum(src_fwd[i]['nuse'] for i in range(len(src_fwd)))
+print('the fwd src space contains %d spaces and %d points' % (len(src_fwd), n))
+
+# Load data
+condition = 'Left Auditory'
+evoked = mne.read_evokeds(fname_evoked, condition=condition,
+                          baseline=(None, 0))
+noise_cov = mne.read_cov(fname_cov)
+
+# Compute inverse solution and for each epoch
+snr = 3.0           # use smaller SNR for raw data
+inv_method = 'MNE'  # sLORETA, MNE, dSPM
+parc = 'aparc'      # the parcellation to use, e.g., 'aparc' 'aparc.a2009s'
+
+lambda2 = 1.0 / snr ** 2
+
+# Compute inverse operator
+inverse_operator = make_inverse_operator(evoked.info, fwd, noise_cov,
+                                         depth=None, fixed=False)
+
+stcs = apply_inverse(evoked, inverse_operator, lambda2, inv_method,
+                     pick_ori=None)
+
+# Get labels for FreeSurfer 'aparc' cortical parcellation with 34 labels/hemi
+labels_parc = mne.read_labels_from_annot(subject, parc=parc,
+                                         subjects_dir=subjects_dir)
+
+# Average the source estimates within each label of the cortical parcellation
+# and each sub structure contained in the src space
+# If mode = 'mean_flip' this option is used only for the surface cortical label
+src = inverse_operator['src']
+
+label_ts = mne.extract_label_time_course([stcs], labels_parc, src,
+                                         mode='mean',
+                                         allow_empty=True,
+                                         return_generator=False)
+
+# plot the times series of 2 labels
+fig, axes = plt.subplots(1)
+axes.plot(1e3 * stcs.times, label_ts[0][0, :], 'k', label='bankssts-lh')
+axes.plot(1e3 * stcs.times, label_ts[0][71, :].T, 'r',
+          label='Brain-stem')
+axes.set(xlabel='Time (ms)', ylabel='MNE current (nAm)')
+axes.legend()
+mne.viz.tight_layout()
+plt.show()
diff --git a/examples/inverse/plot_mne_point_spread_function.py b/examples/inverse/plot_mne_point_spread_function.py
index a41da35..0b7e1df 100644
--- a/examples/inverse/plot_mne_point_spread_function.py
+++ b/examples/inverse/plot_mne_point_spread_function.py
@@ -34,8 +34,7 @@ fname_label = [data_path + '/MEG/sample/labels/Aud-rh.label',
 
 
 # read forward solution
-forward = mne.read_forward_solution(fname_fwd, force_fixed=False,
-                                    surf_ori=True)
+forward = mne.read_forward_solution(fname_fwd)
 
 # read inverse operators
 inverse_operator_eegmeg = read_inverse_operator(fname_inv_eegmeg)
diff --git a/examples/inverse/plot_rap_music.py b/examples/inverse/plot_rap_music.py
index 8ef72d7..a8a89cb 100644
--- a/examples/inverse/plot_rap_music.py
+++ b/examples/inverse/plot_rap_music.py
@@ -4,13 +4,14 @@ Compute Rap-Music on evoked data
 ================================
 
 Compute a Recursively Applied and Projected MUltiple Signal Classification
-(RAP-MUSIC) on evoked dataset.
-
-The reference for Rap-Music is:
-J.C. Mosher and R.M. Leahy. 1999. Source localization using recursively
-applied and projected (RAP) MUSIC. Trans. Sig. Proc. 47, 2
-(February 1999), 332-340.
-DOI=10.1109/78.740118 http://dx.doi.org/10.1109/78.740118
+(RAP-MUSIC) [1]_ on evoked data.
+
+References
+----------
+.. [1] J.C. Mosher and R.M. Leahy. 1999. Source localization using recursively
+       applied and projected (RAP) MUSIC. Trans. Sig. Proc. 47, 2
+       (February 1999), 332-340.
+       DOI=10.1109/78.740118 http://dx.doi.org/10.1109/78.740118
 """
 
 # Author: Yousra Bekhti <yousra.bekhti at gmail.com>
@@ -40,8 +41,7 @@ evoked.crop(tmin=0.05, tmax=0.15)  # select N100
 evoked.pick_types(meg=True, eeg=False)
 
 # Read the forward solution
-forward = mne.read_forward_solution(fwd_fname, surf_ori=True,
-                                    force_fixed=False)
+forward = mne.read_forward_solution(fwd_fname)
 
 # Read noise covariance matrix
 noise_cov = mne.read_cov(cov_fname)
diff --git a/examples/inverse/plot_read_source_space.py b/examples/inverse/plot_read_source_space.py
index 2bf7776..6b5f6a6 100644
--- a/examples/inverse/plot_read_source_space.py
+++ b/examples/inverse/plot_read_source_space.py
@@ -18,23 +18,11 @@ from mne.datasets import sample
 print(__doc__)
 
 data_path = sample.data_path()
-fname = op.join(data_path, 'subjects', 'sample', 'bem', 'sample-oct-6-src.fif')
+subjects_dir = op.join(data_path, 'subjects')
+fname = op.join(subjects_dir, 'sample', 'bem', 'sample-oct-6-src.fif')
 
 patch_stats = True  # include high resolution source space
 src = mne.read_source_spaces(fname, patch_stats=patch_stats)
 
-# 3D source space (high sampling)
-lh_points = src[0]['rr']
-lh_faces = src[0]['tris']
-rh_points = src[1]['rr']
-rh_faces = src[1]['tris']
-
-from mayavi import mlab  # noqa
-mlab.figure(size=(600, 600), bgcolor=(0, 0, 0),)
-mesh = mlab.triangular_mesh(lh_points[:, 0], lh_points[:, 1], lh_points[:, 2],
-                            lh_faces, colormap='RdBu')
-mesh.module_manager.scalar_lut_manager.reverse_lut = True
-
-mesh = mlab.triangular_mesh(rh_points[:, 0], rh_points[:, 1], rh_points[:, 2],
-                            rh_faces, colormap='RdBu')
-mesh.module_manager.scalar_lut_manager.reverse_lut = True
+# Plot the 3D source space (high sampling)
+src.plot(subjects_dir=subjects_dir)
diff --git a/examples/inverse/plot_tf_dics.py b/examples/inverse/plot_tf_dics.py
index 7300b3d..80a4c6c 100644
--- a/examples/inverse/plot_tf_dics.py
+++ b/examples/inverse/plot_tf_dics.py
@@ -3,12 +3,14 @@
 Time-frequency beamforming using DICS
 =====================================
 
-Compute DICS source power in a grid of time-frequency windows and display
-results.
-
-The original reference is:
-Dalal et al. Five-dimensional neuroimaging: Localization of the time-frequency
-dynamics of cortical activity. NeuroImage (2008) vol. 40 (4) pp. 1686-1700
+Compute DICS source power [1]_ in a grid of time-frequency windows and
+display results.
+
+References
+----------
+.. [1] Dalal et al. Five-dimensional neuroimaging: Localization of the
+       time-frequency dynamics of cortical activity.
+       NeuroImage (2008) vol. 40 (4) pp. 1686-1700
 """
 # Author: Roman Goj <roman.goj at gmail.com>
 #
@@ -81,7 +83,7 @@ epochs_noise.apply_proj()
 epochs_noise = epochs_noise[:len(epochs.events)]
 
 # Read forward operator
-forward = mne.read_forward_solution(fname_fwd, surf_ori=True)
+forward = mne.read_forward_solution(fname_fwd)
 
 # Read label
 label = mne.read_label(fname_label)
diff --git a/examples/inverse/plot_tf_lcmv.py b/examples/inverse/plot_tf_lcmv.py
index 7e8f116..7e2d473 100644
--- a/examples/inverse/plot_tf_lcmv.py
+++ b/examples/inverse/plot_tf_lcmv.py
@@ -3,12 +3,14 @@
 Time-frequency beamforming using LCMV
 =====================================
 
-Compute LCMV source power in a grid of time-frequency windows and display
-results.
-
-The original reference is:
-Dalal et al. Five-dimensional neuroimaging: Localization of the time-frequency
-dynamics of cortical activity. NeuroImage (2008) vol. 40 (4) pp. 1686-1700
+Compute LCMV source power [1]_ in a grid of time-frequency windows and
+display results.
+
+References
+----------
+.. [1] Dalal et al. Five-dimensional neuroimaging: Localization of the
+       time-frequency dynamics of cortical activity.
+       NeuroImage (2008) vol. 40 (4) pp. 1686-1700
 """
 # Author: Roman Goj <roman.goj at gmail.com>
 #
@@ -90,7 +92,7 @@ epochs_noise = mne.Epochs(raw_noise, events_noise, event_id, tmin, tmax,
 epochs_noise = epochs_noise[:len(epochs.events)]
 
 # Read forward operator
-forward = mne.read_forward_solution(fname_fwd, surf_ori=True)
+forward = mne.read_forward_solution(fname_fwd)
 
 # Read label
 label = mne.read_label(fname_label)
@@ -119,7 +121,7 @@ subtract_evoked = False
 noise_covs = []
 for (l_freq, h_freq) in freq_bins:
     raw_band = raw_noise.copy()
-    raw_band.filter(l_freq, h_freq, method='iir', n_jobs=1)
+    raw_band.filter(l_freq, h_freq, n_jobs=1, fir_design='firwin')
     epochs_band = mne.Epochs(raw_band, epochs_noise.events, event_id,
                              tmin=tmin_plot, tmax=tmax_plot, baseline=None,
                              proj=True)
diff --git a/examples/inverse/plot_time_frequency_mixed_norm_inverse.py b/examples/inverse/plot_time_frequency_mixed_norm_inverse.py
index cc32bb3..80be86f 100644
--- a/examples/inverse/plot_time_frequency_mixed_norm_inverse.py
+++ b/examples/inverse/plot_time_frequency_mixed_norm_inverse.py
@@ -4,8 +4,8 @@ Compute MxNE with time-frequency sparse prior
 =============================================
 
 The TF-MxNE solver is a distributed inverse method (like dSPM or sLORETA)
-that promotes focal (sparse) sources (such as dipole fitting techniques).
-The benefit of this approach is that:
+that promotes focal (sparse) sources (such as dipole fitting techniques)
+[1]_ [2]_. The benefit of this approach is that:
 
   - it is spatio-temporal without assuming stationarity (sources properties
     can vary over time)
@@ -16,31 +16,34 @@ The benefit of this approach is that:
   - the solver solves a convex optimization problem, hence cannot be
     trapped in local minima.
 
-References:
-
-A. Gramfort, D. Strohmeier, J. Haueisen, M. Hamalainen, M. Kowalski
-Time-Frequency Mixed-Norm Estimates: Sparse M/EEG imaging with
-non-stationary source activations
-Neuroimage, Volume 70, 15 April 2013, Pages 410-422, ISSN 1053-8119,
-DOI: 10.1016/j.neuroimage.2012.12.051.
-
-A. Gramfort, D. Strohmeier, J. Haueisen, M. Hamalainen, M. Kowalski
-Functional Brain Imaging with M/EEG Using Structured Sparsity in
-Time-Frequency Dictionaries
-Proceedings Information Processing in Medical Imaging
-Lecture Notes in Computer Science, 2011, Volume 6801/2011,
-600-611, DOI: 10.1007/978-3-642-22092-0_49
-http://dx.doi.org/10.1007/978-3-642-22092-0_49
+References
+----------
+.. [1] A. Gramfort, D. Strohmeier, J. Haueisen, M. Hamalainen, M. Kowalski
+   "Time-Frequency Mixed-Norm Estimates: Sparse M/EEG imaging with
+   non-stationary source activations",
+   Neuroimage, Volume 70, pp. 410-422, 15 April 2013.
+   DOI: 10.1016/j.neuroimage.2012.12.051
+
+.. [2] A. Gramfort, D. Strohmeier, J. Haueisen, M. Hamalainen, M. Kowalski
+   "Functional Brain Imaging with M/EEG Using Structured Sparsity in
+   Time-Frequency Dictionaries",
+   Proceedings Information Processing in Medical Imaging
+   Lecture Notes in Computer Science, Volume 6801/2011, pp. 600-611, 2011.
+   DOI: 10.1007/978-3-642-22092-0_49
 """
 # Author: Alexandre Gramfort <alexandre.gramfort at telecom-paristech.fr>
+#         Daniel Strohmeier <daniel.strohmeier at tu-ilmenau.de>
 #
 # License: BSD (3-clause)
 
+import numpy as np
+
 import mne
 from mne.datasets import sample
 from mne.minimum_norm import make_inverse_operator, apply_inverse
-from mne.inverse_sparse import tf_mixed_norm
-from mne.viz import plot_sparse_source_estimates
+from mne.inverse_sparse import tf_mixed_norm, make_stc_from_dipoles
+from mne.viz import (plot_sparse_source_estimates,
+                     plot_dipole_locations, plot_dipole_amplitudes)
 
 print(__doc__)
 
@@ -62,14 +65,13 @@ evoked = mne.pick_channels_evoked(evoked)
 evoked.crop(tmin=-0.1, tmax=0.4)
 
 # Handling forward solution
-forward = mne.read_forward_solution(fwd_fname, force_fixed=False,
-                                    surf_ori=True)
+forward = mne.read_forward_solution(fwd_fname)
 
 ###############################################################################
 # Run solver
 
 # alpha_space regularization parameter is between 0 and 100 (100 is high)
-alpha_space = 50.  # spatial regularization parameter
+alpha_space = 30.  # spatial regularization parameter
 # alpha_time parameter promotes temporal smoothness
 # (0 means no temporal regularization)
 alpha_time = 1.  # temporal regularization parameter
@@ -82,18 +84,36 @@ inverse_operator = make_inverse_operator(evoked.info, forward, cov,
 stc_dspm = apply_inverse(evoked, inverse_operator, lambda2=1. / 9.,
                          method='dSPM')
 
-# Compute TF-MxNE inverse solution
-stc, residual = tf_mixed_norm(evoked, forward, cov, alpha_space, alpha_time,
-                              loose=loose, depth=depth, maxit=200, tol=1e-4,
-                              weights=stc_dspm, weights_min=8., debias=True,
-                              wsize=16, tstep=4, window=0.05,
-                              return_residual=True)
+# Compute TF-MxNE inverse solution with dipole output
+dipoles, residual = tf_mixed_norm(
+    evoked, forward, cov, alpha_space, alpha_time, loose=loose, depth=depth,
+    maxit=200, tol=1e-6, weights=stc_dspm, weights_min=8., debias=True,
+    wsize=16, tstep=4, window=0.05, return_as_dipoles=True,
+    return_residual=True)
 
 # Crop to remove edges
-stc.crop(tmin=-0.05, tmax=0.3)
+for dip in dipoles:
+    dip.crop(tmin=-0.05, tmax=0.3)
 evoked.crop(tmin=-0.05, tmax=0.3)
 residual.crop(tmin=-0.05, tmax=0.3)
 
+###############################################################################
+# Plot dipole activations
+plot_dipole_amplitudes(dipoles)
+
+# Plot dipole location of the strongest dipole with MRI slices
+idx = np.argmax([np.max(np.abs(dip.amplitude)) for dip in dipoles])
+plot_dipole_locations(dipoles[idx], forward['mri_head_t'], 'sample',
+                      subjects_dir=subjects_dir, mode='orthoview',
+                      idx='amplitude')
+
+# # Plot dipole locations of all dipoles with MRI slices
+# for dip in dipoles:
+#     plot_dipole_locations(dip, forward['mri_head_t'], 'sample',
+#                           subjects_dir=subjects_dir, mode='orthoview',
+#                           idx='amplitude')
+
+###############################################################################
 # Show the evoked response and the residual for gradiometers
 ylim = dict(grad=[-120, 120])
 evoked.pick_types(meg='grad', exclude='bads')
@@ -105,6 +125,10 @@ residual.plot(titles=dict(grad='Residuals: Gradiometers'), ylim=ylim,
               proj=True)
 
 ###############################################################################
+# Generate stc from dipoles
+stc = make_stc_from_dipoles(dipoles, forward['src'])
+
+###############################################################################
 # View in 2D and 3D ("glass" brain like 3D plot)
 plot_sparse_source_estimates(forward['src'], stc, bgcolor=(1, 1, 1),
                              opacity=0.1, fig_name="TF-MxNE (cond %s)"
diff --git a/examples/inverse/plot_vector_mne_solution.py b/examples/inverse/plot_vector_mne_solution.py
new file mode 100644
index 0000000..b566ba2
--- /dev/null
+++ b/examples/inverse/plot_vector_mne_solution.py
@@ -0,0 +1,41 @@
+"""
+==============================
+Plotting the full MNE solution
+==============================
+
+The source space that is used for the inverse computation defines a set of
+dipoles, distributed across the cortex. When visualizing a source estimate, it
+is sometimes useful to show the dipole directions, as well as their estimated
+magnitude.
+"""
+# Author: Marijn van Vliet <w.m.vanvliet at gmail.com>
+#
+# License: BSD (3-clause)
+import mne
+from mne.datasets import sample
+from mne.minimum_norm import read_inverse_operator, apply_inverse
+
+print(__doc__)
+
+data_path = sample.data_path()
+subjects_dir = data_path + '/subjects'
+
+# Read evoked data
+fname_evoked = data_path + '/MEG/sample/sample_audvis-ave.fif'
+evoked = mne.read_evokeds(fname_evoked, condition=0, baseline=(None, 0))
+
+# Read inverse solution
+fname_inv = data_path + '/MEG/sample/sample_audvis-meg-oct-6-meg-inv.fif'
+inv = read_inverse_operator(fname_inv)
+
+# Apply inverse solution, set pick_ori='vector' to obtain a
+# :class:`mne.VectorSourceEstimate` object
+snr = 3.0
+lambda2 = 1.0 / snr ** 2
+stc = apply_inverse(evoked, inv, lambda2, 'dSPM', pick_ori='vector')
+
+# Use peak getter to move vizualization to the time point of the peak magnitude
+_, peak_time = stc.magnitude().get_peak(hemi='lh')
+
+# Plot the source estimate
+brain = stc.plot(initial_time=peak_time, hemi='lh', subjects_dir=subjects_dir)
diff --git a/examples/io/README.txt b/examples/io/README.txt
index 4cd1df1..d35d8d3 100644
--- a/examples/io/README.txt
+++ b/examples/io/README.txt
@@ -2,5 +2,5 @@
 Input/Ouput
 -----------
 
-Reading and writing files. See also our :ref:`tutorials` on manipulating
+Reading and writing files. See also our :ref:`documentation` on manipulating
 data structures.
diff --git a/examples/io/plot_elekta_epochs.py b/examples/io/plot_elekta_epochs.py
index de0e0f3..488832e 100644
--- a/examples/io/plot_elekta_epochs.py
+++ b/examples/io/plot_elekta_epochs.py
@@ -15,7 +15,6 @@ averaging parameters and get epochs.
 import mne
 import os
 from mne.datasets import multimodal
-from mne import AcqParserFIF
 
 fname_raw = os.path.join(multimodal.data_path(), 'multimodal_raw.fif')
 
@@ -23,27 +22,27 @@ fname_raw = os.path.join(multimodal.data_path(), 'multimodal_raw.fif')
 print(__doc__)
 
 ###############################################################################
-# Read raw file and create parser instance
+# Read raw file
 raw = mne.io.read_raw_fif(fname_raw)
-ap = AcqParserFIF(raw.info)
 
 ###############################################################################
 # Check DACQ defined averaging categories and other info
-print(ap)
+print(raw.acqparser)
 
 ###############################################################################
 # Extract epochs corresponding to a category
-cond = ap.get_condition(raw, 'Auditory right')
+cond = raw.acqparser.get_condition(raw, 'Auditory right')
 epochs = mne.Epochs(raw, **cond)
-epochs.average().plot_topo()
+epochs.average().plot_topo(background_color='w')
 
 ###############################################################################
 # Get epochs from all conditions, average
 evokeds = []
-for cat in ap.categories:
-    cond = ap.get_condition(raw, cat)
+for cat in raw.acqparser.categories:
+    cond = raw.acqparser.get_condition(raw, cat)
     # copy (supported) rejection parameters from DACQ settings
-    epochs = mne.Epochs(raw, reject=ap.reject, flat=ap.flat, **cond)
+    epochs = mne.Epochs(raw, reject=raw.acqparser.reject,
+                        flat=raw.acqparser.flat, **cond)
     evoked = epochs.average()
     evoked.comment = cat['comment']
     evokeds.append(evoked)
@@ -63,6 +62,7 @@ newcat['reqwithin'] = .5  # ...required within .5 sec (before or after)
 newcat['reqwhen'] = 2  # ...required before (1) or after (2) ref. event
 newcat['index'] = 9  # can be set freely
 
-cond = ap.get_condition(raw, newcat)
-epochs = mne.Epochs(raw, reject=ap.reject, flat=ap.flat, **cond)
+cond = raw.acqparser.get_condition(raw, newcat)
+epochs = mne.Epochs(raw, reject=raw.acqparser.reject,
+                    flat=raw.acqparser.flat, **cond)
 epochs.average().plot()
diff --git a/examples/io/plot_objects_from_arrays.py b/examples/io/plot_objects_from_arrays.py
index 1e1f906..01b8caa 100644
--- a/examples/io/plot_objects_from_arrays.py
+++ b/examples/io/plot_objects_from_arrays.py
@@ -37,11 +37,14 @@ ch_types = ['mag', 'mag', 'grad', 'grad']
 ch_names = ['sin', 'cos', 'sinX2', 'cosX2']
 
 ###############################################################################
-# Creation of info dictionary.
+# Create an :class:`info <mne.Info>` object.
 
 # It is also possible to use info from another raw object.
 info = mne.create_info(ch_names=ch_names, sfreq=sfreq, ch_types=ch_types)
 
+###############################################################################
+# Create a dummy :class:`mne.io.RawArray` object
+
 raw = mne.io.RawArray(data, info)
 
 # Scaling of the figure.
@@ -60,7 +63,8 @@ raw.plot(n_channels=4, scalings=scalings, title='Auto-scaled Data from arrays',
 ###############################################################################
 # EpochsArray
 
-event_id = 1
+event_id = 1  # This is used to identify the events.
+# First column is for the sample number.
 events = np.array([[200, 0, event_id],
                    [1200, 0, event_id],
                    [2000, 0, event_id]])  # List of three arbitrary events
@@ -95,6 +99,37 @@ evokeds = mne.EvokedArray(evoked_data, info=info, tmin=-0.2,
 evokeds.plot(picks=picks, show=True, units={'mag': '-'},
              titles={'mag': 'sin and cos averaged'})
 
+###############################################################################
+# Create epochs by windowing the raw data.
+
+# The events are spaced evenly every 1 second.
+duration = 1.
+
+# create a fixed size events array
+# start=0 and stop=None by default
+events = mne.make_fixed_length_events(raw, event_id, duration=duration)
+print(events)
+
+# for fixed size events no start time before and after event
+tmin = 0.
+tmax = 0.99  # inclusive tmax, 1 second epochs
+
+# create :class:`Epochs <mne.Epochs>` object
+epochs = mne.Epochs(raw, events=events, event_id=event_id, tmin=tmin,
+                    tmax=tmax, baseline=None, verbose=True)
+epochs.plot(scalings='auto', block=True)
+
+###############################################################################
+# Create overlapping epochs using :func:`mne.make_fixed_length_events` (50 %
+# overlap). This also roughly doubles the amount of events compared to the
+# previous event list.
+
+duration = 0.5
+events = mne.make_fixed_length_events(raw, event_id, duration=duration)
+print(events)
+epochs = mne.Epochs(raw, events=events, tmin=tmin, tmax=tmax, baseline=None,
+                    verbose=True)
+epochs.plot(scalings='auto', block=True)
 
 ###############################################################################
 # Extracting data from NEO file
@@ -110,11 +145,14 @@ title = seg.file_origin
 
 ch_names = list()
 data = list()
-for asig in seg.analogsignals:
+for ai, asig in enumerate(seg.analogsignals):
     # Since the data does not contain channel names, channel indices are used.
-    ch_names.append(str(asig.channel_index))
-    asig = asig.rescale('V').magnitude
-    data.append(asig)
+    ch_names.append('Neo %02d' % (ai + 1,))
+    # We need the ravel() here because Neo < 0.5 gave 1D, Neo 0.5 gives
+    # 2D (but still a single channel).
+    data.append(asig.rescale('V').magnitude.ravel())
+
+data = np.array(data, float)
 
 sfreq = int(seg.analogsignals[0].sampling_rate.magnitude)
 
diff --git a/examples/io/plot_read_events.py b/examples/io/plot_read_events.py
new file mode 100644
index 0000000..faac793
--- /dev/null
+++ b/examples/io/plot_read_events.py
@@ -0,0 +1,77 @@
+"""
+.. _ex_read_events:
+
+=====================
+Reading an event file
+=====================
+
+Read events from a file. For a more detailed guide on how to read events
+using MNE-Python, see :ref:`tut_epoching_and_averaging`.
+"""
+# Authors: Alexandre Gramfort <alexandre.gramfort at telecom-paristech.fr>
+#          Chris Holdgraf <choldgraf at berkeley.edu>
+#
+# License: BSD (3-clause)
+
+import matplotlib.pyplot as plt
+import mne
+from mne.datasets import sample
+
+print(__doc__)
+
+data_path = sample.data_path()
+fname = data_path + '/MEG/sample/sample_audvis_raw-eve.fif'
+
+###############################################################################
+# Reading events
+# --------------
+#
+# Below we'll read in an events file. We suggest that this file end in
+# ``-eve.fif``. Note that we can read in the entire events file, or only
+# events corresponding to particular event types with the ``include`` and
+# ``exclude`` parameters.
+
+events_1 = mne.read_events(fname, include=1)
+events_1_2 = mne.read_events(fname, include=[1, 2])
+events_not_4_32 = mne.read_events(fname, exclude=[4, 32])
+
+###############################################################################
+# Events objects are essentially numpy arrays with three columns:
+# ``event_sample | previous_event_id | event_id``
+
+print(events_1[:5], '\n\n---\n\n', events_1_2[:5], '\n\n')
+
+for ind, before, after in events_1[:5]:
+    print("At sample %d stim channel went from %d to %d"
+          % (ind, before, after))
+
+###############################################################################
+# Plotting events
+# ---------------
+#
+# We can also plot events in order to visualize how events occur over the
+# course of our recording session. Below we'll plot our three event types
+# to see which ones were included.
+
+fig, axs = plt.subplots(1, 3, figsize=(15, 5))
+
+mne.viz.plot_events(events_1, axes=axs[0], show=False)
+axs[0].set(title="restricted to event 1")
+
+mne.viz.plot_events(events_1_2, axes=axs[1], show=False)
+axs[1].set(title="restricted to event 1 or 2")
+
+mne.viz.plot_events(events_not_4_32, axes=axs[2], show=False)
+axs[2].set(title="keep all but 4 and 32")
+plt.setp([ax.get_xticklabels() for ax in axs], rotation=45)
+plt.tight_layout()
+plt.show()
+
+###############################################################################
+# Writing events
+# --------------
+#
+# Finally, we can write events to disk. Remember to use the naming convention
+# ``-eve.fif`` for your file.
+
+mne.write_events('example-eve.fif', events_1)
diff --git a/examples/io/read_events.py b/examples/io/read_events.py
deleted file mode 100644
index ec6da51..0000000
--- a/examples/io/read_events.py
+++ /dev/null
@@ -1,31 +0,0 @@
-"""
-=====================
-Reading an event file
-=====================
-
-Read events from a file.
-"""
-# Author: Alexandre Gramfort <alexandre.gramfort at telecom-paristech.fr>
-#
-# License: BSD (3-clause)
-
-import mne
-from mne.datasets import sample
-
-print(__doc__)
-
-data_path = sample.data_path()
-fname = data_path + '/MEG/sample/sample_audvis_raw-eve.fif'
-
-# Reading events
-# events = mne.read_events(fname)  # all
-events = mne.read_events(fname, include=1)  # restricted to event 1
-events = mne.read_events(fname, include=[1, 2])  # restricted to event 1 or 2
-events = mne.read_events(fname, exclude=[4, 32])  # keep all but 4 and 32
-
-# Writing events
-mne.write_events('events.fif', events)
-
-for ind, before, after in events[:5]:
-    print("At sample %d stim channel went from %d to %d"
-          % (ind, before, after))
diff --git a/examples/preprocessing/plot_head_positions.py b/examples/preprocessing/plot_head_positions.py
new file mode 100644
index 0000000..0fea28f
--- /dev/null
+++ b/examples/preprocessing/plot_head_positions.py
@@ -0,0 +1,31 @@
+"""
+===============================
+Visualize subject head movement
+===============================
+
+Show how subjects move as a function of time.
+"""
+# Authors: Eric Larson <larson.eric.d at gmail.com>
+#
+# License: BSD (3-clause)
+
+from os import path as op
+
+import mne
+
+print(__doc__)
+
+data_path = op.join(mne.datasets.testing.data_path(verbose=True), 'SSS')
+
+pos = mne.chpi.read_head_pos(op.join(data_path, 'test_move_anon_raw.pos'))
+
+###############################################################################
+# Visualize the subject head movements as traces:
+
+mne.viz.plot_head_positions(pos, mode='traces')
+
+###############################################################################
+# Or we can visualize them as a continuous field (with the vectors pointing
+# in the head-upward direction):
+
+mne.viz.plot_head_positions(pos, mode='field')
diff --git a/examples/preprocessing/plot_movement_compensation.py b/examples/preprocessing/plot_movement_compensation.py
index 629f32e..2e2b9b3 100644
--- a/examples/preprocessing/plot_movement_compensation.py
+++ b/examples/preprocessing/plot_movement_compensation.py
@@ -29,7 +29,12 @@ raw = mne.io.read_raw_fif(op.join(data_path, 'simulated_movement_raw.fif'))
 raw_stat = mne.io.read_raw_fif(op.join(data_path,
                                        'simulated_stationary_raw.fif'))
 
-##############################################################################
+###############################################################################
+# Visualize the "subject" head movements (traces)
+
+mne.viz.plot_head_positions(pos, mode='traces')
+
+###############################################################################
 # Process our simulated raw data (taking into account head movements)
 
 # extract our resulting events
diff --git a/examples/preprocessing/plot_rereference_eeg.py b/examples/preprocessing/plot_rereference_eeg.py
index 1d9dd43..86de180 100644
--- a/examples/preprocessing/plot_rereference_eeg.py
+++ b/examples/preprocessing/plot_rereference_eeg.py
@@ -3,7 +3,7 @@
 Re-referencing the EEG signal
 =============================
 
-Load raw data and apply some EEG referencing schemes.
+This example shows how to load raw data and apply some EEG referencing schemes.
 """
 # Authors: Marijn van Vliet <w.m.vanvliet at gmail.com>
 #          Alexandre Gramfort <alexandre.gramfort at telecom-paristech.fr>
@@ -31,35 +31,37 @@ events = mne.read_events(event_fname)
 picks = mne.pick_types(raw.info, meg=False, eeg=True, eog=True, exclude='bads')
 
 ###############################################################################
-# Apply different EEG referencing schemes and plot the resulting evokeds
-
-reject = dict(eeg=180e-6, eog=150e-6)
+# We will now apply different EEG referencing schemes and plot the resulting
+# evoked potentials. Note that when we construct epochs with ``mne.Epochs``, we
+# supply the ``proj=True`` argument. This means that any available projectors
+# are applied automatically. Specifically, if there is an average reference
+# projector set by ``raw.set_eeg_reference('average', projection=True)``, MNE
+# applies this projector when creating epochs.
+
+reject = dict(eog=150e-6)
 epochs_params = dict(events=events, event_id=event_id, tmin=tmin, tmax=tmax,
-                     picks=picks, reject=reject)
+                     picks=picks, reject=reject, proj=True)
 
 fig, (ax1, ax2, ax3) = plt.subplots(nrows=3, ncols=1, sharex=True)
 
 # No reference. This assumes that the EEG has already been referenced properly.
-# This explicitly prevents MNE from adding a default EEG reference.
-raw_no_ref, _ = mne.io.set_eeg_reference(raw, [])
-evoked_no_ref = mne.Epochs(raw_no_ref, **epochs_params).average()
-del raw_no_ref  # Free memory
+# This explicitly prevents MNE from adding a default EEG reference. Any average
+# reference projector is automatically removed.
+raw.set_eeg_reference([])
+evoked_no_ref = mne.Epochs(raw, **epochs_params).average()
 
-evoked_no_ref.plot(axes=ax1, titles=dict(eeg='EEG Original reference'))
+evoked_no_ref.plot(axes=ax1, titles=dict(eeg='Original reference'), show=False)
 
 # Average reference. This is normally added by default, but can also be added
 # explicitly.
-raw_car, _ = mne.io.set_eeg_reference(raw)
-evoked_car = mne.Epochs(raw_car, **epochs_params).average()
-del raw_car
-
-evoked_car.plot(axes=ax2, titles=dict(eeg='EEG Average reference'))
+raw.set_eeg_reference('average', projection=True)
+evoked_car = mne.Epochs(raw, **epochs_params).average()
 
-# Use the mean of channels EEG 001 and EEG 002 as a reference
-raw_custom, _ = mne.io.set_eeg_reference(raw, ['EEG 001', 'EEG 002'])
-evoked_custom = mne.Epochs(raw_custom, **epochs_params).average()
-del raw_custom
+evoked_car.plot(axes=ax2, titles=dict(eeg='Average reference'), show=False)
 
-evoked_custom.plot(axes=ax3, titles=dict(eeg='EEG Custom reference'))
+# Re-reference from an average reference to the mean of channels EEG 001 and
+# EEG 002.
+raw.set_eeg_reference(['EEG 001', 'EEG 002'])
+evoked_custom = mne.Epochs(raw, **epochs_params).average()
 
-mne.viz.tight_layout()
+evoked_custom.plot(axes=ax3, titles=dict(eeg='Custom reference'))
diff --git a/examples/preprocessing/plot_resample.py b/examples/preprocessing/plot_resample.py
index 75b0af5..8f126db 100644
--- a/examples/preprocessing/plot_resample.py
+++ b/examples/preprocessing/plot_resample.py
@@ -14,8 +14,6 @@ reduction in data size, at the cost of an equal loss of temporal resolution.
 # Authors: Marijn van Vliet <w.m.vanvliet at gmail.com>
 #
 # License: BSD (3-clause)
-#
-from __future__ import print_function
 
 from matplotlib import pyplot as plt
 
@@ -61,7 +59,7 @@ mne.viz.tight_layout()
 # When resampling epochs is unwanted or impossible, for example when the data
 # doesn't fit into memory or your analysis pipeline doesn't involve epochs at
 # all, the alternative approach is to resample the continuous data. This
-# can also be done on non-preloaded data.
+# can only be done on loaded or pre-loaded data.
 
 # Resample to 300 Hz
 raw_resampled = raw.copy().resample(300, npad='auto')
diff --git a/examples/preprocessing/plot_run_ica.py b/examples/preprocessing/plot_run_ica.py
index 9b49188..322c368 100644
--- a/examples/preprocessing/plot_run_ica.py
+++ b/examples/preprocessing/plot_run_ica.py
@@ -25,9 +25,9 @@ print(__doc__)
 ###############################################################################
 # Read and preprocess the data. Preprocessing consists of:
 #
-# - meg channel selection
+# - MEG channel selection
 #
-# - 1 - 30 Hz band-pass IIR filter
+# - 1-30 Hz band-pass filter
 #
 # - epoching -0.2 to 0.5 seconds with respect to events
 
@@ -36,7 +36,7 @@ raw_fname = data_path + '/MEG/sample/sample_audvis_filt-0-40_raw.fif'
 
 raw = mne.io.read_raw_fif(raw_fname, preload=True)
 raw.pick_types(meg=True, eeg=False, exclude='bads', stim=True)
-raw.filter(1, 30, method='iir')
+raw.filter(1, 30, fir_design='firwin')
 
 # longer + more epochs for more artifact exposure
 events = mne.find_events(raw, stim_channel='STI 014')
diff --git a/examples/preprocessing/plot_virtual_evoked.py b/examples/preprocessing/plot_virtual_evoked.py
index 3c24364..c304a74 100644
--- a/examples/preprocessing/plot_virtual_evoked.py
+++ b/examples/preprocessing/plot_virtual_evoked.py
@@ -3,8 +3,8 @@
 Remap MEG channel types
 =======================
 
-In this example, MEG data are remapped from one
-channel type to another. This is useful to:
+In this example, MEG data are remapped from one channel type to another.
+This is useful to:
 
     - visualize combined magnetometers and gradiometers as magnetometers
       or gradiometers.
diff --git a/examples/preprocessing/plot_xdawn_denoising.py b/examples/preprocessing/plot_xdawn_denoising.py
index 7c15f5d..5716971 100644
--- a/examples/preprocessing/plot_xdawn_denoising.py
+++ b/examples/preprocessing/plot_xdawn_denoising.py
@@ -9,10 +9,11 @@ XDAWN components. The process is similar to an ICA, but is
 supervised in order to maximize the signal to signal + noise ratio of the
 evoked response.
 
-WARNING: As this denoising method exploits the known events to
-maximize SNR of the contrast between conditions it can lead to overfit.
-To avoid a statistical analysis problem you should split epochs used
-in fit with the ones used in apply method.
+.. warning:: As this denoising method exploits the known events to
+             maximize SNR of the contrast between conditions it can lead
+             to overfitting. To avoid a statistical analysis problem you
+             should split epochs used in fit with the ones used in
+             apply method.
 
 References
 ----------
@@ -49,7 +50,7 @@ event_id = dict(vis_r=4)
 
 # Setup for reading the raw data
 raw = io.read_raw_fif(raw_fname, preload=True)
-raw.filter(1, 20, method='iir')  # replace baselining with high-pass
+raw.filter(1, 20, fir_design='firwin')  # replace baselining with high-pass
 events = read_events(event_fname)
 
 raw.info['bads'] = ['MEG 2443']  # set bad channels
@@ -58,7 +59,7 @@ picks = pick_types(raw.info, meg=True, eeg=False, stim=False, eog=False,
 # Epoching
 epochs = Epochs(raw, events, event_id, tmin, tmax, proj=False,
                 picks=picks, baseline=None, preload=True,
-                add_eeg_ref=False, verbose=False)
+                verbose=False)
 
 # Plot image epoch before xdawn
 plot_epochs_image(epochs['vis_r'], picks=[230], vmin=-500, vmax=500)
diff --git a/examples/realtime/ftclient_rt_average.py b/examples/realtime/ftclient_rt_average.py
index 10b89cf..bf88cdc 100644
--- a/examples/realtime/ftclient_rt_average.py
+++ b/examples/realtime/ftclient_rt_average.py
@@ -82,7 +82,7 @@ with FieldTripClient(host='localhost', port=1972,
         plot_events(rt_epochs.events[-5:], sfreq=ev.info['sfreq'],
                     first_samp=-rt_client.tmin_samp, axes=ax[0])
 
-        evoked.plot(axes=ax[1])  # plot on second subplot
+        evoked.plot(axes=ax[1], selectable=False)  # plot on second subplot
         ax[1].set_title('Evoked response for gradiometer channels'
                         '(event_id = %d)' % event_id)
 
diff --git a/examples/realtime/plot_compute_rt_decoder.py b/examples/realtime/plot_compute_rt_decoder.py
index 1c16308..1981ba1 100644
--- a/examples/realtime/plot_compute_rt_decoder.py
+++ b/examples/realtime/plot_compute_rt_decoder.py
@@ -40,7 +40,8 @@ rt_client = MockRtClient(raw)
 
 # create the real-time epochs object
 rt_epochs = RtEpochs(rt_client, event_id, tmin, tmax, picks=picks, decim=1,
-                     reject=dict(grad=4000e-13, eog=150e-6))
+                     reject=dict(grad=4000e-13, eog=150e-6), baseline=None,
+                     isi_max=4.)
 
 # start the acquisition
 rt_epochs.start()
@@ -54,13 +55,15 @@ n_times = len(rt_epochs.times)
 from sklearn import preprocessing  # noqa
 from sklearn.svm import SVC  # noqa
 from sklearn.pipeline import Pipeline  # noqa
-from sklearn.cross_validation import cross_val_score, ShuffleSplit  # noqa
+from sklearn.model_selection import cross_val_score, ShuffleSplit  # noqa
 from mne.decoding import Vectorizer, FilterEstimator  # noqa
 
 
 scores_x, scores, std_scores = [], [], []
 
-filt = FilterEstimator(rt_epochs.info, 1, 40)
+# don't highpass filter because it's epoched data and the signal length
+# is small
+filt = FilterEstimator(rt_epochs.info, None, 40, fir_design='firwin')
 scaler = preprocessing.StandardScaler()
 vectorizer = Vectorizer()
 clf = SVC(C=1, kernel='linear')
@@ -70,6 +73,14 @@ concat_classifier = Pipeline([('filter', filt), ('vector', vectorizer),
 
 data_picks = mne.pick_types(rt_epochs.info, meg='grad', eeg=False, eog=True,
                             stim=False, exclude=raw.info['bads'])
+ax = plt.subplot(111)
+ax.set_xlabel('Trials')
+ax.set_ylabel('Classification score (% correct)')
+ax.set_title('Real-time decoding')
+ax.set_xlim([min_trials, 50])
+ax.set_ylim([30, 105])
+plt.axhline(50, color='k', linestyle='--', label="Chance level")
+plt.show(block=False)
 
 for ev_num, ev in enumerate(rt_epochs.iter_evoked()):
 
@@ -84,7 +95,7 @@ for ev_num, ev in enumerate(rt_epochs.iter_evoked()):
 
     if ev_num >= min_trials:
 
-        cv = ShuffleSplit(len(y), 5, test_size=0.2, random_state=42)
+        cv = ShuffleSplit(5, test_size=0.2, random_state=42)
         scores_t = cross_val_score(concat_classifier, X, y, cv=cv,
                                    n_jobs=1) * 100
 
@@ -93,21 +104,19 @@ for ev_num, ev in enumerate(rt_epochs.iter_evoked()):
         scores_x.append(ev_num)
 
         # Plot accuracy
-        plt.clf()
 
-        plt.plot(scores_x, scores, '+', label="Classif. score")
-        plt.hold(True)
-        plt.plot(scores_x, scores)
-        plt.axhline(50, color='k', linestyle='--', label="Chance level")
+        plt.plot(scores_x[-2:], scores[-2:], '-x', color='b',
+                 label="Classif. score")
+        ax.plot(scores_x[-1], scores[-1])
+
         hyp_limits = (np.asarray(scores) - np.asarray(std_scores),
                       np.asarray(scores) + np.asarray(std_scores))
-        plt.fill_between(scores_x, hyp_limits[0], y2=hyp_limits[1],
-                         color='b', alpha=0.5)
-        plt.xlabel('Trials')
-        plt.ylabel('Classification score (% correct)')
-        plt.xlim([min_trials, 50])
-        plt.ylim([30, 105])
-        plt.title('Real-time decoding')
-        plt.show(block=False)
+        fill = plt.fill_between(scores_x, hyp_limits[0], y2=hyp_limits[1],
+                                color='b', alpha=0.5)
         plt.pause(0.01)
-plt.show()
+        plt.draw()
+        ax.collections.remove(fill)  # Remove old fill area
+
+plt.fill_between(scores_x, hyp_limits[0], y2=hyp_limits[1], color='b',
+                 alpha=0.5)
+plt.draw()  # Final figure
diff --git a/examples/realtime/rt_feedback_server.py b/examples/realtime/rt_feedback_server.py
index 296a664..a0b75c7 100644
--- a/examples/realtime/rt_feedback_server.py
+++ b/examples/realtime/rt_feedback_server.py
@@ -36,7 +36,7 @@ import matplotlib.pyplot as plt
 from sklearn import preprocessing
 from sklearn.svm import SVC
 from sklearn.pipeline import Pipeline
-from sklearn.cross_validation import train_test_split
+from sklearn.model_selection import train_test_split
 from sklearn.metrics import confusion_matrix
 
 import mne
diff --git a/examples/simulation/plot_simulate_evoked_data.py b/examples/simulation/plot_simulate_evoked_data.py
index 0fab94f..4f1178f 100644
--- a/examples/simulation/plot_simulate_evoked_data.py
+++ b/examples/simulation/plot_simulate_evoked_data.py
@@ -33,7 +33,7 @@ fwd_fname = data_path + '/MEG/sample/sample_audvis-meg-eeg-oct-6-fwd.fif'
 ave_fname = data_path + '/MEG/sample/sample_audvis-no-filter-ave.fif'
 cov_fname = data_path + '/MEG/sample/sample_audvis-cov.fif'
 
-fwd = mne.read_forward_solution(fwd_fname, force_fixed=True, surf_ori=True)
+fwd = mne.read_forward_solution(fwd_fname)
 fwd = mne.pick_types_forward(fwd, meg=True, eeg=True, exclude=raw.info['bads'])
 cov = mne.read_cov(cov_fname)
 info = mne.io.read_info(ave_fname)
@@ -51,9 +51,10 @@ rng = np.random.RandomState(42)
 
 def data_fun(times):
     """Function to generate random source time courses"""
-    return (1e-9 * np.sin(30. * times) *
+    return (50e-9 * np.sin(30. * times) *
             np.exp(- (times - 0.15 + 0.05 * rng.randn(1)) ** 2 / 0.01))
 
+
 stc = simulate_sparse_stc(fwd['src'], n_dipoles=2, times=times,
                           random_state=42, labels=labels, data_fun=data_fun)
 
@@ -61,8 +62,9 @@ stc = simulate_sparse_stc(fwd['src'], n_dipoles=2, times=times,
 # Generate noisy evoked data
 picks = mne.pick_types(raw.info, meg=True, exclude='bads')
 iir_filter = fit_iir_model_raw(raw, order=5, picks=picks, tmin=60, tmax=180)[1]
-snr = 6.  # dB
-evoked = simulate_evoked(fwd, stc, info, cov, snr, iir_filter=iir_filter)
+nave = 100  # simulate average of 100 epochs
+evoked = simulate_evoked(fwd, stc, info, cov, nave=nave, use_cps=True,
+                         iir_filter=iir_filter)
 
 ###############################################################################
 # Plot
diff --git a/examples/stats/plot_linear_regression_raw.py b/examples/stats/plot_linear_regression_raw.py
index 6033c2e..4ed57fb 100644
--- a/examples/stats/plot_linear_regression_raw.py
+++ b/examples/stats/plot_linear_regression_raw.py
@@ -30,8 +30,9 @@ from mne.stats.regression import linear_regression_raw
 # Load and preprocess data
 data_path = sample.data_path()
 raw_fname = data_path + '/MEG/sample/sample_audvis_filt-0-40_raw.fif'
-raw = mne.io.read_raw_fif(raw_fname, preload=True).pick_types(
-    meg='grad', stim=True, eeg=False).filter(1, None, method='iir')
+raw = mne.io.read_raw_fif(raw_fname, preload=True)
+raw.pick_types(meg='grad', stim=True, eeg=False)
+raw.filter(1, None, fir_design='firwin')  # high-pass
 
 # Set up events
 events = mne.find_events(raw)
@@ -55,7 +56,9 @@ fig, (ax1, ax2, ax3) = plt.subplots(3, 1)
 params = dict(spatial_colors=True, show=False, ylim=dict(grad=(-200, 200)))
 epochs[cond].average().plot(axes=ax1, **params)
 evokeds[cond].plot(axes=ax2, **params)
-(evokeds[cond] - epochs[cond].average()).plot(axes=ax3, **params)
+contrast = mne.combine_evoked([evokeds[cond], -epochs[cond].average()],
+                              weights='equal')
+contrast.plot(axes=ax3, **params)
 ax1.set_title("Traditional averaging")
 ax2.set_title("rERF")
 ax3.set_title("Difference")
diff --git a/examples/stats/plot_sensor_permutation_test.py b/examples/stats/plot_sensor_permutation_test.py
index c650fa1..925afba 100644
--- a/examples/stats/plot_sensor_permutation_test.py
+++ b/examples/stats/plot_sensor_permutation_test.py
@@ -34,13 +34,9 @@ tmax = 0.5
 raw = io.read_raw_fif(raw_fname)
 events = mne.read_events(event_fname)
 
-#   Set up pick list: MEG + STI 014 - bad channels (modify to your needs)
-include = []  # or stim channel ['STI 014']
-raw.info['bads'] += ['MEG 2443', 'EEG 053']  # bads + 2 more
-
 # pick MEG Gradiometers
 picks = mne.pick_types(raw.info, meg='grad', eeg=False, stim=False, eog=True,
-                       include=include, exclude='bads')
+                       exclude='bads')
 epochs = mne.Epochs(raw, events, event_id, tmin, tmax, picks=picks,
                     baseline=(None, 0), reject=dict(grad=4000e-13, eog=150e-6))
 data = epochs.get_data()
@@ -68,7 +64,7 @@ evoked = mne.EvokedArray(-np.log10(p_values)[:, np.newaxis],
 stats_picks = mne.pick_channels(evoked.ch_names, significant_sensors_names)
 mask = p_values[:, np.newaxis] <= 0.05
 
-evoked.plot_topomap(ch_type='grad', times=[0], scale=1,
+evoked.plot_topomap(ch_type='grad', times=[0], scalings=1,
                     time_format=None, cmap='Reds', vmin=0., vmax=np.max,
-                    unit='-log10(p)', cbar_fmt='-%0.1f', mask=mask,
+                    units='-log10(p)', cbar_fmt='-%0.1f', mask=mask,
                     size=3, show_names=lambda x: x[4:] + ' ' * 20)
diff --git a/examples/stats/plot_sensor_regression.py b/examples/stats/plot_sensor_regression.py
index b12c2db..89d7ed2 100644
--- a/examples/stats/plot_sensor_regression.py
+++ b/examples/stats/plot_sensor_regression.py
@@ -64,13 +64,14 @@ design_matrix = np.column_stack([intercept,  # intercept
 lm = linear_regression(epochs, design_matrix, names)
 
 
-def plot_topomap(x, unit):
-    x.plot_topomap(ch_type='mag', scale=1, size=1.5, vmax=np.max,
-                   unit=unit, times=np.linspace(0.1, 0.2, 5))
+def plot_topomap(x, units):
+    x.plot_topomap(ch_type='mag', scalings=1., size=1.5, vmax=np.max,
+                   units=units, times=np.linspace(0.1, 0.2, 5))
+
 
 trial_count = lm['trial-count']
 
-plot_topomap(trial_count.beta, unit='z (beta)')
-plot_topomap(trial_count.t_val, unit='t')
-plot_topomap(trial_count.mlog10_p_val, unit='-log10 p')
-plot_topomap(trial_count.stderr, unit='z (error)')
+plot_topomap(trial_count.beta, units='z (beta)')
+plot_topomap(trial_count.t_val, units='t')
+plot_topomap(trial_count.mlog10_p_val, units='-log10 p')
+plot_topomap(trial_count.stderr, units='z (error)')
diff --git a/examples/time_frequency/plot_compute_raw_data_spectrum.py b/examples/time_frequency/plot_compute_raw_data_spectrum.py
index 80f6656..ffa634c 100644
--- a/examples/time_frequency/plot_compute_raw_data_spectrum.py
+++ b/examples/time_frequency/plot_compute_raw_data_spectrum.py
@@ -23,7 +23,13 @@ from mne.time_frequency import psd_multitaper
 print(__doc__)
 
 ###############################################################################
-# Set parameters
+# Load data
+# ---------
+#
+# We'll load a sample MEG dataset, along with SSP projections that will
+# allow us to reduce EOG and ECG artifacts. For more information about
+# reducing artifacts, see the preprocessing section in :ref:`documentation`.
+
 data_path = sample.data_path()
 raw_fname = data_path + '/MEG/sample/sample_audvis_raw.fif'
 proj_fname = data_path + '/MEG/sample/sample_audvis_eog-proj.fif'
@@ -42,10 +48,29 @@ raw.add_proj(projs, remove_existing=True)
 fmin, fmax = 2, 300  # look at frequencies between 2 and 300Hz
 n_fft = 2048  # the FFT size (n_fft). Ideally a power of 2
 
-# Let's first check out all channel types
-raw.plot_psd(area_mode='range', tmax=10.0, show=False)
+###############################################################################
+# Plot the raw PSD
+# ----------------
+#
+# First we'll visualize the raw PSD of our data. We'll do this on all of the
+# channels first. Note that there are several parameters to the
+# :meth:`mne.io.Raw.plot_psd` method, some of which will be explained below.
+
+raw.plot_psd(area_mode='range', tmax=10.0, show=False, average=True)
+
+###############################################################################
+# Plot a cleaned PSD
+# ------------------
+#
+# Next we'll focus the visualization on a subset of channels.
+# This can be useful for identifying particularly noisy channels or
+# investigating how the power spectrum changes across channels.
+#
+# We'll visualize how this PSD changes after applying some standard
+# filtering techniques. We'll first apply the SSP projections, which is
+# accomplished with the ``proj=True`` kwarg. We'll then perform a notch filter
+# to remove particular frequency bands.
 
-# Now let's focus on a smaller subset:
 # Pick MEG magnetometers in the Left-temporal region
 selection = read_selection('Left-temporal')
 picks = mne.pick_types(raw.info, meg='mag', eeg=False, eog=False,
@@ -58,24 +83,31 @@ plt.figure()
 ax = plt.axes()
 raw.plot_psd(tmin=tmin, tmax=tmax, fmin=fmin, fmax=fmax, n_fft=n_fft,
              n_jobs=1, proj=False, ax=ax, color=(0, 0, 1),  picks=picks,
-             show=False)
+             show=False, average=True)
 
-# And now do the same with SSP applied
 raw.plot_psd(tmin=tmin, tmax=tmax, fmin=fmin, fmax=fmax, n_fft=n_fft,
              n_jobs=1, proj=True, ax=ax, color=(0, 1, 0), picks=picks,
-             show=False)
+             show=False, average=True)
 
 # And now do the same with SSP + notch filtering
 # Pick all channels for notch since the SSP projection mixes channels together
-raw.notch_filter(np.arange(60, 241, 60), n_jobs=1)
+raw.notch_filter(np.arange(60, 241, 60), n_jobs=1, fir_design='firwin')
 raw.plot_psd(tmin=tmin, tmax=tmax, fmin=fmin, fmax=fmax, n_fft=n_fft,
              n_jobs=1, proj=True, ax=ax, color=(1, 0, 0), picks=picks,
-             show=False)
+             show=False, average=True)
 
 ax.set_title('Four left-temporal magnetometers')
-plt.legend(['Without SSP', 'With SSP', 'SSP + Notch'])
+plt.legend(ax.lines[::3], ['Without SSP', 'With SSP', 'SSP + Notch'])
+
+###############################################################################
+# Alternative functions for PSDs
+# ------------------------------
+#
+# There are also several functions in MNE that create a PSD using a Raw
+# object. These are in the :mod:`mne.time_frequency` module and begin with
+# ``psd_*``. For example, we'll use a multitaper method to compute the PSD
+# below.
 
-# Alternatively, you may also create PSDs from Raw objects with ``psd_*``
 f, ax = plt.subplots()
 psds, freqs = psd_multitaper(raw, low_bias=True, tmin=tmin, tmax=tmax,
                              fmin=fmin, fmax=fmax, proj=True, picks=picks,
diff --git a/examples/time_frequency/plot_source_label_time_frequency.py b/examples/time_frequency/plot_source_label_time_frequency.py
index e3b203d..4f6f9f6 100644
--- a/examples/time_frequency/plot_source_label_time_frequency.py
+++ b/examples/time_frequency/plot_source_label_time_frequency.py
@@ -55,9 +55,9 @@ epochs = mne.Epochs(raw, events, event_id, tmin, tmax, picks=picks,
 
 # Compute a source estimate per frequency band including and excluding the
 # evoked response
-frequencies = np.arange(7, 30, 2)  # define frequencies of interest
+freqs = np.arange(7, 30, 2)  # define frequencies of interest
 label = mne.read_label(fname_label)
-n_cycles = frequencies / 3.  # different number of cycle per frequency
+n_cycles = freqs / 3.  # different number of cycle per frequency
 
 # subtract the evoked response in order to exclude evoked activity
 epochs_induced = epochs.copy().subtract_evoked()
@@ -69,7 +69,7 @@ for ii, (this_epochs, title) in enumerate(zip([epochs, epochs_induced],
                                                'induced only'])):
     # compute the source space power and the inter-trial coherence
     power, itc = source_induced_power(
-        this_epochs, inverse_operator, frequencies, label, baseline=(-0.1, 0),
+        this_epochs, inverse_operator, freqs, label, baseline=(-0.1, 0),
         baseline_mode='percent', n_cycles=n_cycles, n_jobs=1)
 
     power = np.mean(power, axis=0)  # average over sources
@@ -81,7 +81,7 @@ for ii, (this_epochs, title) in enumerate(zip([epochs, epochs_induced],
     plt.subplots_adjust(0.1, 0.08, 0.96, 0.94, 0.2, 0.43)
     plt.subplot(2, 2, 2 * ii + 1)
     plt.imshow(20 * power,
-               extent=[times[0], times[-1], frequencies[0], frequencies[-1]],
+               extent=[times[0], times[-1], freqs[0], freqs[-1]],
                aspect='auto', origin='lower', vmin=0., vmax=30., cmap='RdBu_r')
     plt.xlabel('Time (s)')
     plt.ylabel('Frequency (Hz)')
@@ -90,7 +90,7 @@ for ii, (this_epochs, title) in enumerate(zip([epochs, epochs_induced],
 
     plt.subplot(2, 2, 2 * ii + 2)
     plt.imshow(itc,
-               extent=[times[0], times[-1], frequencies[0], frequencies[-1]],
+               extent=[times[0], times[-1], freqs[0], freqs[-1]],
                aspect='auto', origin='lower', vmin=0, vmax=0.7,
                cmap='RdBu_r')
     plt.xlabel('Time (s)')
diff --git a/examples/time_frequency/plot_source_space_time_frequency.py b/examples/time_frequency/plot_source_space_time_frequency.py
index 0fa5546..817b1e3 100644
--- a/examples/time_frequency/plot_source_space_time_frequency.py
+++ b/examples/time_frequency/plot_source_space_time_frequency.py
@@ -54,7 +54,7 @@ bands = dict(alpha=[9, 11], beta=[18, 22])
 stcs = source_band_induced_power(epochs, inverse_operator, bands, n_cycles=2,
                                  use_fft=False, n_jobs=1)
 
-for b, stc in stcs.iteritems():
+for b, stc in stcs.items():
     stc.save('induced_power_%s' % b)
 
 ###############################################################################
diff --git a/examples/time_frequency/plot_temporal_whitening.py b/examples/time_frequency/plot_temporal_whitening.py
index 550e76c..9dcd157 100644
--- a/examples/time_frequency/plot_temporal_whitening.py
+++ b/examples/time_frequency/plot_temporal_whitening.py
@@ -3,7 +3,7 @@
 Temporal whitening with AR model
 ================================
 
-This script shows how to fit an AR model to data and use it
+Here we fit an AR model to the data and use it
 to temporally whiten the signals.
 
 """
@@ -35,11 +35,11 @@ raw.info['bads'] = ['MEG 2443', 'EEG 053']  # mark bad channels
 picks = mne.pick_types(raw.info, meg='grad', exclude='bads')
 
 order = 5  # define model order
-picks = picks[:5]
+picks = picks[:1]
 
 # Estimate AR models on raw data
 b, a = fit_iir_model_raw(raw, order=order, picks=picks, tmin=60, tmax=180)
-d, times = raw[0, 1e4:2e4]  # look at one channel from now on
+d, times = raw[0, 10000:20000]  # look at one channel from now on
 d = d.ravel()  # make flat vector
 innovation = signal.convolve(d, a, 'valid')
 d_ = signal.lfilter(b, a, innovation)  # regenerate the signal
diff --git a/examples/time_frequency/plot_time_frequency_erds.py b/examples/time_frequency/plot_time_frequency_erds.py
new file mode 100644
index 0000000..ade2c62
--- /dev/null
+++ b/examples/time_frequency/plot_time_frequency_erds.py
@@ -0,0 +1,118 @@
+"""
+===============================
+Compute and visualize ERDS maps
+===============================
+
+This example calculates and displays ERDS maps of event-related EEG data. ERDS
+(sometimes also written as ERD/ERS) is short for event-related
+desynchronization (ERD) and event-related synchronization (ERS) [1]_.
+Conceptually, ERD corresponds to a decrease in power in a specific frequency
+band relative to a baseline. Similarly, ERS corresponds to an increase in
+power. An ERDS map is a time/frequency representation of ERD/ERS over a range
+of frequencies [2]_. ERDS maps are also known as ERSP (event-related spectral
+perturbation) [3]_.
+
+We use a public EEG BCI data set containing two different motor imagery tasks
+available at PhysioNet. The two tasks are imagined hand and feet movement. Our
+goal is to generate ERDS maps for each of the two tasks.
+
+First, we load the data and create epochs of 5s length. The data sets contain
+multiple channels, but we will only consider the three channels C3, Cz, and C4.
+We compute maps containing frequencies ranging from 2 to 35Hz. We map ERD to
+red color and ERS to blue color, which is the convention in many ERDS
+publications. Note that we do not perform any significance tests on the map
+values, but instead we display the whole time/frequency maps.
+
+References
+----------
+
+.. [1] G. Pfurtscheller, F. H. Lopes da Silva. Event-related EEG/MEG
+       synchronization and desynchronization: basic principles. Clinical
+       Neurophysiology 110(11), 1842-1857, 1999.
+.. [2] B. Graimann, J. E. Huggins, S. P. Levine, G. Pfurtscheller.
+       Visualization of significant ERD/ERS patterns in multichannel EEG and
+       ECoG data. Clinical Neurophysiology 113(1), 43-47, 2002.
+.. [3] S. Makeig. Auditory event-related dynamics of the EEG spectrum and
+       effects of exposure to tones. Electroencephalography and Clinical
+       Neurophysiology 86(4), 283-293, 1993.
+"""
+# Authors: Clemens Brunner <clemens.brunner at gmail.com>
+#
+# License: BSD (3-clause)
+
+
+import numpy as np
+import matplotlib.pyplot as plt
+from matplotlib.colors import LinearSegmentedColormap
+import mne
+from mne.datasets import eegbci
+from mne.io import concatenate_raws, read_raw_edf
+from mne.time_frequency import tfr_multitaper
+
+
+def center_cmap(cmap, vmin, vmax):
+    """Center given colormap (ranging from vmin to vmax) at value 0.
+
+    Note that eventually this could also be achieved by re-normalizing a given
+    colormap by subclassing matplotlib.colors.Normalize as described here:
+    https://matplotlib.org/users/colormapnorms.html#custom-normalization-two-linear-ranges
+    """  # noqa: E501
+    vzero = abs(vmin) / (vmax - vmin)
+    index_old = np.linspace(0, 1, cmap.N)
+    index_new = np.hstack([np.linspace(0, vzero, cmap.N // 2, endpoint=False),
+                           np.linspace(vzero, 1, cmap.N // 2)])
+    cdict = {"red": [], "green": [], "blue": [], "alpha": []}
+    for old, new in zip(index_old, index_new):
+        r, g, b, a = cmap(old)
+        cdict["red"].append((new, r, r))
+        cdict["green"].append((new, g, g))
+        cdict["blue"].append((new, b, b))
+        cdict["alpha"].append((new, a, a))
+    return LinearSegmentedColormap("erds", cdict)
+
+
+# load and preprocess data ####################################################
+subject = 1  # use data from subject 1
+runs = [6, 10, 14]  # use only hand and feet motor imagery runs
+
+fnames = eegbci.load_data(subject, runs)
+raws = [read_raw_edf(f, preload=True, stim_channel='auto') for f in fnames]
+raw = concatenate_raws(raws)
+
+raw.rename_channels(lambda x: x.strip('.'))  # remove dots from channel names
+
+events = mne.find_events(raw, shortest_event=0, stim_channel='STI 014')
+
+picks = mne.pick_channels(raw.info["ch_names"], ["C3", "Cz", "C4"])
+
+# epoch data ##################################################################
+tmin, tmax = -1, 4  # define epochs around events (in s)
+event_ids = dict(hands=2, feet=3)  # map event IDs to tasks
+
+epochs = mne.Epochs(raw, events, event_ids, tmin - 0.5, tmax + 0.5,
+                    picks=picks, baseline=None, preload=True)
+
+# compute ERDS maps ###########################################################
+freqs = np.arange(2, 36, 1)  # frequencies from 2-35Hz
+n_cycles = freqs  # use constant t/f resolution
+vmin, vmax = -1, 1.5  # set min and max ERDS values in plot
+cmap = center_cmap(plt.cm.RdBu, vmin, vmax)  # zero maps to white
+
+for event in event_ids:
+    power = tfr_multitaper(epochs[event], freqs=freqs, n_cycles=n_cycles,
+                           use_fft=True, return_itc=False, decim=2)
+    power.crop(tmin, tmax)
+
+    fig, ax = plt.subplots(1, 4, figsize=(12, 4),
+                           gridspec_kw={"width_ratios": [10, 10, 10, 1]})
+    for i in range(3):
+        power.plot([i], baseline=[-1, 0], mode="percent", vmin=vmin, vmax=vmax,
+                   cmap=(cmap, False), axes=ax[i], colorbar=False, show=False)
+        ax[i].set_title(epochs.ch_names[i], fontsize=10)
+        ax[i].axvline(0, linewidth=1, color="black", linestyle=":")  # event
+        if i > 0:
+            ax[i].set_ylabel("")
+            ax[i].set_yticklabels("")
+    fig.colorbar(ax[0].collections[0], cax=ax[-1])
+    fig.suptitle("ERDS ({})".format(event))
+    fig.show()
diff --git a/examples/time_frequency/plot_time_frequency_global_field_power.py b/examples/time_frequency/plot_time_frequency_global_field_power.py
new file mode 100644
index 0000000..8a06b4c
--- /dev/null
+++ b/examples/time_frequency/plot_time_frequency_global_field_power.py
@@ -0,0 +1,128 @@
+"""
+===========================================================
+Explore event-related dynamics for specific frequency bands
+===========================================================
+
+The objective is to show you how to explore spectrally localized
+effects. For this purpose we adapt the method described in [1]_ and use it on
+the somato dataset. The idea is to track the band-limited temporal evolution
+of spatial patterns by using the Global Field Power (GFP).
+
+We first bandpass filter the signals and then apply a Hilbert transform. To
+reveal oscillatory activity the evoked response is then subtracted from every
+single trial. Finally, we rectify the signals prior to averaging across trials
+by taking the magniude of the Hilbert.
+Then the GFP is computed as described in [2]_, using the sum of the squares
+but without normalization by the rank.
+Baselining is subsequently applied to make the GFPs comparable between
+frequencies.
+The procedure is then repeated for each frequency band of interest and
+all GFPs are visualized. To estimate uncertainty, non-parametric confidence
+intervals are computed as described in [3]_ across channels.
+
+The advantage of this method over summarizing the Space x Time x Frequency
+output of a Morlet Wavelet in frequency bands is relative speed and, more
+importantly, the clear-cut comparability of the spectral decomposition (the
+same type of filter is used across all bands).
+
+References
+----------
+
+.. [1] Hari R. and Salmelin R. Human cortical oscillations: a neuromagnetic
+       view through the skull (1997). Trends in Neuroscience 20 (1),
+       pp. 44-49.
+.. [2] Engemann D. and Gramfort A. (2015) Automated model selection in
+       covariance estimation and spatial whitening of MEG and EEG signals,
+       vol. 108, 328-342, NeuroImage.
+.. [3] Efron B. and Hastie T. Computer Age Statistical Inference (2016).
+       Cambrdige University Press, Chapter 11.2.
+"""
+# Authors: Denis A. Engemann <denis.engemann at gmail.com>
+#
+# License: BSD (3-clause)
+
+import numpy as np
+import matplotlib.pyplot as plt
+
+import mne
+from mne.datasets import somato
+from mne.baseline import rescale
+from mne.stats import _bootstrap_ci
+
+###############################################################################
+# Set parameters
+data_path = somato.data_path()
+raw_fname = data_path + '/MEG/somato/sef_raw_sss.fif'
+
+# let's explore some frequency bands
+iter_freqs = [
+    ('Theta', 4, 7),
+    ('Alpha', 8, 12),
+    ('Beta', 13, 25),
+    ('Gamma', 30, 45)
+]
+
+###############################################################################
+# We create average power time courses for each frequency band
+
+# set epoching parameters
+event_id, tmin, tmax = 1, -1., 3.
+baseline = None
+
+# get the header to extract events
+raw = mne.io.read_raw_fif(raw_fname, preload=False)
+events = mne.find_events(raw, stim_channel='STI 014')
+
+frequency_map = list()
+
+for band, fmin, fmax in iter_freqs:
+    # (re)load the data to save memory
+    raw = mne.io.read_raw_fif(raw_fname, preload=True)
+    raw.pick_types(meg='grad', eog=True)  # we just look at gradiometers
+
+    # bandpass filter and compute Hilbert
+    raw.filter(fmin, fmax, n_jobs=1,  # use more jobs to speed up.
+               l_trans_bandwidth=1,  # make sure filter params are the same
+               h_trans_bandwidth=1,  # in each band and skip "auto" option.
+               fir_design='firwin')
+    raw.apply_hilbert(n_jobs=1, envelope=False)
+
+    epochs = mne.Epochs(raw, events, event_id, tmin, tmax, baseline=baseline,
+                        reject=dict(grad=4000e-13, eog=350e-6), preload=True)
+    # remove evoked response and get analytic signal (envelope)
+    epochs.subtract_evoked()  # for this we need to construct new epochs.
+    epochs = mne.EpochsArray(
+        data=np.abs(epochs.get_data()), info=epochs.info, tmin=epochs.tmin)
+    # now average and move on
+    frequency_map.append(((band, fmin, fmax), epochs.average()))
+
+###############################################################################
+# Now we can compute the Global Field Power
+# We can track the emergence of spatial patterns compared to baseline
+# for each frequency band, with a bootstrapped confidence interval.
+#
+# We see dominant responses in the Alpha and Beta bands.
+
+fig, axes = plt.subplots(4, 1, figsize=(10, 7), sharex=True, sharey=True)
+colors = plt.get_cmap('winter_r')(np.linspace(0, 1, 4))
+for ((freq_name, fmin, fmax), average), color, ax in zip(
+        frequency_map, colors, axes.ravel()[::-1]):
+    times = average.times * 1e3
+    gfp = np.sum(average.data ** 2, axis=0)
+    gfp = mne.baseline.rescale(gfp, times, baseline=(None, 0))
+    ax.plot(times, gfp, label=freq_name, color=color, linewidth=2.5)
+    ax.axhline(0, linestyle='--', color='grey', linewidth=2)
+    ci_low, ci_up = _bootstrap_ci(average.data, random_state=0,
+                                  stat_fun=lambda x: np.sum(x ** 2, axis=0))
+    ci_low = rescale(ci_low, average.times, baseline=(None, 0))
+    ci_up = rescale(ci_up, average.times, baseline=(None, 0))
+    ax.fill_between(times, gfp + ci_up, gfp - ci_low, color=color, alpha=0.3)
+    ax.grid(True)
+    ax.set_ylabel('GFP')
+    ax.annotate('%s (%d-%dHz)' % (freq_name, fmin, fmax),
+                xy=(0.95, 0.8),
+                horizontalalignment='right',
+                xycoords='axes fraction')
+    ax.set_xlim(-1000, 3000)
+
+axes.ravel()[-1].set_xlabel('Time [ms]')
diff --git a/examples/time_frequency/plot_time_frequency_simulated.py b/examples/time_frequency/plot_time_frequency_simulated.py
index f99e84b..5ef3e2b 100644
--- a/examples/time_frequency/plot_time_frequency_simulated.py
+++ b/examples/time_frequency/plot_time_frequency_simulated.py
@@ -1,26 +1,35 @@
 """
-========================================================
-Time-frequency on simulated data (Multitaper vs. Morlet)
-========================================================
-
-This examples demonstrates on simulated data the different time-frequency
-estimation methods. It shows the time-frequency resolution trade-off
-and the problem of estimation variance.
+======================================================================
+Time-frequency on simulated data (Multitaper vs. Morlet vs. Stockwell)
+======================================================================
+
+This example demonstrates the different time-frequency estimation methods
+on simulated data. It shows the time-frequency resolution trade-off
+and the problem of estimation variance. In addition it highlights
+alternative functions for generating TFRs without averaging across
+trials, or by operating on numpy arrays.
 """
 # Authors: Hari Bharadwaj <hari at nmr.mgh.harvard.edu>
 #          Denis Engemann <denis.engemann at gmail.com>
+#          Chris Holdgraf <choldgraf at berkeley.edu>
 #
 # License: BSD (3-clause)
 
 import numpy as np
+from matplotlib import pyplot as plt
 
 from mne import create_info, EpochsArray
-from mne.time_frequency import tfr_multitaper, tfr_stockwell, tfr_morlet
+from mne.baseline import rescale
+from mne.time_frequency import (tfr_multitaper, tfr_stockwell, tfr_morlet,
+                                tfr_array_morlet)
 
 print(__doc__)
 
 ###############################################################################
 # Simulate data
+# -------------
+#
+# We'll simulate data with a known spectro-temporal structure.
 
 sfreq = 1000.0
 ch_names = ['SIM0001', 'SIM0002']
@@ -51,62 +60,137 @@ for k in range(n_epochs):
 epochs = EpochsArray(data=data, info=info, events=events, event_id=event_id,
                      reject=reject)
 
-
 ###############################################################################
-# Consider different parameter possibilities for multitaper convolution
+# Calculate a time-frequency representation (TFR)
+# -----------------------------------------------
+#
+# Below we'll demonstrate the output of several TFR functions in MNE:
+#
+# * :func:`mne.time_frequency.tfr_multitaper`
+# * :func:`mne.time_frequency.tfr_stockwell`
+# * :func:`mne.time_frequency.tfr_morlet`
+#
+# Multitaper transform
+# ====================
+# First we'll use the multitaper method for calculating the TFR.
+# This creates several orthogonal tapering windows in the TFR estimation,
+# which reduces variance. We'll also show some of the parameters that can be
+# tweaked (e.g., ``time_bandwidth``) that will result in different multitaper
+# properties, and thus a different TFR. You can trade time resolution or
+# frequency resolution or both in order to get a reduction in variance.
+
 freqs = np.arange(5., 100., 3.)
+vmin, vmax = -3., 3.  # Define our color limits.
 
-# You can trade time resolution or frequency resolution or both
-# in order to get a reduction in variance
+###############################################################################
+# **(1) Least smoothing (most variance/background fluctuations).**
 
-# (1) Least smoothing (most variance/background fluctuations).
 n_cycles = freqs / 2.
 time_bandwidth = 2.0  # Least possible frequency-smoothing (1 taper)
 power = tfr_multitaper(epochs, freqs=freqs, n_cycles=n_cycles,
                        time_bandwidth=time_bandwidth, return_itc=False)
 # Plot results. Baseline correct based on first 100 ms.
-power.plot([0], baseline=(0., 0.1), mode='mean', vmin=-1., vmax=3.,
+power.plot([0], baseline=(0., 0.1), mode='mean', vmin=vmin, vmax=vmax,
            title='Sim: Least smoothing, most variance')
 
+###############################################################################
+# **(2) Less frequency smoothing, more time smoothing.**
 
-# (2) Less frequency smoothing, more time smoothing.
 n_cycles = freqs  # Increase time-window length to 1 second.
 time_bandwidth = 4.0  # Same frequency-smoothing as (1) 3 tapers.
 power = tfr_multitaper(epochs, freqs=freqs, n_cycles=n_cycles,
                        time_bandwidth=time_bandwidth, return_itc=False)
 # Plot results. Baseline correct based on first 100 ms.
-power.plot([0], baseline=(0., 0.1), mode='mean', vmin=-1., vmax=3.,
+power.plot([0], baseline=(0., 0.1), mode='mean', vmin=vmin, vmax=vmax,
            title='Sim: Less frequency smoothing, more time smoothing')
 
+###############################################################################
+# **(3) Less time smoothing, more frequency smoothing.**
 
-# (3) Less time smoothing, more frequency smoothing.
 n_cycles = freqs / 2.
 time_bandwidth = 8.0  # Same time-smoothing as (1), 7 tapers.
 power = tfr_multitaper(epochs, freqs=freqs, n_cycles=n_cycles,
                        time_bandwidth=time_bandwidth, return_itc=False)
 # Plot results. Baseline correct based on first 100 ms.
-power.plot([0], baseline=(0., 0.1), mode='mean', vmin=-1., vmax=3.,
+power.plot([0], baseline=(0., 0.1), mode='mean', vmin=vmin, vmax=vmax,
            title='Sim: Less time smoothing, more frequency smoothing')
 
-# #############################################################################
+##############################################################################
 # Stockwell (S) transform
-
-# S uses a Gaussian window to balance temporal and spectral resolution
+# =======================
+#
+# Stockwell uses a Gaussian window to balance temporal and spectral resolution.
 # Importantly, frequency bands are phase-normalized, hence strictly comparable
 # with regard to timing, and, the input signal can be recoverd from the
-# transform in a lossless way if we disregard numerical errors.
+# transform in a lossless way if we disregard numerical errors. In this case,
+# we control the spectral / temporal resolution by specifying different widths
+# of the gaussian window using the ``width`` parameter.
 
+fig, axs = plt.subplots(1, 3, figsize=(15, 5), sharey=True)
 fmin, fmax = freqs[[0, -1]]
-for width in (0.7, 3.0):
+for width, ax in zip((0.2, .7, 3.0), axs):
     power = tfr_stockwell(epochs, fmin=fmin, fmax=fmax, width=width)
-    power.plot([0], baseline=(0., 0.1), mode='mean',
-               title='Sim: Using S transform, width '
-                     '= {:0.1f}'.format(width), show=True)
+    power.plot([0], baseline=(0., 0.1), mode='mean', axes=ax, show=False,
+               colorbar=False)
+    ax.set_title('Sim: Using S transform, width = {:0.1f}'.format(width))
+plt.tight_layout()
+
+###############################################################################
+# Morlet Wavelets
+# ===============
+#
+# Finally, show the TFR using morlet wavelets, which are a sinusoidal wave
+# with a gaussian envelope. We can control the balance between spectral and
+# temporal resolution with the ``n_cycles`` parameter, which defines the
+# number of cycles to include in the window.
+
+fig, axs = plt.subplots(1, 3, figsize=(15, 5), sharey=True)
+all_n_cycles = [1, 3, freqs / 2.]
+for n_cycles, ax in zip(all_n_cycles, axs):
+    power = tfr_morlet(epochs, freqs=freqs,
+                       n_cycles=n_cycles, return_itc=False)
+    power.plot([0], baseline=(0., 0.1), mode='mean', vmin=vmin, vmax=vmax,
+               axes=ax, show=False, colorbar=False)
+    n_cycles = 'scaled by freqs' if not isinstance(n_cycles, int) else n_cycles
+    ax.set_title('Sim: Using Morlet wavelet, n_cycles = %s' % n_cycles)
+plt.tight_layout()
 
-# #############################################################################
-# Finally, compare to morlet wavelet
+###############################################################################
+# Calculating a TFR without averaging over epochs
+# -----------------------------------------------
+#
+# It is also possible to calculate a TFR without averaging across trials.
+# We can do this by using ``average=False``. In this case, an instance of
+# :class:`mne.time_frequency.EpochsTFR` is returned.
 
 n_cycles = freqs / 2.
-power = tfr_morlet(epochs, freqs=freqs, n_cycles=n_cycles, return_itc=False)
-power.plot([0], baseline=(0., 0.1), mode='mean', vmin=-1., vmax=3.,
-           title='Sim: Using Morlet wavelet')
+power = tfr_morlet(epochs, freqs=freqs,
+                   n_cycles=n_cycles, return_itc=False, average=False)
+print(type(power))
+avgpower = power.average()
+avgpower.plot([0], baseline=(0., 0.1), mode='mean', vmin=vmin, vmax=vmax,
+              title='Using Morlet wavelets and EpochsTFR', show=False)
+
+###############################################################################
+# Operating on arrays
+# -------------------
+#
+# MNE also has versions of the functions above which operate on numpy arrays
+# instead of MNE objects. They expect inputs of the shape
+# ``(n_epochs, n_channels, n_times)``. They will also return a numpy array
+# of shape ``(n_epochs, n_channels, n_freqs, n_times)``.
+
+power = tfr_array_morlet(epochs.get_data(), sfreq=epochs.info['sfreq'],
+                         freqs=freqs, n_cycles=n_cycles,
+                         output='avg_power')
+# Baseline the output
+rescale(power, epochs.times, (0., 0.1), mode='mean', copy=False)
+fig, ax = plt.subplots()
+mesh = ax.pcolormesh(epochs.times * 1000, freqs, power[0],
+                     cmap='RdBu_r', vmin=vmin, vmax=vmax)
+ax.set_title('TFR calculated on a numpy array')
+ax.set(ylim=freqs[[0, -1]], xlabel='Time (ms)')
+fig.colorbar(mesh)
+plt.tight_layout()
+
+plt.show()
diff --git a/examples/visualization/make_report.py b/examples/visualization/make_report.py
index ce462f2..238a29e 100644
--- a/examples/visualization/make_report.py
+++ b/examples/visualization/make_report.py
@@ -6,7 +6,8 @@ Make an MNE-Report with a Slider
 In this example, MEG evoked data are plotted in an html slider.
 """
 
-# Authors: Teon Brooks <teon.brooks at gmail.com
+# Authors: Teon Brooks <teon.brooks at gmail.com>
+#          Eric Larson <larson.eric.d at gmail.com>
 #
 # License: BSD (3-clause)
 
@@ -16,22 +17,34 @@ from mne import read_evokeds
 from matplotlib import pyplot as plt
 
 
-report = Report()
-path = sample.data_path()
-fname = path + '/MEG/sample/sample_audvis-ave.fif'
+data_path = sample.data_path()
+meg_path = data_path + '/MEG/sample'
+subjects_dir = data_path + '/subjects'
+evoked_fname = meg_path + '/sample_audvis-ave.fif'
+
+###############################################################################
+# Do standard folder parsing (this can take a couple of minutes):
+
+report = Report(image_format='png', subjects_dir=subjects_dir,
+                info_fname=evoked_fname, subject='sample')
+report.parse_folder(meg_path)
+
+###############################################################################
+# Add a custom section with an evoked slider:
 
 # Load the evoked data
-evoked = read_evokeds(fname, condition='Left Auditory',
+evoked = read_evokeds(evoked_fname, condition='Left Auditory',
                       baseline=(None, 0), verbose=False)
 evoked.crop(0, .2)
 times = evoked.times[::4]
 # Create a list of figs for the slider
 figs = list()
-for time in times:
-    figs.append(evoked.plot_topomap(time, vmin=-300, vmax=300,
-                                    res=100, show=False))
+for t in times:
+    figs.append(evoked.plot_topomap(t, vmin=-300, vmax=300, res=100,
+                                    show=False))
     plt.close(figs[-1])
-report.add_slider_to_section(figs, times, 'Evoked Response')
+report.add_slider_to_section(figs, times, 'Evoked Response',
+                             image_format='svg')
 
-# # to save report
+# to save report
 # report.save('foobar.html', True)
diff --git a/examples/visualization/plot_3d_to_2d.py b/examples/visualization/plot_3d_to_2d.py
new file mode 100644
index 0000000..9776430
--- /dev/null
+++ b/examples/visualization/plot_3d_to_2d.py
@@ -0,0 +1,121 @@
+"""
+====================================================
+How to convert 3D electrode positions to a 2D image.
+====================================================
+
+Sometimes we want to convert a 3D representation of electrodes into a 2D
+image. For example, if we are using electrocorticography it is common to
+create scatterplots on top of a brain, with each point representing an
+electrode.
+
+In this example, we'll show two ways of doing this in MNE-Python. First,
+if we have the 3D locations of each electrode then we can use Mayavi to
+take a snapshot of a view of the brain. If we do not have these 3D locations,
+and only have a 2D image of the electrodes on the brain, we can use the
+:class:`mne.viz.ClickableImage` class to choose our own electrode positions
+on the image.
+"""
+# Authors: Christopher Holdgraf <choldgraf at berkeley.edu>
+#
+# License: BSD (3-clause)
+from scipy.io import loadmat
+import numpy as np
+from mayavi import mlab
+from matplotlib import pyplot as plt
+from os import path as op
+
+import mne
+from mne.viz import ClickableImage  # noqa
+from mne.viz import plot_alignment, snapshot_brain_montage
+
+
+print(__doc__)
+
+subjects_dir = mne.datasets.sample.data_path() + '/subjects'
+path_data = mne.datasets.misc.data_path() + '/ecog/sample_ecog.mat'
+
+# We've already clicked and exported
+layout_path = op.join(op.dirname(mne.__file__), 'data', 'image')
+layout_name = 'custom_layout.lout'
+
+###############################################################################
+# Load data
+# ---------
+#
+# First we'll load a sample ECoG dataset which we'll use for generating
+# a 2D snapshot.
+
+mat = loadmat(path_data)
+ch_names = mat['ch_names'].tolist()
+elec = mat['elec']
+dig_ch_pos = dict(zip(ch_names, elec))
+mon = mne.channels.DigMontage(dig_ch_pos=dig_ch_pos)
+info = mne.create_info(ch_names, 1000., 'ecog', montage=mon)
+print('Created %s channel positions' % len(ch_names))
+
+###############################################################################
+# Project 3D electrodes to a 2D snapshot
+# --------------------------------------
+#
+# Because we have the 3D location of each electrode, we can use the
+# :func:`mne.viz.snapshot_brain_montage` function to return a 2D image along
+# with the electrode positions on that image. We use this in conjunction with
+# :func:`mne.viz.plot_alignment`, which visualizes electrode positions.
+
+fig = plot_alignment(info, subject='sample', subjects_dir=subjects_dir,
+                     surfaces=['pial'], meg=False)
+mlab.view(200, 70)
+xy, im = snapshot_brain_montage(fig, mon)
+
+# Convert from a dictionary to array to plot
+xy_pts = np.vstack(xy[ch] for ch in info['ch_names'])
+
+# Define an arbitrary "activity" pattern for viz
+activity = np.linspace(100, 200, xy_pts.shape[0])
+
+# This allows us to use matplotlib to create arbitrary 2d scatterplots
+fig2, ax = plt.subplots(figsize=(10, 10))
+ax.imshow(im)
+ax.scatter(*xy_pts.T, c=activity, s=200, cmap='coolwarm')
+ax.set_axis_off()
+# fig2.savefig('./brain.png', bbox_inches='tight')  # For ClickableImage
+
+###############################################################################
+# Manually creating 2D electrode positions
+# ----------------------------------------
+#
+# If we don't have the 3D electrode positions then we can still create a
+# 2D representation of the electrodes. Assuming that you can see the electrodes
+# on the 2D image, we can use :class:`mne.viz.ClickableImage` to open the image
+# interactively. You can click points on the image and the x/y coordinate will
+# be stored.
+#
+# We'll open an image file, then use ClickableImage to
+# return 2D locations of mouse clicks (or load a file already created).
+# Then, we'll return these xy positions as a layout for use with plotting topo
+# maps.
+
+
+# This code opens the image so you can click on it. Commented out
+# because we've stored the clicks as a layout file already.
+
+# # The click coordinates are stored as a list of tuples
+# im = plt.imread('./brain.png')
+# click = ClickableImage(im)
+# click.plot_clicks()
+
+# # Generate a layout from our clicks and normalize by the image
+# print('Generating and saving layout...')
+# lt = click.to_layout()
+# lt.save(op.join(layout_path, layout_name))  # To save if we want
+
+# # We've already got the layout, load it
+lt = mne.channels.read_layout(layout_name, path=layout_path, scale=False)
+x = lt.pos[:, 0] * float(im.shape[1])
+y = (1 - lt.pos[:, 1]) * float(im.shape[0])  # Flip the y-position
+fig, ax = plt.subplots()
+ax.imshow(im)
+ax.scatter(x, y, s=120, color='r')
+plt.autoscale(tight=True)
+ax.set_axis_off()
+plt.show()
diff --git a/examples/visualization/plot_channel_epochs_image.py b/examples/visualization/plot_channel_epochs_image.py
index 50077ab..646b9e1 100644
--- a/examples/visualization/plot_channel_epochs_image.py
+++ b/examples/visualization/plot_channel_epochs_image.py
@@ -9,7 +9,7 @@ potential / field (ERP/ERF) image.
 2 images are produced. One with a good channel and one with a channel
 that does not see any evoked field.
 
-It is also demonstrated how to reorder the epochs using a 1d spectral
+It is also demonstrated how to reorder the epochs using a 1D spectral
 embedding as described in:
 
 Graph-based variability estimation in single-trial event-related neural
@@ -36,7 +36,7 @@ data_path = sample.data_path()
 # Set parameters
 raw_fname = data_path + '/MEG/sample/sample_audvis_filt-0-40_raw.fif'
 event_fname = data_path + '/MEG/sample/sample_audvis_filt-0-40_raw-eve.fif'
-event_id, tmin, tmax = 1, -0.2, 0.5
+event_id, tmin, tmax = 1, -0.2, 0.4
 
 # Setup for reading the raw data
 raw = io.read_raw_fif(raw_fname)
@@ -70,6 +70,10 @@ def order_func(times, data):
 good_pick = 97  # channel with a clear evoked response
 bad_pick = 98  # channel with no evoked response
 
+# We'll also plot a sample time onset for each trial
+plt_times = np.linspace(0, .2, len(epochs))
+
 plt.close('all')
-mne.viz.plot_epochs_image(epochs, [good_pick, bad_pick], sigma=0.5, vmin=-100,
-                          vmax=250, colorbar=True, order=order_func, show=True)
+mne.viz.plot_epochs_image(epochs, [good_pick, bad_pick], sigma=.5,
+                          order=order_func, vmin=-250, vmax=250,
+                          overlay_times=plt_times, show=True)
diff --git a/examples/visualization/plot_clickable_image.py b/examples/visualization/plot_clickable_image.py
deleted file mode 100644
index 6ead345..0000000
--- a/examples/visualization/plot_clickable_image.py
+++ /dev/null
@@ -1,66 +0,0 @@
-"""
-================================================================
-Demonstration of how to use ClickableImage / generate_2d_layout.
-================================================================
-
-In this example, we open an image file, then use ClickableImage to
-return 2D locations of mouse clicks (or load a file already created).
-Then, we use generate_2d_layout to turn those xy positions into a layout
-for use with plotting topo maps. In this way, you can take arbitrary xy
-positions and turn them into a plottable layout.
-"""
-# Authors: Christopher Holdgraf <choldgraf at berkeley.edu>
-#
-# License: BSD (3-clause)
-from scipy.ndimage import imread
-import numpy as np
-from matplotlib import pyplot as plt
-from os import path as op
-import mne
-from mne.viz import ClickableImage, add_background_image  # noqa
-from mne.channels import generate_2d_layout  # noqa
-
-print(__doc__)
-
-# Set parameters and paths
-plt.rcParams['image.cmap'] = 'gray'
-
-im_path = op.join(op.dirname(mne.__file__), 'data', 'image', 'mni_brain.gif')
-# We've already clicked and exported
-layout_path = op.join(op.dirname(mne.__file__), 'data', 'image')
-layout_name = 'custom_layout.lout'
-
-###############################################################################
-# Load data and click
-im = imread(im_path)
-plt.imshow(im)
-"""
-This code opens the image so you can click on it. Commented out
-because we've stored the clicks as a layout file already.
-
-# The click coordinates are stored as a list of tuples
-click = ClickableImage(im)
-click.plot_clicks()
-coords = click.coords
-
-# Generate a layout from our clicks and normalize by the image
-lt = generate_2d_layout(np.vstack(coords), bg_image=im)
-lt.save(layout_path + layout_name)  # To save if we want
-"""
-# We've already got the layout, load it
-lt = mne.channels.read_layout(layout_name, path=layout_path, scale=False)
-
-# Create some fake data
-nchans = len(lt.pos)
-nepochs = 50
-sr = 1000
-nsec = 5
-events = np.arange(nepochs).reshape([-1, 1])
-events = np.hstack([events, np.zeros([nepochs, 2], dtype=int)])
-data = np.random.randn(nepochs, nchans, sr * nsec)
-info = mne.create_info(nchans, sr, ch_types='eeg')
-epochs = mne.EpochsArray(data, info, events)
-evoked = epochs.average()
-
-# Using the native plot_topo function with the image plotted in the background
-f = evoked.plot_topo(layout=lt, fig_background=im)
diff --git a/examples/visualization/plot_eeg_on_scalp.py b/examples/visualization/plot_eeg_on_scalp.py
new file mode 100644
index 0000000..0f2a56d
--- /dev/null
+++ b/examples/visualization/plot_eeg_on_scalp.py
@@ -0,0 +1,25 @@
+"""
+=================================
+Plotting EEG sensors on the scalp
+=================================
+
+In this example, digitized EEG sensor locations are shown on the scalp.
+"""
+# Author: Eric Larson <larson.eric.d at gmail.com>
+#
+# License: BSD (3-clause)
+
+import mne
+from mne.viz import plot_alignment
+from mayavi import mlab
+
+print(__doc__)
+
+data_path = mne.datasets.sample.data_path()
+subjects_dir = data_path + '/subjects'
+trans = mne.read_trans(data_path + '/MEG/sample/sample_audvis_raw-trans.fif')
+raw = mne.io.read_raw_fif(data_path + '/MEG/sample/sample_audvis_raw.fif')
+fig = plot_alignment(raw.info, trans, subject='sample', dig=False,
+                     eeg=['original', 'projected'], meg=[],
+                     coord_frame='head', subjects_dir=subjects_dir)
+mlab.view(135, 80)
diff --git a/examples/visualization/plot_evoked_topomap.py b/examples/visualization/plot_evoked_topomap.py
index 2db4ad0..83599b0 100644
--- a/examples/visualization/plot_evoked_topomap.py
+++ b/examples/visualization/plot_evoked_topomap.py
@@ -4,7 +4,6 @@ Plotting topographic maps of evoked data
 ========================================
 
 Load evoked data and plot topomaps for selected time points.
-
 """
 # Authors: Christian Brodbeck <christianbrodbeck at nyu.edu>
 #          Tal Linzen <linzen at nyu.edu>
diff --git a/examples/visualization/plot_evoked_whitening.py b/examples/visualization/plot_evoked_whitening.py
index 309c485..f45f42c 100644
--- a/examples/visualization/plot_evoked_whitening.py
+++ b/examples/visualization/plot_evoked_whitening.py
@@ -7,13 +7,13 @@ Evoked data are loaded and then whitened using a given noise covariance
 matrix. It's an excellent quality check to see if baseline signals match
 the assumption of Gaussian white noise from which we expect values around
 0 with less than 2 standard deviations. Covariance estimation and diagnostic
-plots are based on [1].
+plots are based on [1]_.
 
 References
 ----------
-[1] Engemann D. and Gramfort A. (2015) Automated model selection in covariance
-    estimation and spatial whitening of MEG and EEG signals, vol. 108,
-    328-342, NeuroImage.
+.. [1] Engemann D. and Gramfort A. (2015) Automated model selection in
+    covariance estimation and spatial whitening of MEG and EEG signals, vol.
+    108, 328-342, NeuroImage.
 
 """
 # Authors: Alexandre Gramfort <alexandre.gramfort at telecom-paristech.fr>
@@ -37,7 +37,7 @@ raw_fname = data_path + '/MEG/sample/sample_audvis_filt-0-40_raw.fif'
 event_fname = data_path + '/MEG/sample/sample_audvis_filt-0-40_raw-eve.fif'
 
 raw = io.read_raw_fif(raw_fname, preload=True)
-raw.filter(1, 40, method='iir', n_jobs=1)
+raw.filter(1, 40, n_jobs=1, fir_design='firwin')
 raw.info['bads'] += ['MEG 2443']  # bads + 1 more
 events = mne.read_events(event_fname)
 
diff --git a/examples/visualization/plot_meg_sensors.py b/examples/visualization/plot_meg_sensors.py
index 6d7d976..9bf244a 100644
--- a/examples/visualization/plot_meg_sensors.py
+++ b/examples/visualization/plot_meg_sensors.py
@@ -16,8 +16,9 @@ from mayavi import mlab
 
 import mne
 from mne.io import read_raw_fif, read_raw_ctf, read_raw_bti, read_raw_kit
-from mne.datasets import sample, spm_face
-from mne.viz import plot_trans
+from mne.io import read_raw_artemis123
+from mne.datasets import sample, spm_face, testing
+from mne.viz import plot_alignment
 
 print(__doc__)
 
@@ -32,11 +33,23 @@ raws = dict(
                                op.join(bti_path, 'test_config_linux'),
                                op.join(bti_path, 'test_hs_linux')),
     KIT=read_raw_kit(op.join(kit_path, 'test.sqd')),
+    Artemis123=read_raw_artemis123(op.join(
+        testing.data_path(), 'ARTEMIS123',
+        'Artemis_Data_2017-04-14-10h-38m-59s_Phantom_1k_HPI_1s.bin'))
 )
 
 for system, raw in raws.items():
+    meg = ['helmet', 'sensors']
     # We don't have coil definitions for KIT refs, so exclude them
-    ref_meg = False if system == 'KIT' else True
-    fig = plot_trans(raw.info, trans=None, dig=False, eeg_sensors=False,
-                     meg_sensors=True, coord_frame='meg', ref_meg=ref_meg)
-    mlab.title(system)
+    if system != 'KIT':
+        meg.append('ref')
+    fig = plot_alignment(raw.info, trans=None, dig=False, eeg=False,
+                         surfaces=[], meg=meg, coord_frame='meg')
+    text = mlab.title(system)
+    text.x_position = 0.5
+    text.y_position = 0.95
+    text.property.vertical_justification = 'top'
+    text.property.justification = 'center'
+    text.actor.text_scale_mode = 'none'
+    text.property.bold = True
+    mlab.draw(fig)
diff --git a/examples/visualization/plot_parcellation.py b/examples/visualization/plot_parcellation.py
new file mode 100644
index 0000000..1fa93a0
--- /dev/null
+++ b/examples/visualization/plot_parcellation.py
@@ -0,0 +1,47 @@
+# -*- coding: utf-8 -*-
+"""
+============================
+Plot a cortical parcellation
+============================
+
+In this example, we download the HCP-MMP1.0 parcellation [1]_ and show it
+on fsaverage.
+
+.. note:: The HCP-MMP dataset has license terms restricting its use.
+          Of particular relevance:
+
+              "I will acknowledge the use of WU-Minn HCP data and data
+              derived from WU-Minn HCP data when publicly presenting any
+              results or algorithms that benefitted from their use."
+
+References
+----------
+.. [1] Glasser MF et al. (2016) A multi-modal parcellation of human
+       cerebral cortex. Nature 536:171-178.
+"""
+# Author: Eric Larson <larson.eric.d at gmail.com>
+#
+# License: BSD (3-clause)
+
+from surfer import Brain
+
+import mne
+
+subjects_dir = mne.datasets.sample.data_path() + '/subjects'
+mne.datasets.fetch_hcp_mmp_parcellation(subjects_dir=subjects_dir,
+                                        verbose=True)
+labels = mne.read_labels_from_annot(
+    'fsaverage', 'HCPMMP1', 'lh', subjects_dir=subjects_dir)
+
+brain = Brain('fsaverage', 'lh', 'inflated', subjects_dir=subjects_dir,
+              cortex='low_contrast', background='white', size=(800, 600))
+brain.add_annotation('HCPMMP1')
+aud_label = [label for label in labels if label.name == 'L_A1_ROI-lh'][0]
+brain.add_label(aud_label, borders=False)
+
+###############################################################################
+# We can also plot a combined set of labels (23 per hemisphere).
+
+brain = Brain('fsaverage', 'lh', 'inflated', subjects_dir=subjects_dir,
+              cortex='low_contrast', background='white', size=(800, 600))
+brain.add_annotation('HCPMMP1_combined')
diff --git a/examples/visualization/plot_roi_erpimage_by_rt.py b/examples/visualization/plot_roi_erpimage_by_rt.py
new file mode 100644
index 0000000..dd903a6
--- /dev/null
+++ b/examples/visualization/plot_roi_erpimage_by_rt.py
@@ -0,0 +1,72 @@
+"""
+===========================================================
+Plot single trial activity, grouped by ROI and sorted by RT
+===========================================================
+
+This will produce what is sometimes called an event related
+potential / field (ERP/ERF) image.
+
+The EEGLAB example file - containing an experiment with button press responses
+to simple visual stimuli - is read in and response times are calculated.
+ROIs are determined by the channel types (in 10/20 channel notation,
+even channels are right, odd are left, and 'z' are central). The
+median and the Global Field Power within each channel group is calculated,
+and the trials are plotted, sorted by response time.
+"""
+# Authors: Jona Sassenhagen <jona.sassenhagen at gmail.com>
+#
+# License: BSD (3-clause)
+
+import mne
+from mne.datasets import testing
+from mne import Epochs, io, pick_types
+from mne.event import define_target_events
+
+print(__doc__)
+
+###############################################################################
+# Load EEGLAB example data (a small EEG dataset)
+data_path = testing.data_path()
+fname = data_path + "/EEGLAB/test_raw.set"
+montage = data_path + "/EEGLAB/test_chans.locs"
+
+event_id = {"rt": 1, "square": 2}  # must be specified for str events
+eog = {"FPz", "EOG1", "EOG2"}
+raw = io.eeglab.read_raw_eeglab(fname, eog=eog, montage=montage,
+                                event_id=event_id)
+picks = pick_types(raw.info, eeg=True)
+events = mne.find_events(raw)
+
+###############################################################################
+# Create Epochs
+
+# define target events:
+# 1. find response times: distance between "square" and "rt" events
+# 2. extract A. "square" events B. followed by a button press within 700 msec
+tmax = .7
+sfreq = raw.info["sfreq"]
+reference_id, target_id = 2, 1
+new_events, rts = define_target_events(events, reference_id, target_id, sfreq,
+                                       tmin=0., tmax=tmax, new_id=2)
+
+epochs = Epochs(raw, events=new_events, tmax=tmax + .1,
+                event_id={"square": 2}, picks=picks)
+
+###############################################################################
+# Plot
+
+# Parameters for plotting
+order = rts.argsort()  # sorting from fast to slow trials
+
+rois = dict()
+for pick, channel in enumerate(epochs.ch_names):
+    last_char = channel[-1]  # for 10/20, last letter codes the hemisphere
+    roi = ("Midline" if last_char == "z" else
+           ("Left" if int(last_char) % 2 else "Right"))
+    rois[roi] = rois.get(roi, list()) + [pick]
+
+# The actual plots
+for combine_measures in ('gfp', 'median'):
+    epochs.plot_image(group_by=rois, order=order, overlay_times=rts / 1000.,
+                      sigma=1.5, combine=combine_measures,
+                      ts_args=dict(vlines=[0, rts.mean() / 1000.]))
diff --git a/examples/visualization/plot_sensor_noise_level.py b/examples/visualization/plot_sensor_noise_level.py
new file mode 100644
index 0000000..9855631
--- /dev/null
+++ b/examples/visualization/plot_sensor_noise_level.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+"""
+======================================
+Show noise levels from empty room data
+======================================
+
+This shows how to use :meth:`mne.io.Raw.plot_psd` to examine noise levels
+of systems. See [1]_ for an example.
+
+References
+----------
+.. [1] Khan S, Cohen D (2013). Note: Magnetic noise from the inner wall of
+   a magnetically shielded room. Review of Scientific Instruments 84:56101.
+   http://dx.doi.org/10.1063/1.4802845
+"""
+# Author: Eric Larson <larson.eric.d at gmail.com>
+#
+# License: BSD (3-clause)
+
+import os.path as op
+import mne
+
+data_path = mne.datasets.sample.data_path()
+
+raw_erm = mne.io.read_raw_fif(op.join(data_path, 'MEG', 'sample',
+                                      'ernoise_raw.fif'), preload=True)
+
+###############################################################################
+# We can plot the absolute noise levels:
+raw_erm.plot_psd(tmax=10., average=True, dB=False, xscale='log')
diff --git a/examples/visualization/plot_ssp_projs_sensitivity_map.py b/examples/visualization/plot_ssp_projs_sensitivity_map.py
index 1c692e3..2a95515 100644
--- a/examples/visualization/plot_ssp_projs_sensitivity_map.py
+++ b/examples/visualization/plot_ssp_projs_sensitivity_map.py
@@ -13,6 +13,7 @@ similar to the first SSP vector correcting for ECG.
 import matplotlib.pyplot as plt
 
 from mne import read_forward_solution, read_proj, sensitivity_map
+
 from mne.datasets import sample
 
 print(__doc__)
@@ -23,7 +24,8 @@ subjects_dir = data_path + '/subjects'
 fname = data_path + '/MEG/sample/sample_audvis-meg-eeg-oct-6-fwd.fif'
 ecg_fname = data_path + '/MEG/sample/sample_audvis_ecg-proj.fif'
 
-fwd = read_forward_solution(fname, surf_ori=True)
+fwd = read_forward_solution(fname)
+
 projs = read_proj(ecg_fname)
 # take only one projection per channel type
 projs = projs[::2]
diff --git a/examples/visualization/plot_topo_compare_conditions.py b/examples/visualization/plot_topo_compare_conditions.py
index cd94977..5e5c5e5 100644
--- a/examples/visualization/plot_topo_compare_conditions.py
+++ b/examples/visualization/plot_topo_compare_conditions.py
@@ -3,11 +3,9 @@
 Compare evoked responses for different conditions
 =================================================
 
-In this example, an Epochs object for visual and
-auditory responses is created. Both conditions
-are then accessed by their respective names to
-create a sensor layout plot of the related
-evoked responses.
+In this example, an Epochs object for visual and auditory responses is created.
+Both conditions are then accessed by their respective names to create a sensor
+layout plot of the related evoked responses.
 
 """
 
@@ -62,14 +60,9 @@ evokeds = [epochs[name].average() for name in ('left', 'right')]
 ###############################################################################
 # Show topography for two different conditions
 
-colors = 'yellow', 'green'
-title = 'MNE sample data - left vs right (A/V combined)'
+colors = 'blue', 'red'
+title = 'MNE sample data\nleft vs right (A/V combined)'
 
-plot_evoked_topo(evokeds, color=colors, title=title)
-
-conditions = [e.comment for e in evokeds]
-for cond, col, pos in zip(conditions, colors, (0.025, 0.07)):
-    plt.figtext(0.99, pos, cond, color=col, fontsize=12,
-                horizontalalignment='right')
+plot_evoked_topo(evokeds, color=colors, title=title, background_color='w')
 
 plt.show()
diff --git a/examples/visualization/plot_topo_customized.py b/examples/visualization/plot_topo_customized.py
index 8fdc8f6..c2b6d97 100644
--- a/examples/visualization/plot_topo_customized.py
+++ b/examples/visualization/plot_topo_customized.py
@@ -30,7 +30,7 @@ data_path = sample.data_path()
 raw_fname = data_path + '/MEG/sample/sample_audvis_filt-0-40_raw.fif'
 
 raw = io.read_raw_fif(raw_fname, preload=True)
-raw.filter(1, 20)
+raw.filter(1, 20, fir_design='firwin')
 
 picks = mne.pick_types(raw.info, meg=True, exclude=[])
 tmin, tmax = 0, 120  # use the first 120s of data
@@ -51,6 +51,7 @@ def my_callback(ax, ch_idx):
     ax.set_xlabel = 'Frequency (Hz)'
     ax.set_ylabel = 'Power (dB)'
 
+
 for ax, idx in iter_topography(raw.info,
                                fig_facecolor='white',
                                axis_facecolor='white',
diff --git a/examples/visualization/plot_xhemi.py b/examples/visualization/plot_xhemi.py
new file mode 100644
index 0000000..373537d
--- /dev/null
+++ b/examples/visualization/plot_xhemi.py
@@ -0,0 +1,47 @@
+# -*- coding: utf-8 -*-
+"""
+===========================
+Cross-hemisphere comparison
+===========================
+
+This example illustrates how to visualize the difference between activity in
+the left and the right hemisphere. The data from the right hemisphere is
+mapped to the left hemisphere, and then the difference is plotted. For more
+information see :func:`mne.compute_morph_matrix`.
+"""
+# Author: Christian Brodbeck <christianbrodbeck at nyu.edu>
+#
+# License: BSD (3-clause)
+
+import mne
+
+
+data_dir = mne.datasets.sample.data_path()
+subjects_dir = data_dir + '/subjects'
+stc_path = data_dir + '/MEG/sample/sample_audvis-meg-eeg'
+
+stc = mne.read_source_estimate(stc_path, 'sample')
+
+# First, morph the data to fsaverage_sym, for which we have left_right
+# registrations:
+stc = stc.morph('fsaverage_sym', subjects_dir=subjects_dir, smooth=5)
+
+# Compute a morph-matrix mapping the right to the left hemisphere. Use the
+# vertices parameters to determine source and target hemisphere:
+mm = mne.compute_morph_matrix(
+    'fsaverage_sym', 'fsaverage_sym', xhemi=True,  # cross-hemisphere morphing
+    vertices_from=[[], stc.vertices[1]],  # from the right hemisphere
+    vertices_to=[stc.vertices[0], []],  # to the left hemisphere
+    subjects_dir=subjects_dir)
+
+# SourceEstimate on the left hemisphere:
+stc_lh = mne.SourceEstimate(stc.lh_data, [stc.vertices[0], []], stc.tmin,
+                            stc.tstep, stc.subject)
+# SourceEstimate of the right hemisphere, morphed to the left:
+stc_rh_on_lh = mne.SourceEstimate(mm * stc.rh_data, [stc.vertices[0], []],
+                                  stc.tmin, stc.tstep, stc.subject)
+# Since both STCs are now on the same hemisphere we can subtract them:
+diff = stc_lh - stc_rh_on_lh
+
+diff.plot(hemi='lh', subjects_dir=subjects_dir, initial_time=0.07,
+          size=(800, 600))
diff --git a/logo/generate_mne_logos.py b/logo/generate_mne_logos.py
index 39f5ef4..062e7a0 100644
--- a/logo/generate_mne_logos.py
+++ b/logo/generate_mne_logos.py
@@ -51,7 +51,12 @@ Z1 = bivariate_normal(X, Y, 8.0, 7.0, -5.0, 0.9, 1.0)
 Z2 = bivariate_normal(X, Y, 15.0, 2.5, 2.6, -2.5, 2.5)
 Z = Z2 - 0.7 * Z1
 
-# color map: field gradient (yellow-red-transparent-blue-cyan)
+# color map: field gradient (yellow-red-gray-blue-cyan)
+# yrtbc = {
+#     'red': ((0, 1, 1), (0.4, 1, 1), (0.5, 0.5, 0.5), (0.6, 0, 0), (1, 0, 0)),
+#     'blue': ((0, 0, 0), (0.4, 0, 0), (0.5, 0.5, 0.5), (0.6, 1, 1), (1, 1, 1)),  # noqa
+#     'green': ((0, 1, 1), (0.4, 0, 0), (0.5, 0.5, 0.5), (0.6, 0, 0), (1, 1, 1)),  # noqa
+# }
 yrtbc = {'red': ((0.0, 1.0, 1.0), (0.5, 1.0, 0.0), (1.0, 0.0, 0.0)),
          'blue': ((0.0, 0.0, 0.0), (0.5, 0.0, 1.0), (1.0, 1.0, 1.0)),
          'green': ((0.0, 1.0, 1.0), (0.5, 0.0, 0.0), (1.0, 1.0, 1.0)),
@@ -78,6 +83,7 @@ mult = (plot_dims / dims).min()
 mult = [mult, -mult]  # y axis is inverted (origin at top left)
 offset = plot_dims / 2. - center_fudge
 mne_clip = Path(offset + vert * mult, mne_path.codes)
+ax.add_patch(PathPatch(mne_clip, color='w', zorder=0, linewidth=0))
 # apply clipping mask to field gradient and lines
 im.set_clip_path(mne_clip, transform=im.get_transform())
 for coll in cs.collections:
@@ -103,11 +109,8 @@ yy = np.max([tag_clip.vertices.max(0)[-1],
 ax.set_ylim(np.ceil(yy), yl[-1])
 
 # only save actual image extent plus a bit of padding
-extent = Bbox(np.c_[ax.get_xlim(), ax.get_ylim()])
-extent = extent.transformed(ax.transData + fig.dpi_scale_trans.inverted())
 plt.draw()
-plt.savefig(op.join(static_dir, 'mne_logo.png'),
-            bbox_inches=extent.expanded(1.2, 1.))
+plt.savefig(op.join(static_dir, 'mne_logo.png'), transparent=True)
 plt.close()
 
 # 92x22 image
@@ -125,7 +128,9 @@ ax = plt.Axes(fig, [0., 0., 1., 1.])
 ax.set_axis_off()
 fig.add_axes(ax)
 # plot rainbow
-im = plt.imshow(X, cmap=mne_field_grad_cols, aspect='equal')
+im = ax.imshow(X, cmap=mne_field_grad_cols, aspect='equal', zorder=1)
+im = ax.imshow(np.ones_like(X) * 0.5, cmap='Greys', aspect='equal', zorder=0,
+               clim=[0, 1])
 plot_dims = np.r_[np.diff(ax.get_xbound()), np.diff(ax.get_ybound())]
 # MNE text in white
 mne_path = TextPath((0, 0), 'MNE')
@@ -148,9 +153,6 @@ xpad = np.abs(np.diff([xmin, xl[1]])) / 20.
 ypad = np.abs(np.diff([ymax, ymin])) / 20.
 ax.set_xlim(xmin - xpad, xl[1] + xpad)
 ax.set_ylim(ymax + ypad, ymin - ypad)
-extent = Bbox(np.c_[ax.get_xlim(), ax.get_ylim()])
-extent = extent.transformed(ax.transData + fig.dpi_scale_trans.inverted())
 plt.draw()
-plt.savefig(op.join(static_dir, 'mne_logo_small.png'), transparent=True,
-            bbox_inches=extent)
+plt.savefig(op.join(static_dir, 'mne_logo_small.png'), transparent=True)
 plt.close()
diff --git a/mne/__init__.py b/mne/__init__.py
index 92bd7cb..a3e9903 100644
--- a/mne/__init__.py
+++ b/mne/__init__.py
@@ -1,5 +1,4 @@
-"""MNE for MEG and EEG data analysis
-"""
+"""MNE software for MEG and EEG data analysis."""
 
 # PEP0440 compatible formatted version, see:
 # https://www.python.org/dev/peps/pep-0440/
@@ -17,12 +16,12 @@
 # Dev branch marker is: 'X.Y.devN' where N is an integer.
 #
 
-__version__ = '0.13.1'
+__version__ = '0.15.2'
 
 # have to import verbose first since it's needed by many things
 from .utils import (set_log_level, set_log_file, verbose, set_config,
                     get_config, get_config_path, set_cache_dir,
-                    set_memmap_min_size, grand_average, sys_info)
+                    set_memmap_min_size, grand_average, sys_info, open_docs)
 from .io.pick import (pick_types, pick_channels,
                       pick_channels_regexp, pick_channels_forward,
                       pick_types_forward, pick_channels_cov,
@@ -48,7 +47,8 @@ from .forward import (read_forward_solution, apply_forward, apply_forward_raw,
                       convert_forward_solution, make_field_map,
                       make_forward_dipole)
 from .source_estimate import (read_source_estimate, MixedSourceEstimate,
-                              SourceEstimate, VolSourceEstimate, morph_data,
+                              SourceEstimate, VectorSourceEstimate,
+                              VolSourceEstimate, morph_data,
                               morph_data_precomputed, compute_morph_matrix,
                               grade_to_tris, grade_to_vertices,
                               spatial_src_connectivity,
@@ -65,9 +65,11 @@ from .source_space import (read_source_spaces, vertex_to_mni,
                            write_source_spaces, setup_source_space,
                            setup_volume_source_space, SourceSpaces,
                            add_source_space_distances, morph_source_spaces,
-                           get_volume_labels_from_aseg)
+                           get_volume_labels_from_aseg,
+                           get_volume_labels_from_src)
 from .annotations import Annotations
-from .epochs import Epochs, EpochsArray, read_epochs, concatenate_epochs
+from .epochs import (BaseEpochs, Epochs, EpochsArray, read_epochs,
+                     concatenate_epochs)
 from .evoked import Evoked, EvokedArray, read_evokeds, write_evokeds, combine_evoked
 from .label import (read_label, label_sign_flip,
                     write_label, stc_to_label, grow_labels, Label, split_label,
@@ -92,7 +94,9 @@ from . import connectivity
 from . import coreg
 from . import cuda
 from . import datasets
+from . import dipole
 from . import epochs
+from . import event
 from . import externals
 from . import io
 from . import filter
diff --git a/mne/annotations.py b/mne/annotations.py
index 4f4a94b..4cda50f 100644
--- a/mne/annotations.py
+++ b/mne/annotations.py
@@ -7,6 +7,7 @@ import time
 
 import numpy as np
 
+from .utils import _pl
 from .externals.six import string_types
 
 
@@ -14,12 +15,12 @@ class Annotations(object):
     """Annotation object for annotating segments of raw data.
 
     Annotations are added to instance of :class:`mne.io.Raw` as an attribute
-    named ``annotations``. See the example below. To reject bad epochs using
-    annotations, use annotation description starting with 'bad' keyword. The
-    epochs with overlapping bad segments are then rejected automatically by
-    default.
+    named ``annotations``. To reject bad epochs using annotations, use
+    annotation description starting with 'bad' keyword. The epochs with
+    overlapping bad segments are then rejected automatically by default.
 
     To remove epochs with blinks you can do::
+
         >>> eog_events = mne.preprocessing.find_eog_events(raw)  # doctest: +SKIP
         >>> n_blinks = len(eog_events)  # doctest: +SKIP
         >>> onset = eog_events[:, 0] / raw.info['sfreq'] - 0.25  # doctest: +SKIP
@@ -32,7 +33,8 @@ class Annotations(object):
     Parameters
     ----------
     onset : array of float, shape (n_annotations,)
-        Annotation time onsets from the beginning of the recording in seconds.
+        Annotation time onsets relative to the ``orig_time``, the starting time
+        of annotation acquisition.
     duration : array of float, shape (n_annotations,)
         Durations of the annotations in seconds.
     description : array of str, shape (n_annotations,) | str
@@ -53,9 +55,10 @@ class Annotations(object):
     If ``orig_time`` is None, the annotations are synced to the start of the
     data (0 seconds). Otherwise the annotations are synced to sample 0 and
     ``raw.first_samp`` is taken into account the same way as with events.
-    """  # noqa
-    def __init__(self, onset, duration, description, orig_time=None):
+    """  # noqa: E501
 
+    def __init__(self, onset, duration, description,
+                 orig_time=None):  # noqa: D102
         if orig_time is not None:
             if isinstance(orig_time, datetime):
                 orig_time = float(time.mktime(orig_time.timetuple()))
@@ -81,11 +84,58 @@ class Annotations(object):
 
         self.onset = onset
         self.duration = duration
-        self.description = np.array(description)
-
-
-def _combine_annotations(annotations, last_samps, first_samps, sfreq):
-    """Helper for combining a tuple of annotations."""
+        self.description = np.array(description, dtype=str)
+
+    def __repr__(self):
+        """Show the representation."""
+        kinds = sorted(set('%s' % d.split(' ')[0].lower()
+                           for d in self.description))
+        kinds = ['%s (%s)' % (kind, sum(d.lower().startswith(kind)
+                                        for d in self.description))
+                 for kind in kinds]
+        kinds = ', '.join(kinds[:3]) + ('' if len(kinds) <= 3 else '...')
+        kinds = (': ' if len(kinds) > 0 else '') + kinds
+        return ('<Annotations  |  %s segment%s %s >'
+                % (len(self.onset), _pl(len(self.onset)), kinds))
+
+    def __len__(self):
+        """Return the number of annotations."""
+        return len(self.duration)
+
+    def append(self, onset, duration, description):
+        """Add an annotated segment. Operates inplace.
+
+        Parameters
+        ----------
+        onset : float
+            Annotation time onset from the beginning of the recording in
+            seconds.
+        duration : float
+            Duration of the annotation in seconds.
+        description : str
+            Description for the annotation. To reject epochs, use description
+            starting with keyword 'bad'
+        """
+        self.onset = np.append(self.onset, onset)
+        self.duration = np.append(self.duration, duration)
+        self.description = np.append(self.description, description)
+
+    def delete(self, idx):
+        """Remove an annotation. Operates inplace.
+
+        Parameters
+        ----------
+        idx : int | list of int
+            Index of the annotation to remove.
+        """
+        self.onset = np.delete(self.onset, idx)
+        self.duration = np.delete(self.duration, idx)
+        self.description = np.delete(self.description, idx)
+
+
+def _combine_annotations(annotations, last_samps, first_samps, sfreq,
+                         meas_date):
+    """Combine a tuple of annotations."""
     if not any(annotations):
         return None
     elif annotations[1] is None:
@@ -101,9 +151,13 @@ def _combine_annotations(annotations, last_samps, first_samps, sfreq):
         old_description = annotations[0].description
         old_orig_time = annotations[0].orig_time
 
-    extra_samps = len(first_samps) - 1  # Account for sample 0
-    onset = (annotations[1].onset + (sum(last_samps[:-1]) + extra_samps -
-                                     sum(first_samps[:-1])) / sfreq)
+    extra_samps = len(first_samps)  # Account for sample 0
+    if old_orig_time is not None and annotations[1].orig_time is None:
+        meas_date = _handle_meas_date(meas_date)
+        extra_samps += sfreq * (meas_date - old_orig_time) + first_samps[0]
+
+    onset = annotations[1].onset + (np.sum(last_samps) + extra_samps -
+                                    np.sum(first_samps)) / sfreq
 
     onset = np.concatenate([old_onset, onset])
     duration = np.concatenate([old_duration, annotations[1].duration])
@@ -111,10 +165,8 @@ def _combine_annotations(annotations, last_samps, first_samps, sfreq):
     return Annotations(onset, duration, description, old_orig_time)
 
 
-def _onset_to_seconds(raw, onset):
-    """Helper function for adjusting annotation onsets in relation to raw data.
-    """
-    meas_date = raw.info['meas_date']
+def _handle_meas_date(meas_date):
+    """Convert meas_date to seconds."""
     if meas_date is None:
         meas_date = 0
     elif not np.isscalar(meas_date):
@@ -122,11 +174,40 @@ def _onset_to_seconds(raw, onset):
             meas_date = meas_date[0] + meas_date[1] / 1000000.
         else:
             meas_date = meas_date[0]
+    return meas_date
+
+
+def _sync_onset(raw, onset, inverse=False):
+    """Adjust onsets in relation to raw data."""
+    meas_date = _handle_meas_date(raw.info['meas_date'])
     if raw.annotations.orig_time is None:
         orig_time = meas_date
     else:
-        orig_time = (raw.annotations.orig_time -
-                     raw.first_samp / raw.info['sfreq'])
+        offset = -raw._first_time if inverse else raw._first_time
+        orig_time = raw.annotations.orig_time - offset
 
     annot_start = orig_time - meas_date + onset
     return annot_start
+
+
+def _annotations_starts_stops(raw, kinds, name='unknown'):
+    """Get starts and stops from given kinds."""
+    if not isinstance(kinds, (string_types, list, tuple)):
+        raise TypeError('%s must be str, list, or tuple, got %s'
+                        % (type(kinds), name))
+    elif isinstance(kinds, string_types):
+        kinds = [kinds]
+    elif not all(isinstance(kind, string_types) for kind in kinds):
+        raise TypeError('All entries in %s must be str' % (name,))
+    if raw.annotations is None:
+        return np.array([], int), np.array([], int)
+    idxs = [idx for idx, desc in enumerate(raw.annotations.description)
+            if any(desc.upper().startswith(kind.upper())
+                   for kind in kinds)]
+    onsets = raw.annotations.onset[idxs]
+    onsets = _sync_onset(raw, onsets)
+    ends = onsets + raw.annotations.duration[idxs]
+    order = np.argsort(onsets)
+    onsets = raw.time_as_index(onsets[order])
+    ends = raw.time_as_index(ends[order])
+    return onsets, ends
diff --git a/mne/baseline.py b/mne/baseline.py
index aa2c4f9..b975ae7 100644
--- a/mne/baseline.py
+++ b/mne/baseline.py
@@ -1,5 +1,4 @@
-"""Util function to baseline correct data
-"""
+"""Util function to baseline correct data."""
 
 # Authors: Alexandre Gramfort <alexandre.gramfort at telecom-paristech.fr>
 #
@@ -11,7 +10,7 @@ from .utils import logger, verbose
 
 
 def _log_rescale(baseline, mode='mean'):
-    """Helper to log the rescaling method"""
+    """Log the rescaling method."""
     if baseline is not None:
         valid_modes = ('logratio', 'ratio', 'zscore', 'mean', 'percent',
                        'zlogratio')
@@ -20,12 +19,12 @@ def _log_rescale(baseline, mode='mean'):
         msg = 'Applying baseline correction (mode: %s)' % mode
     else:
         msg = 'No baseline correction applied'
-    logger.info(msg)
+    return msg
 
 
 @verbose
 def rescale(data, times, baseline, mode='mean', copy=True, verbose=None):
-    """Rescale aka baseline correct data
+    """Rescale (baseline correct) data.
 
     Parameters
     ----------
@@ -42,28 +41,34 @@ def rescale(data, times, baseline, mode='mean', copy=True, verbose=None):
         and if ``bmax is None`` then ``bmax`` is set to the end of the
         interval. If baseline is ``(None, None)`` the entire time
         interval is used. If baseline is None, no correction is applied.
-    mode : None | 'ratio' | 'zscore' | 'mean' | 'percent' | 'logratio' | 'zlogratio' # noqa
-        Do baseline correction with ratio (power is divided by mean
-        power during baseline) or zscore (power is divided by standard
-        deviation of power during baseline after subtracting the mean,
-        power = [power - mean(power_baseline)] / std(power_baseline)), mean
-        simply subtracts the mean power, percent is the same as applying ratio
-        then mean, logratio is the same as mean but then rendered in log-scale,
-        zlogratio is the same as zscore but data is rendered in log-scale
-        first.
+    mode : 'mean' | 'ratio' | 'logratio' | 'percent' | 'zscore' | 'zlogratio' | None
+        Perform baseline correction by
+
+          - subtracting the mean baseline power ('mean')
+          - dividing by the mean baseline power ('ratio')
+          - dividing by the mean baseline power and taking the log ('logratio')
+          - subtracting the mean baseline power followed by dividing by the
+            mean baseline power ('percent')
+          - subtracting the mean baseline power and dividing by the standard
+            deviation of the baseline power ('zscore')
+          - dividing by the mean baseline power, taking the log, and dividing
+            by the standard deviation of the baseline power ('zlogratio')
+
         If None no baseline correction is applied.
     copy : bool
         Whether to return a new instance or modify in place.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
     data_scaled: array
         Array of same shape as data after rescaling.
-    """
+    """  # noqa: E501
     data = data.copy() if copy else data
-    _log_rescale(baseline, mode)
+    msg = _log_rescale(baseline, mode)
+    logger.info(msg)
     if baseline is None:
         return data
 
@@ -95,18 +100,18 @@ def rescale(data, times, baseline, mode='mean', copy=True, verbose=None):
         mean = 0  # otherwise we get an ugly nan
     if mode == 'mean':
         data -= mean
-    if mode == 'logratio':
+    elif mode == 'ratio':
         data /= mean
-        data = np.log10(data)  # a value of 1 means 10 times bigger
-    if mode == 'ratio':
+    elif mode == 'logratio':
+        data /= mean
+        data = np.log10(data)
+    elif mode == 'percent':
+        data -= mean
         data /= mean
     elif mode == 'zscore':
         std = np.std(data[..., imin:imax], axis=-1)[..., None]
         data -= mean
         data /= std
-    elif mode == 'percent':
-        data -= mean
-        data /= mean
     elif mode == 'zlogratio':
         data /= mean
         data = np.log10(data)
diff --git a/mne/beamformer/__init__.py b/mne/beamformer/__init__.py
index 75ea807..4582fbd 100644
--- a/mne/beamformer/__init__.py
+++ b/mne/beamformer/__init__.py
@@ -1,6 +1,6 @@
-"""Beamformers for source localization
-"""
+"""Beamformers for source localization."""
 
-from ._lcmv import lcmv, lcmv_epochs, lcmv_raw, tf_lcmv
+from ._lcmv import (make_lcmv, apply_lcmv, apply_lcmv_epochs, apply_lcmv_raw,
+                    lcmv, lcmv_epochs, lcmv_raw, tf_lcmv)
 from ._dics import dics, dics_epochs, dics_source_power, tf_dics
 from ._rap_music import rap_music
diff --git a/mne/beamformer/_dics.py b/mne/beamformer/_dics.py
index 8532fc2..1c4a182 100644
--- a/mne/beamformer/_dics.py
+++ b/mne/beamformer/_dics.py
@@ -1,5 +1,4 @@
-"""Dynamic Imaging of Coherent Sources (DICS).
-"""
+"""Dynamic Imaging of Coherent Sources (DICS)."""
 
 # Authors: Roman Goj <roman.goj at gmail.com>
 #
@@ -15,61 +14,29 @@ from ..forward import _subject_from_forward
 from ..minimum_norm.inverse import combine_xyz, _check_reference
 from ..source_estimate import _make_stc
 from ..time_frequency import CrossSpectralDensity, csd_epochs
-from ._lcmv import _prepare_beamformer_input, _setup_picks
+from ._lcmv import _prepare_beamformer_input, _setup_picks, _reg_pinv
 from ..externals import six
 
 
 @verbose
 def _apply_dics(data, info, tmin, forward, noise_csd, data_csd, reg,
-                label=None, picks=None, pick_ori=None, verbose=None):
-    """Dynamic Imaging of Coherent Sources (DICS).
-
-    Calculate the DICS spatial filter based on a given cross-spectral
-    density object and return estimates of source activity based on given data.
-
-    Parameters
-    ----------
-    data : array or list / iterable
-        Sensor space data. If data.ndim == 2 a single observation is assumed
-        and a single stc is returned. If data.ndim == 3 or if data is
-        a list / iterable, a list of stc's is returned.
-    info : dict
-        Measurement info.
-    tmin : float
-        Time of first sample.
-    forward : dict
-        Forward operator.
-    noise_csd : instance of CrossSpectralDensity
-        The noise cross-spectral density.
-    data_csd : instance of CrossSpectralDensity
-        The data cross-spectral density.
-    reg : float
-        The regularization for the cross-spectral density.
-    label : Label | None
-        Restricts the solution to a given label.
-    picks : array-like of int | None
-        Indices (in info) of data channels. If None, MEG and EEG data channels
-        (without bad channels) will be used.
-    pick_ori : None | 'normal'
-        If 'normal', rather than pooling the orientations by taking the norm,
-        only the radial component is kept.
-    verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
-
-    Returns
-    -------
-    stc : SourceEstimate | VolSourceEstimate
-        Source time courses
-    """
-
+                label=None, picks=None, pick_ori=None, real_filter=False,
+                verbose=None):
+    """Dynamic Imaging of Coherent Sources (DICS)."""
     is_free_ori, _, proj, vertno, G =\
         _prepare_beamformer_input(info, forward, label, picks, pick_ori)
 
-    Cm = data_csd.data
+    Cm = data_csd.data.copy()
+
+    # Take real part of Cm to compute real filters
+    if real_filter:
+        Cm = Cm.real
 
-    # Calculating regularized inverse, equivalent to an inverse operation after
-    # regularization: Cm += reg * np.trace(Cm) / len(Cm) * np.eye(len(Cm))
-    Cm_inv = linalg.pinv(Cm, reg)
+    # Tikhonov regularization using reg parameter to control for
+    # trade-off between spatial resolution and noise sensitivity
+    # eq. 25 in Gross and Ioannides, 1999 Phys. Med. Biol. 44 2081
+    Cm_inv, _ = _reg_pinv(Cm, reg)
+    del Cm
 
     # Compute spatial filters
     W = np.dot(G.T, Cm_inv)
@@ -139,19 +106,19 @@ def _apply_dics(data, info, tmin, forward, noise_csd, data_csd, reg,
 
 
 @verbose
-def dics(evoked, forward, noise_csd, data_csd, reg=0.01, label=None,
-         pick_ori=None, verbose=None):
+def dics(evoked, forward, noise_csd, data_csd, reg=0.05, label=None,
+         pick_ori=None, real_filter=False, verbose=None):
     """Dynamic Imaging of Coherent Sources (DICS).
 
-    Compute a Dynamic Imaging of Coherent Sources (DICS) beamformer
+    Compute a Dynamic Imaging of Coherent Sources (DICS) [1]_ beamformer
     on evoked data and return estimates of source time courses.
 
-    NOTE : Fixed orientation forward operators will result in complex time
-    courses in which case absolute values will be  returned. Therefore the
-    orientation will no longer be fixed.
+    .. note:: Fixed orientation forward operators with ``real_filter=False``
+              will result in complex time courses, in which case absolute
+              values will be returned.
 
-    NOTE : This implementation has not been heavily tested so please
-    report any issues or suggestions.
+    .. note:: This implementation has not been heavily tested so please
+              report any issues or suggestions.
 
     Parameters
     ----------
@@ -170,8 +137,12 @@ def dics(evoked, forward, noise_csd, data_csd, reg=0.01, label=None,
     pick_ori : None | 'normal'
         If 'normal', rather than pooling the orientations by taking the norm,
         only the radial component is kept.
+    real_filter : bool
+        If True, take only the real part of the cross-spectral-density matrices
+        to compute real filters as in [2]_. Default is False.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -184,37 +155,47 @@ def dics(evoked, forward, noise_csd, data_csd, reg=0.01, label=None,
 
     Notes
     -----
-    The original reference is:
-    Gross et al. Dynamic imaging of coherent sources: Studying neural
-    interactions in the human brain. PNAS (2001) vol. 98 (2) pp. 694-699
-    """
+    For more information about ``real_filter``, see the
+    `supplemental information <http://www.cell.com/cms/attachment/616681/4982593/mmc1.pdf>`_
+    from [2]_.
+
+    References
+    ----------
+    .. [1] Gross et al. Dynamic imaging of coherent sources: Studying neural
+           interactions in the human brain. PNAS (2001) vol. 98 (2) pp. 694-699
+    .. [2] Hipp JF, Engel AK, Siegel M (2011) Oscillatory Synchronization
+           in Large-Scale Cortical Networks Predicts Perception.
+           Neuron 69:387-396.
+    """  # noqa: E501
     _check_reference(evoked)
     info = evoked.info
     data = evoked.data
     tmin = evoked.times[0]
 
-    picks = _setup_picks(picks=None, info=info, forward=forward)
+    picks = _setup_picks(info=info, forward=forward)
     data = data[picks]
 
     stc = _apply_dics(data, info, tmin, forward, noise_csd, data_csd, reg=reg,
-                      label=label, pick_ori=pick_ori, picks=picks)
+                      label=label, pick_ori=pick_ori, picks=picks,
+                      real_filter=real_filter)
     return six.advance_iterator(stc)
 
 
 @verbose
-def dics_epochs(epochs, forward, noise_csd, data_csd, reg=0.01, label=None,
-                pick_ori=None, return_generator=False, verbose=None):
+def dics_epochs(epochs, forward, noise_csd, data_csd, reg=0.05, label=None,
+                pick_ori=None, return_generator=False, real_filter=False,
+                verbose=None):
     """Dynamic Imaging of Coherent Sources (DICS).
 
     Compute a Dynamic Imaging of Coherent Sources (DICS) beamformer
     on single trial data and return estimates of source time courses.
 
-    NOTE : Fixed orientation forward operators will result in complex time
-    courses in which case absolute values will be  returned. Therefore the
-    orientation will no longer be fixed.
+    .. note:: Fixed orientation forward operators with ``real_filter=False``
+              will result in complex time courses, in which case absolute
+              values will be returned.
 
-    NOTE : This implementation has not been heavily tested so please
-    report any issues or suggestions.
+    .. warning:: This implementation has not been heavily tested so please
+                 report any issues or suggestions.
 
     Parameters
     ----------
@@ -236,8 +217,12 @@ def dics_epochs(epochs, forward, noise_csd, data_csd, reg=0.01, label=None,
     return_generator : bool
         Return a generator object instead of a list. This allows iterating
         over the stcs without having to keep them all in memory.
+    real_filter : bool
+        If True, take only the real part of the cross-spectral-density matrices
+        to compute real filters as in [1]_. Default is False.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -248,22 +233,23 @@ def dics_epochs(epochs, forward, noise_csd, data_csd, reg=0.01, label=None,
     --------
     dics
 
-    Notes
-    -----
-    The original reference is:
-    Gross et al. Dynamic imaging of coherent sources: Studying neural
-    interactions in the human brain. PNAS (2001) vol. 98 (2) pp. 694-699
+    References
+    ----------
+    .. [1] Hipp JF, Engel AK, Siegel M (2011) Oscillatory Synchronization
+           in Large-Scale Cortical Networks Predicts Perception.
+           Neuron 69:387-396.
     """
     _check_reference(epochs)
 
     info = epochs.info
     tmin = epochs.times[0]
 
-    picks = _setup_picks(picks=None, info=info, forward=forward)
+    picks = _setup_picks(info=info, forward=forward)
     data = epochs.get_data()[:, picks, :]
 
     stcs = _apply_dics(data, info, tmin, forward, noise_csd, data_csd, reg=reg,
-                       label=label, pick_ori=pick_ori, picks=picks)
+                       label=label, pick_ori=pick_ori, picks=picks,
+                       real_filter=real_filter)
 
     if not return_generator:
         stcs = list(stcs)
@@ -272,8 +258,9 @@ def dics_epochs(epochs, forward, noise_csd, data_csd, reg=0.01, label=None,
 
 
 @verbose
-def dics_source_power(info, forward, noise_csds, data_csds, reg=0.01,
-                      label=None, pick_ori=None, verbose=None):
+def dics_source_power(info, forward, noise_csds, data_csds, reg=0.05,
+                      label=None, pick_ori=None, real_filter=False,
+                      verbose=None):
     """Dynamic Imaging of Coherent Sources (DICS).
 
     Calculate source power in time and frequency windows specified in the
@@ -302,8 +289,12 @@ def dics_source_power(info, forward, noise_csds, data_csds, reg=0.01,
     pick_ori : None | 'normal'
         If 'normal', rather than pooling the orientations by taking the norm,
         only the radial component is kept.
+    real_filter : bool
+        If True, take only the real part of the cross-spectral-density matrices
+        to compute real filters.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -316,7 +307,6 @@ def dics_source_power(info, forward, noise_csds, data_csds, reg=0.01,
     Gross et al. Dynamic imaging of coherent sources: Studying neural
     interactions in the human brain. PNAS (2001) vol. 98 (2) pp. 694-699
     """
-
     if isinstance(data_csds, CrossSpectralDensity):
         data_csds = [data_csds]
 
@@ -334,16 +324,16 @@ def dics_source_power(info, forward, noise_csds, data_csds, reg=0.01,
 
     frequencies = []
     for data_csd, noise_csd in zip(data_csds, noise_csds):
-        if not np.allclose(data_csd.frequencies, noise_csd.frequencies):
+        if not np.allclose(data_csd.freqs, noise_csd.freqs):
             raise ValueError('Data and noise CSDs should be calculated at '
                              'identical frequencies')
 
         # If CSD is summed over multiple frequencies, take the average
         # frequency
-        if(len(data_csd.frequencies) > 1):
-            frequencies.append(np.mean(data_csd.frequencies))
+        if(len(data_csd.freqs) > 1):
+            frequencies.append(np.mean(data_csd.freqs))
         else:
-            frequencies.append(data_csd.frequencies[0])
+            frequencies.append(data_csd.freqs[0])
     fmin = frequencies[0]
 
     if len(frequencies) > 2:
@@ -359,7 +349,7 @@ def dics_source_power(info, forward, noise_csds, data_csds, reg=0.01,
     else:
         fstep = 1  # dummy value
 
-    picks = _setup_picks(picks=None, info=info, forward=forward)
+    picks = _setup_picks(info=info, forward=forward)
 
     is_free_ori, _, proj, vertno, G =\
         _prepare_beamformer_input(info, forward, label, picks=picks,
@@ -376,12 +366,17 @@ def dics_source_power(info, forward, noise_csds, data_csds, reg=0.01,
             logger.info('    computing DICS spatial filter %d out of %d' %
                         (i + 1, n_csds))
 
-        Cm = data_csd.data
+        Cm = data_csd.data.copy()
+
+        # Take real part of Cm to compute real filters
+        if real_filter:
+            Cm = Cm.real
 
-        # Calculating regularized inverse, equivalent to an inverse operation
-        # after the following regularization:
-        # Cm += reg * np.trace(Cm) / len(Cm) * np.eye(len(Cm))
-        Cm_inv = linalg.pinv(Cm, reg)
+        # Tikhonov regularization using reg parameter to control for
+        # trade-off between spatial resolution and noise sensitivity
+        # eq. 25 in Gross and Ioannides, 1999 Phys. Med. Biol. 44 2081
+        Cm_inv, _ = _reg_pinv(Cm, reg)
+        del Cm
 
         # Compute spatial filters
         W = np.dot(G.T, Cm_inv)
@@ -420,18 +415,18 @@ def dics_source_power(info, forward, noise_csds, data_csds, reg=0.01,
 @verbose
 def tf_dics(epochs, forward, noise_csds, tmin, tmax, tstep, win_lengths,
             freq_bins, subtract_evoked=False, mode='fourier', n_ffts=None,
-            mt_bandwidths=None, mt_adaptive=False, mt_low_bias=True, reg=0.01,
-            label=None, pick_ori=None, verbose=None):
+            mt_bandwidths=None, mt_adaptive=False, mt_low_bias=True, reg=0.05,
+            label=None, pick_ori=None, real_filter=False, verbose=None):
     """5D time-frequency beamforming based on DICS.
 
     Calculate source power in time-frequency windows using a spatial filter
     based on the Dynamic Imaging of Coherent Sources (DICS) beamforming
-    approach. For each time window and frequency bin combination cross-spectral
-    density (CSD) is computed and used to create a beamformer spatial filter
-    with noise CSD used for normalization.
+    approach [1]_. For each time window and frequency bin combination
+    cross-spectral density (CSD) is computed and used to create a beamformer
+    spatial filter with noise CSD used for normalization.
 
-    NOTE : This implementation has not been heavily tested so please
-    report any issues or suggestions.
+    .. warning:: This implementation has not been heavily tested so please
+                 report any issues or suggestions.
 
     Parameters
     ----------
@@ -476,8 +471,12 @@ def tf_dics(epochs, forward, noise_csds, tmin, tmax, tstep, win_lengths,
     pick_ori : None | 'normal'
         If 'normal', rather than pooling the orientations by taking the norm,
         only the radial component is kept.
+    real_filter : bool
+        If True, take only the real part of the part of the
+        cross-spectral-density matrices to compute real filters.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -487,13 +486,14 @@ def tf_dics(epochs, forward, noise_csds, tmin, tmax, tstep, win_lengths,
 
     Notes
     -----
-    The original reference is:
-    Dalal et al. Five-dimensional neuroimaging: Localization of the
-    time-frequency dynamics of cortical activity.
-    NeuroImage (2008) vol. 40 (4) pp. 1686-1700
-
-    NOTE : Dalal et al. used a synthetic aperture magnetometry beamformer (SAM)
+    Dalal et al. [1]_ used a synthetic aperture magnetometry beamformer (SAM)
     in each time-frequency window instead of DICS.
+
+    References
+    ----------
+    .. [1] Dalal et al. Five-dimensional neuroimaging: Localization of the
+           time-frequency dynamics of cortical activity.
+           NeuroImage (2008) vol. 40 (4) pp. 1686-1700
     """
     _check_reference(epochs)
 
@@ -564,21 +564,18 @@ def tf_dics(epochs, forward, noise_csds, tmin, tmax, tstep, win_lengths,
                 win_tmax = win_tmax + 1e-10
 
                 # Calculating data CSD in current time window
-                data_csd = csd_epochs(epochs, mode=mode,
-                                      fmin=freq_bin[0],
-                                      fmax=freq_bin[1], fsum=True,
-                                      tmin=win_tmin, tmax=win_tmax,
-                                      n_fft=n_fft,
-                                      mt_bandwidth=mt_bandwidth,
-                                      mt_low_bias=mt_low_bias)
+                data_csd = csd_epochs(
+                    epochs, mode=mode, fmin=freq_bin[0], fmax=freq_bin[1],
+                    fsum=True, tmin=win_tmin, tmax=win_tmax, n_fft=n_fft,
+                    mt_bandwidth=mt_bandwidth, mt_low_bias=mt_low_bias)
 
                 # Scale data CSD to allow data and noise CSDs to have different
                 # length
                 data_csd.data /= data_csd.n_fft
 
-                stc = dics_source_power(epochs.info, forward, noise_csd,
-                                        data_csd, reg=reg, label=label,
-                                        pick_ori=pick_ori)
+                stc = dics_source_power(
+                    epochs.info, forward, noise_csd, data_csd, reg=reg,
+                    label=label, pick_ori=pick_ori, real_filter=real_filter)
                 sol_single.append(stc.data[:, 0])
 
             # Average over all time windows that contain the current time
diff --git a/mne/beamformer/_lcmv.py b/mne/beamformer/_lcmv.py
index 9a15809..a3d0426 100644
--- a/mne/beamformer/_lcmv.py
+++ b/mne/beamformer/_lcmv.py
@@ -1,10 +1,10 @@
-"""Compute Linearly constrained minimum variance (LCMV) beamformer.
-"""
+"""Compute Linearly constrained minimum variance (LCMV) beamformer."""
 
 # Authors: Alexandre Gramfort <alexandre.gramfort at telecom-paristech.fr>
 #          Roman Goj <roman.goj at gmail.com>
 #
 # License: BSD (3-clause)
+from copy import deepcopy
 
 import numpy as np
 from scipy import linalg
@@ -18,104 +18,255 @@ from ..minimum_norm.inverse import _get_vertno, combine_xyz, _check_reference
 from ..cov import compute_whitener, compute_covariance
 from ..source_estimate import _make_stc, SourceEstimate
 from ..source_space import label_src_vertno_sel
-from ..utils import logger, verbose, warn
+from ..utils import logger, verbose, warn, estimate_rank
 from .. import Epochs
 from ..externals import six
+from ..channels.channels import _contains_ch_type
 
 
-def _setup_picks(picks, info, forward, noise_cov=None):
-    if picks is None:
-        picks = pick_types(info, meg=True, eeg=True, ref_meg=False,
-                           exclude='bads')
+def _deprecate_picks(info, picks):
+    if picks is not None:
+        warn('Specifying picks is deprecated and will be removed in 0.16. '
+             'Specifying the selection of channels in info and setting picks '
+             'to None will remove this warning.',
+             DeprecationWarning)
 
-    ok_ch_names = set([c['ch_name'] for c in forward['info']['chs']])
-    if noise_cov is not None:
-        ok_ch_names.union(set(noise_cov.ch_names))
+        info = pick_info(info, picks)
+    return info
 
-    if noise_cov is not None and set(info['bads']) != set(noise_cov['bads']):
-        logger.info('info["bads"] and noise_cov["bads"] do not match, '
-                    'excluding bad channels from both')
 
-    bads = set(info['bads'])
-    if noise_cov is not None:
-        bads.union(set(noise_cov['bads']))
+def _reg_pinv(x, reg):
+    """Compute a regularized pseudoinverse of a square array."""
+    if reg == 0:
+        covrank = estimate_rank(x, tol='auto', norm=False,
+                                return_singular=False)
+        if covrank < x.shape[0]:
+            warn('Covariance matrix is rank-deficient, but no regularization '
+                 'is done.')
+
+    # This adds it to the diagonal without using np.eye
+    d = reg * np.trace(x) / len(x)
+    x.flat[::x.shape[0] + 1] += d
+    return linalg.pinv(x), d
+
 
-    ok_ch_names -= bads
+def _eig_inv(x, rank):
+    """Compute a pseudoinverse with smallest component set to zero."""
+    U, s, V = linalg.svd(x)
 
-    ch_names = [info['chs'][k]['ch_name'] for k in picks]
-    ch_names = [c for c in ch_names if c in ok_ch_names]
+    # pseudoinverse is computed by setting eigenvalues not included in
+    # signalspace to zero
+    s_inv = np.zeros(s.shape)
+    s_inv[:rank] = 1. / s[:rank]
+
+    x_inv = np.dot(V.T, s_inv[:, np.newaxis] * U.T)
+    return x_inv
+
+
+def _setup_picks(info, forward, data_cov=None, noise_cov=None):
+    """Return good channels common to forward model and covariance matrices."""
+    # get a list of all channel names:
+    fwd_ch_names = forward['info']['ch_names']
+
+    # handle channels from forward model and info:
+    ch_names = _compare_ch_names(info['ch_names'], fwd_ch_names, info['bads'])
+
+    # inform about excluding channels:
+    if (data_cov is not None and set(info['bads']) != set(data_cov['bads']) and
+            (len(set(ch_names).intersection(data_cov['bads'])) > 0)):
+        logger.info('info["bads"] and data_cov["bads"] do not match, '
+                    'excluding bad channels from both.')
+    if (noise_cov is not None and
+            set(info['bads']) != set(noise_cov['bads']) and
+            (len(set(ch_names).intersection(noise_cov['bads'])) > 0)):
+        logger.info('info["bads"] and noise_cov["bads"] do not match, '
+                    'excluding bad channels from both.')
+
+    # handle channels from data cov if data cov is not None
+    # Note: data cov is supposed to be None in tf_lcmv
+    if data_cov is not None:
+        ch_names = _compare_ch_names(ch_names, data_cov.ch_names,
+                                     data_cov['bads'])
+
+    # handle channels from noise cov if noise cov available:
+    if noise_cov is not None:
+        ch_names = _compare_ch_names(ch_names, noise_cov.ch_names,
+                                     noise_cov['bads'])
 
     picks = [info['ch_names'].index(k) for k in ch_names if k in
              info['ch_names']]
     return picks
 
 
+def _compare_ch_names(names1, names2, bads):
+    """Return channel names of common and good channels."""
+    ch_names = [ch for ch in names1 if ch not in bads and ch in names2]
+    return ch_names
+
+
+def _check_one_ch_type(info, picks, noise_cov):
+    """Check number of sensor types and presence of noise covariance matrix."""
+    info_pick = pick_info(info, sel=picks)
+    ch_types =\
+        [_contains_ch_type(info_pick, tt) for tt in ('mag', 'grad', 'eeg')]
+    if sum(ch_types) > 1 and noise_cov is None:
+        raise ValueError('Source reconstruction with several sensor types '
+                         'requires a noise covariance matrix to be '
+                         'able to apply whitening.')
+
+
+def _pick_channels_spatial_filter(ch_names, filters):
+    """Return data channel indices to be used with spatial filter.
+
+    Unlike ``pick_channels``, this respects the order of ch_names.
+    """
+    sel = []
+    # first check for channel discrepancies between filter and data:
+    for ch_name in filters['ch_names']:
+        if ch_name not in ch_names:
+            raise ValueError('The spatial filter was computed with channel %s '
+                             'which is not present in the data. You should '
+                             'compute a new spatial filter restricted to the '
+                             'good data channels.' % ch_name)
+    # then compare list of channels and get selection based on data:
+    for ii, ch_name in enumerate(ch_names):
+        if ch_name in filters['ch_names']:
+            sel.append(ii)
+    return sel
+
+
+def _check_cov_matrix(data_cov):
+    if data_cov is None:
+        raise ValueError('Source reconstruction with beamformers requires '
+                         'a data covariance matrix.')
+
+
 @verbose
-def _apply_lcmv(data, info, tmin, forward, noise_cov, data_cov, reg,
-                label=None, picks=None, pick_ori=None, rank=None,
-                verbose=None):
-    """ LCMV beamformer for evoked data, single epochs, and raw data
+def make_lcmv(info, forward, data_cov, reg=0.05, noise_cov=None, label=None,
+              pick_ori=None, rank=None, weight_norm='unit-noise-gain',
+              reduce_rank=False, verbose=None):
+    """Compute LCMV spatial filter.
 
     Parameters
     ----------
-    data : array or list / iterable
-        Sensor space data. If data.ndim == 2 a single observation is assumed
-        and a single stc is returned. If data.ndim == 3 or if data is
-        a list / iterable, a list of stc's is returned.
     info : dict
-        Measurement info.
-    tmin : float
-        Time of first sample.
+        The measurement info to specify the channels to include.
+        Bad channels in info['bads'] are not used.
     forward : dict
         Forward operator.
-    noise_cov : Covariance
-        The noise covariance.
     data_cov : Covariance
         The data covariance.
     reg : float
         The regularization for the whitened data covariance.
+    noise_cov : Covariance
+        The noise covariance. If provided, whitening will be done. Providing a
+        noise covariance is mandatory if you mix sensor types, e.g.
+        gradiometers with magnetometers or EEG with MEG.
     label : Label
         Restricts the LCMV solution to a given label.
-    picks : array-like of int | None
-        Indices (in info) of data channels. If None, MEG and EEG data channels
-        (without bad channels) will be used.
     pick_ori : None | 'normal' | 'max-power'
         If 'normal', rather than pooling the orientations by taking the norm,
         only the radial component is kept. If 'max-power', the source
         orientation that maximizes output source power is chosen.
+        If None, the solution depends on the forward model: if the orientation
+        is fixed, a scalar beamformer is computed. If the forward model has
+        free orientation, a vector beamformer is computed, combining the output
+        for all source orientations.
     rank : None | int | dict
         Specified rank of the noise covariance matrix. If None, the rank is
         detected automatically. If int, the rank is specified for the MEG
         channels. A dictionary with entries 'eeg' and/or 'meg' can be used
         to specify the rank for each modality.
+    weight_norm : 'unit-noise-gain' | 'nai' | None
+        If 'unit-noise-gain', the unit-noise gain minimum variance beamformer
+        will be computed (Borgiotti-Kaplan beamformer) [2]_,
+        if 'nai', the Neural Activity Index [1]_ will be computed,
+        if None, the unit-gain LCMV beamformer [2]_ will be computed.
+    reduce_rank : bool
+        If True, the rank of the leadfield will be reduced by 1 for each
+        spatial location. Setting reduce_rank to True is typically necessary
+        if you use a single sphere model for MEG.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
-    stc : SourceEstimate | VolSourceEstimate (or list of thereof)
-        Source time courses.
+    filters | dict
+        Beamformer weights.
+
+    Notes
+    -----
+    The original reference is [1]_.
+
+    References
+    ----------
+    .. [1] Van Veen et al. Localization of brain electrical activity via
+           linearly constrained minimum variance spatial filtering.
+           Biomedical Engineering (1997) vol. 44 (9) pp. 867--880
+    .. [2] Sekihara & Nagarajan. Adaptive spatial filters for electromagnetic
+           brain imaging (2008) Springer Science & Business Media
     """
+    picks = _setup_picks(info, forward, data_cov, noise_cov)
+
     is_free_ori, ch_names, proj, vertno, G = \
         _prepare_beamformer_input(info, forward, label, picks, pick_ori)
 
-    # Handle whitening + data covariance
-    whitener, _ = compute_whitener(noise_cov, info, picks, rank=rank)
-
-    # whiten the leadfield
-    G = np.dot(whitener, G)
-
-    # Apply SSPs + whitener to data covariance
     data_cov = pick_channels_cov(data_cov, include=ch_names)
     Cm = data_cov['data']
+
+    # check number of sensor types present in the data
+    _check_one_ch_type(info, picks, noise_cov)
+
+    # apply SSPs
+    is_ssp = False
     if info['projs']:
         Cm = np.dot(proj, np.dot(Cm, proj.T))
-    Cm = np.dot(whitener, np.dot(Cm, whitener.T))
+        is_ssp = True
+
+    if noise_cov is not None:
+        # Handle whitening + data covariance
+        whitener, _ = compute_whitener(noise_cov, info, picks, rank=rank)
+        # whiten the leadfield
+        G = np.dot(whitener, G)
+        # whiten  data covariance
+        Cm = np.dot(whitener, np.dot(Cm, whitener.T))
+    else:
+        whitener = None
+
+    # Tikhonov regularization using reg parameter d to control for
+    # trade-off between spatial resolution and noise sensitivity
+    Cm_inv, d = _reg_pinv(Cm.copy(), reg)
+
+    if weight_norm is not None:
+        # estimate noise level based on covariance matrix, taking the
+        # smallest eigenvalue that is not zero
+        noise, _ = linalg.eigh(Cm)
+        if rank is not None:
+            rank_Cm = rank
+        else:
+            rank_Cm = estimate_rank(Cm, tol='auto', norm=False,
+                                    return_singular=False)
+        noise = noise[len(noise) - rank_Cm]
+
+        # use either noise floor or regularization parameter d
+        noise = max(noise, d)
+
+        # Compute square of Cm_inv used for weight normalization
+        Cm_inv_sq = np.dot(Cm_inv, Cm_inv)
 
-    # Calculating regularized inverse, equivalent to an inverse operation after
-    # the following regularization:
-    # Cm += reg * np.trace(Cm) / len(Cm) * np.eye(len(Cm))
-    Cm_inv = linalg.pinv(Cm, reg)
+    del Cm
+
+    # leadfield rank and optional rank reduction
+    if reduce_rank:
+        if not pick_ori == 'max-power':
+            raise NotImplementedError('The computation of spatial filters '
+                                      'with rank reduction using reduce_rank '
+                                      'parameter is only implemented with '
+                                      'pick_ori=="max-power".')
+        if not isinstance(reduce_rank, bool):
+            raise ValueError('reduce_rank has to be True or False '
+                             ' (got %s).' % reduce_rank)
 
     # Compute spatial filters
     W = np.dot(G.T, Cm_inv)
@@ -124,53 +275,127 @@ def _apply_lcmv(data, info, tmin, forward, noise_cov, data_cov, reg,
     for k in range(n_sources):
         Wk = W[n_orient * k: n_orient * k + n_orient]
         Gk = G[:, n_orient * k: n_orient * k + n_orient]
+        if np.all(Gk == 0.):
+            continue
         Ck = np.dot(Wk, Gk)
 
-        # Find source orientation maximizing output source power
+        # Compute scalar beamformer by finding the source orientation which
+        # maximizes output source power
         if pick_ori == 'max-power':
-            eig_vals, eig_vecs = linalg.eigh(Ck)
+            # weight normalization and orientation selection:
+            if weight_norm is not None and pick_ori == 'max-power':
+                # finding optimal orientation for NAI and unit-noise-gain
+                # based on [2]_, Eq. 4.47
+                tmp = np.dot(Gk.T, np.dot(Cm_inv_sq, Gk))
+
+                if reduce_rank:
+                    # use pseudo inverse computation setting smallest component
+                    # to zero if the leadfield is not full rank
+                    tmp_inv = _eig_inv(tmp, tmp.shape[0] - 1)
+                else:
+                    # use straight inverse with full rank leadfield
+                    try:
+                        tmp_inv = linalg.inv(tmp)
+                    except np.linalg.linalg.LinAlgError:
+                        raise ValueError('Singular matrix detected when '
+                                         'estimating LCMV filters. Consider '
+                                         'reducing the rank of the leadfield '
+                                         'by using reduce_rank=True.')
+
+                eig_vals, eig_vecs = linalg.eig(np.dot(tmp_inv,
+                                                       np.dot(Wk, Gk)))
+
+                if np.iscomplex(eig_vecs).any():
+                    raise ValueError('The eigenspectrum of the leadfield at '
+                                     'this voxel is complex. Consider '
+                                     'reducing the rank of the leadfield by '
+                                     'using reduce_rank=True.')
+
+                idx_max = eig_vals.argmax()
+                max_ori = eig_vecs[:, idx_max]
+                Wk[:] = np.dot(max_ori, Wk)
+                Gk = np.dot(Gk, max_ori)
+
+                # compute spatial filter for NAI or unit-noise-gain
+                tmp = np.dot(Gk.T, np.dot(Cm_inv_sq, Gk))
+                denom = np.sqrt(tmp)
+                Wk /= denom
+                if weight_norm == 'nai':
+                    Wk /= np.sqrt(noise)
+
+                is_free_ori = False
+
+            # no weight-normalization and max-power is not implemented yet:
+            else:
+                raise NotImplementedError('The max-power orientation '
+                                          'selection is not yet implemented '
+                                          'with weight_norm set to None.')
+
+        else:  # do vector beamformer
+            # compute the filters:
+            if is_free_ori:
+                # Free source orientation
+                Wk[:] = np.dot(linalg.pinv(Ck, 0.1), Wk)
+            else:
+                # Fixed source orientation
+                Wk /= Ck
+
+            # handle noise normalization with free/normal source orientation:
+            if weight_norm == 'nai':
+                raise NotImplementedError('Weight normalization with neural '
+                                          'activity index is not implemented '
+                                          'yet with free or fixed '
+                                          'orientation.')
+
+            if weight_norm == 'unit-noise-gain':
+                noise_norm = np.sum(Wk ** 2, axis=1)
+                if is_free_ori:
+                    noise_norm = np.sum(noise_norm)
+                noise_norm = np.sqrt(noise_norm)
+                if noise_norm == 0.:
+                    noise_norm_inv = 0  # avoid division by 0
+                else:
+                    noise_norm_inv = 1. / noise_norm
+                Wk[:] *= noise_norm_inv
 
-            # Choosing the eigenvector associated with the middle eigenvalue.
-            # The middle and not the minimal eigenvalue is used because MEG is
-            # insensitive to one (radial) of the three dipole orientations and
-            # therefore the smallest eigenvalue reflects mostly noise.
-            for i in range(3):
-                if i != eig_vals.argmax() and i != eig_vals.argmin():
-                    idx_middle = i
+    # Pick source orientation maximizing output source power
+    if pick_ori == 'max-power':
+        W = W[0::3]
+    elif pick_ori == 'normal':
+        W = W[2::3]
+        is_free_ori = False
 
-            # TODO: The eigenvector associated with the smallest eigenvalue
-            # should probably be used when using combined EEG and MEG data
-            max_ori = eig_vecs[:, idx_middle]
+    filters = dict(weights=W, data_cov=data_cov, noise_cov=noise_cov,
+                   whitener=whitener, weight_norm=weight_norm,
+                   pick_ori=pick_ori, ch_names=ch_names, proj=proj,
+                   is_ssp=is_ssp, vertices=vertno, is_free_ori=is_free_ori,
+                   nsource=forward['nsource'], src=deepcopy(forward['src']))
 
-            Wk[:] = np.dot(max_ori, Wk)
-            Ck = np.dot(max_ori, np.dot(Ck, max_ori))
-            is_free_ori = False
+    return filters
 
-        if is_free_ori:
-            # Free source orientation
-            Wk[:] = np.dot(linalg.pinv(Ck, 0.1), Wk)
-        else:
-            # Fixed source orientation
-            Wk /= Ck
 
-    # Pick source orientation maximizing output source power
-    if pick_ori == 'max-power':
-        W = W[0::3]
+def _subject_from_filter(filters):
+    """Get subject id from inverse operator."""
+    return filters['src'][0].get('subject_his_id', None)
 
-    # Preparing noise normalization
-    noise_norm = np.sum(W ** 2, axis=1)
-    if is_free_ori:
-        noise_norm = np.sum(np.reshape(noise_norm, (-1, 3)), axis=1)
-    noise_norm = np.sqrt(noise_norm)
 
-    # Pick source orientation normal to cortical surface
-    if pick_ori == 'normal':
-        W = W[2::3]
-        is_free_ori = False
+def _check_proj_match(info, filters):
+    """Check whether SSP projections in data and spatial filter match."""
+    proj_data, _, _ = make_projector(info['projs'],
+                                     filters['ch_names'])
+    if not np.array_equal(proj_data, filters['proj']):
+            raise ValueError('The SSP projections present in the data '
+                             'do not match the projections used when '
+                             'calculating the spatial filter.')
 
-    # Applying noise normalization
-    if not is_free_ori:
-        W /= noise_norm[:, None]
+
+def _apply_lcmv(data, filters, info, tmin, max_ori_out):
+    """Apply LCMV spatial filter to data for source reconstruction."""
+    if max_ori_out == 'abs':
+        warn('max_ori_out and the return of absolute values is deprecated and '
+             'will be removed in 0.16. Set it to "signed" to remove this '
+             'warning, this will return signed time series.',
+             DeprecationWarning)
 
     if isinstance(data, np.ndarray) and data.ndim == 2:
         data = [data]
@@ -178,38 +403,44 @@ def _apply_lcmv(data, info, tmin, forward, noise_cov, data_cov, reg,
     else:
         return_single = False
 
-    subject = _subject_from_forward(forward)
+    W = filters['weights']
+
+    subject = _subject_from_forward(filters)
     for i, M in enumerate(data):
-        if len(M) != len(picks):
+        if len(M) != len(filters['ch_names']):
             raise ValueError('data and picks must have the same length')
 
         if not return_single:
             logger.info("Processing epoch : %d" % (i + 1))
 
-        # SSP and whitening
-        if info['projs']:
-            M = np.dot(proj, M)
-        M = np.dot(whitener, M)
+        if filters['is_ssp']:
+            # check whether data and filter projs match
+            _check_proj_match(info, filters)
+            # apply projection
+            M = np.dot(filters['proj'], M)
 
-        # project to source space using beamformer weights
+        if filters['whitener'] is not None:
+            M = np.dot(filters['whitener'], M)
 
-        if is_free_ori:
+        # project to source space using beamformer weights
+        if filters['is_free_ori']:
             sol = np.dot(W, M)
             logger.info('combining the current components...')
             sol = combine_xyz(sol)
-            sol /= noise_norm[:, None]
+
         else:
             # Linear inverse: do computation here or delayed
-            if M.shape[0] < W.shape[0] and pick_ori != 'max-power':
+            if (M.shape[0] < W.shape[0] and
+                    filters['pick_ori'] != 'max-power'):
                 sol = (W, M)
             else:
                 sol = np.dot(W, M)
-            if pick_ori == 'max-power':
+            if filters['pick_ori'] == 'max-power' and max_ori_out == 'abs':
                 sol = np.abs(sol)
 
         tstep = 1.0 / info['sfreq']
-        yield _make_stc(sol, vertices=vertno, tmin=tmin, tstep=tstep,
-                        subject=subject)
+        yield _make_stc(sol, vertices=filters['vertices'], tmin=tmin,
+                        tstep=tstep, subject=subject)
 
     logger.info('[done]')
 
@@ -220,7 +451,6 @@ def _prepare_beamformer_input(info, forward, label, picks, pick_ori):
     Check input values, prepare channel list and gain matrix. For documentation
     of parameters, please refer to _apply_lcmv.
     """
-
     is_free_ori = forward['source_ori'] == FIFF.FIFFV_MNE_FREE_ORI
 
     if pick_ori in ['normal', 'max-power'] and not is_free_ori:
@@ -228,6 +458,7 @@ def _prepare_beamformer_input(info, forward, label, picks, pick_ori):
                          'when a forward operator with free orientation is '
                          'used.')
     if pick_ori == 'normal' and not forward['surf_ori']:
+        # XXX eventually this could just call convert_forward_solution
         raise ValueError('Normal orientation can only be picked when a '
                          'forward operator oriented in surface coordinates is '
                          'used.')
@@ -235,15 +466,14 @@ def _prepare_beamformer_input(info, forward, label, picks, pick_ori):
         raise ValueError('Normal orientation can only be picked when a '
                          'forward operator with a surface-based source space '
                          'is used.')
-
     # Restrict forward solution to selected channels
-    info_ch_names = [c['ch_name'] for c in info['chs']]
+    info_ch_names = [ch['ch_name'] for ch in info['chs']]
     ch_names = [info_ch_names[k] for k in picks]
     fwd_ch_names = forward['sol']['row_names']
     # Keep channels in forward present in info:
-    fwd_ch_names = [c for c in fwd_ch_names if c in info_ch_names]
+    fwd_ch_names = [ch for ch in fwd_ch_names if ch in info_ch_names]
     forward = pick_channels_forward(forward, fwd_ch_names)
-    picks_forward = [fwd_ch_names.index(c) for c in ch_names]
+    picks_forward = [fwd_ch_names.index(ch) for ch in ch_names]
 
     # Get gain matrix (forward operator)
     if label is not None:
@@ -273,96 +503,283 @@ def _prepare_beamformer_input(info, forward, label, picks, pick_ori):
 
 
 @verbose
-def lcmv(evoked, forward, noise_cov, data_cov, reg=0.01, label=None,
-         pick_ori=None, picks=None, rank=None, verbose=None):
-    """Linearly Constrained Minimum Variance (LCMV) beamformer.
+def apply_lcmv(evoked, filters, max_ori_out='abs', verbose=None):
+    """Apply Linearly Constrained Minimum Variance (LCMV) beamformer weights.
 
-    Compute Linearly Constrained Minimum Variance (LCMV) beamformer
+    Apply Linearly Constrained Minimum Variance (LCMV) beamformer weights
     on evoked data.
 
+    Parameters
+    ----------
+    evoked : Evoked
+        Evoked data to invert.
+    filters : dict
+        LCMV spatial filter (beamformer weights).
+        Filter weights returned from `make_lcmv`.
+    max_ori_out: 'abs' | 'signed'
+        Specify in case of pick_ori='max-power'.
+        If 'abs', the absolute value of the source space time series will be
+        returned,
+        if 'signed', the signed source space time series will be returned.
+        'abs' is deprecated and will be removed in 0.16. Set max_ori_out to
+        'signed' to remove this warning.
+    verbose : bool, str, int, or None
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
+
+    Returns
+    -------
+    stc : SourceEstimate | VolSourceEstimate
+        Source time courses.
+
+    See Also
+    --------
+    apply_lcmv_raw, apply_lcmv_epochs
+    """
+    _check_reference(evoked)
+
+    info = evoked.info
+    data = evoked.data
+    tmin = evoked.times[0]
+
+    sel = _pick_channels_spatial_filter(evoked.ch_names, filters)
+    data = data[sel]
+
+    stc = _apply_lcmv(data=data, filters=filters, info=info,
+                      tmin=tmin, max_ori_out=max_ori_out)
+
+    return six.advance_iterator(stc)
+
+
+ at verbose
+def apply_lcmv_epochs(epochs, filters, max_ori_out='abs',
+                      return_generator=False, verbose=None):
+    """Apply Linearly Constrained Minimum Variance (LCMV) beamformer weights.
+
+    Apply Linearly Constrained Minimum Variance (LCMV) beamformer weights
+    on single trial data.
+
+    Parameters
+    ----------
+    epochs : Epochs
+        Single trial epochs.
+    filters : dict
+        LCMV spatial filter (beamformer weights)
+        Filter weights returned from `make_lcmv`.
+    max_ori_out: 'abs' | 'signed'
+        Specify in case of pick_ori='max-power'.
+        If 'abs', the absolute value of the source space time series will be
+        returned,
+        if 'signed', the signed source space time series will be returned.
+        'abs' is deprecated and will be removed in 0.16. Set max_ori_out to
+        'signed' to remove this warning.
+    return_generator : bool
+         Return a generator object instead of a list. This allows iterating
+         over the stcs without having to keep them all in memory.
+    verbose : bool, str, int, or None
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
+
+
+    Returns
+    -------
+    stc: list | generator of (SourceEstimate | VolSourceEstimate)
+        The source estimates for all epochs.
+
+    See Also
+    --------
+    apply_lcmv_raw, apply_lcmv
+    """
+    _check_reference(epochs)
+
+    info = epochs.info
+    tmin = epochs.times[0]
+
+    sel = _pick_channels_spatial_filter(epochs.ch_names, filters)
+    data = epochs.get_data()[:, sel, :]
+    stcs = _apply_lcmv(data=data, filters=filters, info=info,
+                       tmin=tmin, max_ori_out=max_ori_out)
+
+    if not return_generator:
+        stcs = [s for s in stcs]
+
+    return stcs
+
+
+ at verbose
+def apply_lcmv_raw(raw, filters, start=None, stop=None, max_ori_out='abs',
+                   verbose=None):
+    """Apply Linearly Constrained Minimum Variance (LCMV) beamformer weights.
+
+    Apply Linearly Constrained Minimum Variance (LCMV) beamformer weights
+    on raw data.
+
     NOTE : This implementation has not been heavily tested so please
     report any issue or suggestions.
 
     Parameters
     ----------
+    raw : mne.io.Raw
+        Raw data to invert.
+    filters : dict
+        LCMV spatial filter (beamformer weights).
+        Filter weights returned from `make_lcmv`.
+    start : int
+        Index of first time sample (index not time is seconds).
+    stop : int
+        Index of first time sample not to include (index not time is seconds).
+    max_ori_out: 'abs' | 'signed'
+        Specify in case of pick_ori='max-power'.
+        If 'abs', the absolute value of the source space time series will be
+        returned,
+        if 'signed', the signed source space time series will be returned.
+        'abs' is deprecated and will be removed in 0.16. Set max_ori_out to
+        'signed' to remove this warning.
+    verbose : bool, str, int, or None
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
+
+    Returns
+    -------
+    stc : SourceEstimate | VolSourceEstimate
+        Source time courses.
+
+    See Also
+    --------
+    apply_lcmv_epochs, apply_lcmv
+    """
+    _check_reference(raw)
+
+    info = raw.info
+
+    sel = _pick_channels_spatial_filter(raw.ch_names, filters)
+    data, times = raw[sel, start:stop]
+    tmin = times[0]
+
+    stc = _apply_lcmv(data=data, filters=filters, info=info,
+                      tmin=tmin, max_ori_out=max_ori_out)
+
+    return six.advance_iterator(stc)
+
+
+ at verbose
+def lcmv(evoked, forward, noise_cov=None, data_cov=None, reg=0.05, label=None,
+         pick_ori=None, picks=None, rank=None, weight_norm='unit-noise-gain',
+         max_ori_out='abs', reduce_rank=False, verbose=None):
+    """Linearly Constrained Minimum Variance (LCMV) beamformer.
+
+    Compute Linearly Constrained Minimum Variance (LCMV) beamformer
+    on evoked data.
+
+    Parameters
+    ----------
     evoked : Evoked
-        Evoked data to invert
+        Evoked data to invert.
     forward : dict
-        Forward operator
+        Forward operator.
     noise_cov : Covariance
-        The noise covariance
+        The noise covariance. If provided, whitening will be done. Providing a
+        noise covariance is mandatory if you mix sensor types, e.g.
+        gradiometers with magnetometers or EEG with MEG.
     data_cov : Covariance
-        The data covariance
+        The data covariance.
     reg : float
         The regularization for the whitened data covariance.
     label : Label
-        Restricts the LCMV solution to a given label
+        Restricts the LCMV solution to a given label.
     pick_ori : None | 'normal' | 'max-power'
         If 'normal', rather than pooling the orientations by taking the norm,
         only the radial component is kept. If 'max-power', the source
         orientation that maximizes output source power is chosen.
+        If None, the solution depends on the forward model: if the orientation
+        is fixed, a scalar beamformer is computed. If the forward model has
+        free orientation, a vector beamformer is computed, combining the output
+        for all source orientations.
     picks : array-like of int
         Channel indices to use for beamforming (if None all channels
         are used except bad channels).
+        picks is deprecated and will be removed in 0.16, use pick_channels or
+        pick_types instead.
     rank : None | int | dict
         Specified rank of the noise covariance matrix. If None, the rank is
         detected automatically. If int, the rank is specified for the MEG
         channels. A dictionary with entries 'eeg' and/or 'meg' can be used
         to specify the rank for each modality.
+    weight_norm : 'unit-noise-gain' | 'nai' | None
+        If 'unit-noise-gain', the unit-noise gain minimum variance beamformer
+        will be computed (Borgiotti-Kaplan beamformer) [2]_,
+        if 'nai', the Neural Activity Index [1]_ will be computed,
+        if None, the unit-gain LCMV beamformer [2]_ will be computed.
+    max_ori_out: 'abs' | 'signed'
+        Specify in case of pick_ori='max-power'.
+        If 'abs', the absolute value of the source space time series will be
+        returned,
+        if 'signed', the signed source space time series will be returned.
+        'abs' is deprecated and will be removed in 0.16. Set max_ori_out to
+        'signed' to remove this warning.
+
+        .. versionadded:: 0.15.0
+    reduce_rank : bool
+        If True, the rank of the leadfield will be reduced by 1 for each
+        spatial location. Setting reduce_rank to True is typically necessary
+        if you use a sphere model for MEG as in this case the actual
+        rank is 2 not 3.
+
+        .. versionadded:: 0.15.0
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
     stc : SourceEstimate | VolSourceEstimate
-        Source time courses
+        Source time courses.
 
     See Also
     --------
+    make_lcmv, apply_lcmv
     lcmv_raw, lcmv_epochs
 
     Notes
     -----
-    The original reference is:
-    Van Veen et al. Localization of brain electrical activity via linearly
-    constrained minimum variance spatial filtering.
-    Biomedical Engineering (1997) vol. 44 (9) pp. 867--880
-
-    The reference for finding the max-power orientation is:
-    Sekihara et al. Asymptotic SNR of scalar and vector minimum-variance
-    beamformers for neuromagnetic source reconstruction.
-    Biomedical Engineering (2004) vol. 51 (10) pp. 1726--34
-    """
-    _check_reference(evoked)
+    The original reference is [1]_.
 
+    References
+    ----------
+    .. [1] Van Veen et al. Localization of brain electrical activity via
+           linearly constrained minimum variance spatial filtering.
+           Biomedical Engineering (1997) vol. 44 (9) pp. 867--880
+    .. [2] Sekihara & Nagarajan. Adaptive spatial filters for electromagnetic
+           brain imaging (2008) Springer Science & Business Media
+    """
     info = evoked.info
-    data = evoked.data
-    tmin = evoked.times[0]
+    info = _deprecate_picks(info, picks)
 
-    picks = _setup_picks(picks, info, forward, noise_cov)
+    # check whether data covariance is supplied
+    _check_cov_matrix(data_cov)
 
-    data = data[picks]
+    # construct spatial filter
+    filters = make_lcmv(info=info, forward=forward, data_cov=data_cov,
+                        reg=reg, noise_cov=noise_cov, label=label,
+                        pick_ori=pick_ori, rank=rank, weight_norm=weight_norm,
+                        reduce_rank=reduce_rank)
 
-    stc = _apply_lcmv(
-        data=data, info=info, tmin=tmin, forward=forward, noise_cov=noise_cov,
-        data_cov=data_cov, reg=reg, label=label, picks=picks, rank=rank,
-        pick_ori=pick_ori)
+    # apply spatial filter to evoked data
+    stc = apply_lcmv(evoked=evoked, filters=filters, max_ori_out=max_ori_out)
 
-    return six.advance_iterator(stc)
+    return stc
 
 
 @verbose
-def lcmv_epochs(epochs, forward, noise_cov, data_cov, reg=0.01, label=None,
+def lcmv_epochs(epochs, forward, noise_cov, data_cov, reg=0.05, label=None,
                 pick_ori=None, return_generator=False, picks=None, rank=None,
-                verbose=None):
+                weight_norm='unit-noise-gain', max_ori_out='abs',
+                reduce_rank=False, verbose=None):
     """Linearly Constrained Minimum Variance (LCMV) beamformer.
 
     Compute Linearly Constrained Minimum Variance (LCMV) beamformer
     on single trial data.
 
-    NOTE : This implementation has not been heavily tested so please
-    report any issue or suggestions.
-
     Parameters
     ----------
     epochs : Epochs
@@ -370,7 +787,9 @@ def lcmv_epochs(epochs, forward, noise_cov, data_cov, reg=0.01, label=None,
     forward : dict
         Forward operator.
     noise_cov : Covariance
-        The noise covariance.
+        The noise covariance. If provided, whitening will be done. Providing a
+        noise covariance is mandatory if you mix sensor types, e.g.
+        gradiometers with magnetometers or EEG with MEG.
     data_cov : Covariance
         The data covariance.
     reg : float
@@ -381,69 +800,99 @@ def lcmv_epochs(epochs, forward, noise_cov, data_cov, reg=0.01, label=None,
         If 'normal', rather than pooling the orientations by taking the norm,
         only the radial component is kept. If 'max-power', the source
         orientation that maximizes output source power is chosen.
+        If None, the solution depends on the forward model: if the orientation
+        is fixed, a scalar beamformer is computed. If the forward model has
+        free orientation, a vector beamformer is computed, combining the output
+        for all source orientations.
     return_generator : bool
         Return a generator object instead of a list. This allows iterating
         over the stcs without having to keep them all in memory.
     picks : array-like of int
         Channel indices to use for beamforming (if None all channels
         are used except bad channels).
+        picks is deprecated and will be removed in 0.16, use pick_channels or
+        pick_types instead.
     rank : None | int | dict
         Specified rank of the noise covariance matrix. If None, the rank is
         detected automatically. If int, the rank is specified for the MEG
         channels. A dictionary with entries 'eeg' and/or 'meg' can be used
         to specify the rank for each modality.
+    weight_norm : 'unit-noise-gain' | 'nai' | None
+        If 'unit-noise-gain', the unit-noise gain minimum variance beamformer
+        will be computed (Borgiotti-Kaplan beamformer) [2]_,
+        if 'nai', the Neural Activity Index [1]_ will be computed,
+        if None, the unit-gain LCMV beamformer [2]_ will be computed.
+    max_ori_out: 'abs' | 'signed'
+        Specify in case of pick_ori='max-power'.
+        If 'abs', the absolute value of the source space time series will be
+        returned,
+        if 'signed', the signed source space time series will be returned.
+        'abs' is deprecated and will be removed in 0.16. Set max_ori_out to
+        'signed' to remove this warning.
+
+        .. versionadded:: 0.15.0
+    reduce_rank : bool
+        If True, the rank of the leadfield will be reduced by 1 for each
+        spatial location. Setting reduce_rank to True is typically necessary
+        if you use a sphere model for MEG as in this case the actual
+        rank is 2 not 3.
+
+        .. versionadded:: 0.15.0
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
     stc: list | generator of (SourceEstimate | VolSourceEstimate)
-        The source estimates for all epochs
+        The source estimates for all epochs.
 
     See Also
     --------
+    make_lcmv, apply_lcmv_epochs
     lcmv_raw, lcmv
 
     Notes
     -----
-    The original reference is:
-    Van Veen et al. Localization of brain electrical activity via linearly
-    constrained minimum variance spatial filtering.
-    Biomedical Engineering (1997) vol. 44 (9) pp. 867--880
-
-    The reference for finding the max-power orientation is:
-    Sekihara et al. Asymptotic SNR of scalar and vector minimum-variance
-    beamformers for neuromagnetic source reconstruction.
-    Biomedical Engineering (2004) vol. 51 (10) pp. 1726--34
-    """
-    _check_reference(epochs)
+    The original reference is [1]_.
 
+    References
+    ----------
+    .. [1] Van Veen et al. Localization of brain electrical activity via
+           linearly constrained minimum variance spatial filtering.
+           Biomedical Engineering (1997) vol. 44 (9) pp. 867--880
+    .. [2] Sekihara & Nagarajan. Adaptive spatial filters for electromagnetic
+           brain imaging (2008) Springer Science & Business Media
+    """
     info = epochs.info
-    tmin = epochs.times[0]
+    info = _deprecate_picks(info, picks)
 
-    picks = _setup_picks(picks, info, forward, noise_cov)
+    # check whether data covariance is supplied
+    _check_cov_matrix(data_cov)
 
-    data = epochs.get_data()[:, picks, :]
-    stcs = _apply_lcmv(
-        data=data, info=info, tmin=tmin, forward=forward, noise_cov=noise_cov,
-        data_cov=data_cov, reg=reg, label=label, picks=picks, rank=rank,
-        pick_ori=pick_ori)
+    # construct spatial filter
+    filters = make_lcmv(info=info, forward=forward, data_cov=data_cov,
+                        reg=reg, noise_cov=noise_cov, label=label,
+                        pick_ori=pick_ori, rank=rank, weight_norm=weight_norm,
+                        reduce_rank=reduce_rank)
 
-    if not return_generator:
-        stcs = [s for s in stcs]
+    # apply spatial filter to epochs
+    stcs = apply_lcmv_epochs(epochs=epochs, filters=filters,
+                             max_ori_out=max_ori_out,
+                             return_generator=return_generator)
 
     return stcs
 
 
 @verbose
-def lcmv_raw(raw, forward, noise_cov, data_cov, reg=0.01, label=None,
+def lcmv_raw(raw, forward, noise_cov, data_cov, reg=0.05, label=None,
              start=None, stop=None, picks=None, pick_ori=None, rank=None,
-             verbose=None):
+             weight_norm='unit-noise-gain', max_ori_out='abs',
+             reduce_rank=False, verbose=None):
     """Linearly Constrained Minimum Variance (LCMV) beamformer.
 
     Compute Linearly Constrained Minimum Variance (LCMV) beamformer
     on raw data.
-
     NOTE : This implementation has not been heavily tested so please
     report any issue or suggestions.
 
@@ -454,7 +903,9 @@ def lcmv_raw(raw, forward, noise_cov, data_cov, reg=0.01, label=None,
     forward : dict
         Forward operator.
     noise_cov : Covariance
-        The noise covariance.
+        The noise covariance. If provided, whitening will be done. Providing a
+        noise covariance is mandatory if you mix sensor types, e.g.
+        gradiometers with magnetometers or EEG with MEG.
     data_cov : Covariance
         The data covariance.
     reg : float
@@ -468,111 +919,93 @@ def lcmv_raw(raw, forward, noise_cov, data_cov, reg=0.01, label=None,
     picks : array-like of int
         Channel indices to use for beamforming (if None all channels
         are used except bad channels).
+        picks is deprecated and will be removed in 0.16, use pick_channels or
+        pick_types instead.
     pick_ori : None | 'normal' | 'max-power'
         If 'normal', rather than pooling the orientations by taking the norm,
         only the radial component is kept. If 'max-power', the source
         orientation that maximizes output source power is chosen.
+        If None, the solution depends on the forward model: if the orientation
+        is fixed, a scalar beamformer is computed. If the forward model has
+        free orientation, a vector beamformer is computed, combining the output
+        for all source orientations.
     rank : None | int | dict
         Specified rank of the noise covariance matrix. If None, the rank is
         detected automatically. If int, the rank is specified for the MEG
         channels. A dictionary with entries 'eeg' and/or 'meg' can be used
         to specify the rank for each modality.
+    weight_norm : 'unit-noise-gain' | 'nai' | None
+        If 'unit-noise-gain', the unit-noise gain minimum variance beamformer
+        will be computed (Borgiotti-Kaplan beamformer) [2]_,
+        if 'nai', the Neural Activity Index [1]_ will be computed,
+        if None, the unit-gain LCMV beamformer [2]_ will be computed.
+    max_ori_out: 'abs' | 'signed'
+        Specify in case of pick_ori='max-power'.
+        If 'abs', the absolute value of the source space time series will be
+        returned,
+        if 'signed', the signed source space time series will be returned.
+        'abs' is deprecated and will be removed in 0.16. Set max_ori_out to
+        'signed' to remove this warning.
+    reduce_rank : bool
+        If True, the rank of the leadfield will be reduced by 1 for each
+        spatial location. Setting reduce_rank to True is typically necessary
+        if you use a sphere model for MEG as in this case the actual
+        rank is 2 not 3.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
     stc : SourceEstimate | VolSourceEstimate
-        Source time courses
+        Source time courses.
 
     See Also
     --------
+    make_lcmv, apply_lcmv_raw
     lcmv, lcmv_epochs
 
     Notes
     -----
-    The original reference is:
-    Van Veen et al. Localization of brain electrical activity via linearly
-    constrained minimum variance spatial filtering.
-    Biomedical Engineering (1997) vol. 44 (9) pp. 867--880
-
-    The reference for finding the max-power orientation is:
-    Sekihara et al. Asymptotic SNR of scalar and vector minimum-variance
-    beamformers for neuromagnetic source reconstruction.
-    Biomedical Engineering (2004) vol. 51 (10) pp. 1726--34
-    """
-    _check_reference(raw)
+    The original reference is [1]_.
 
+    References
+    ----------
+    .. [1] Van Veen et al. Localization of brain electrical activity via
+           linearly constrained minimum variance spatial filtering.
+           Biomedical Engineering (1997) vol. 44 (9) pp. 867--880
+    .. [2] Sekihara & Nagarajan. Adaptive spatial filters for electromagnetic
+           brain imaging (2008) Springer Science & Business Media
+    """
     info = raw.info
+    info = _deprecate_picks(info, picks)
 
-    picks = _setup_picks(picks, info, forward, noise_cov)
+    # check whether data covariance is supplied
+    _check_cov_matrix(data_cov)
 
-    data, times = raw[picks, start:stop]
-    tmin = times[0]
+    # construct spatial filter
+    filters = make_lcmv(info=info, forward=forward, data_cov=data_cov,
+                        reg=reg, noise_cov=noise_cov, label=label,
+                        pick_ori=pick_ori, rank=rank, weight_norm=weight_norm,
+                        reduce_rank=reduce_rank)
 
-    stc = _apply_lcmv(
-        data=data, info=info, tmin=tmin, forward=forward, noise_cov=noise_cov,
-        data_cov=data_cov, reg=reg, label=label, picks=picks, rank=rank,
-        pick_ori=pick_ori)
+    # apply spatial filter to epochs
+    stc = apply_lcmv_raw(raw=raw, filters=filters, start=start, stop=stop,
+                         max_ori_out=max_ori_out)
 
-    return six.advance_iterator(stc)
+    return stc
 
 
 @verbose
-def _lcmv_source_power(info, forward, noise_cov, data_cov, reg=0.01,
-                       label=None, picks=None, pick_ori=None,
-                       rank=None, verbose=None):
-    """Linearly Constrained Minimum Variance (LCMV) beamformer.
+def _lcmv_source_power(info, forward, noise_cov, data_cov, reg=0.05,
+                       label=None, picks=None, pick_ori=None, rank=None,
+                       weight_norm=None, verbose=None):
+    """Linearly Constrained Minimum Variance (LCMV) beamformer."""
+    if weight_norm not in [None, 'unit-noise-gain']:
+        raise ValueError('Unrecognized weight normalization option in '
+                         'weight_norm, available choices are None and '
+                         '"unit-noise-gain", got "%s".' % weight_norm)
 
-    Calculate source power in a time window based on the provided data
-    covariance. Noise covariance is used to whiten the data covariance making
-    the output equivalent to the neural activity index as defined by
-    Van Veen et al. 1997.
-
-    NOTE : This implementation has not been heavily tested so please
-    report any issues or suggestions.
-
-    Parameters
-    ----------
-    info : dict
-        Measurement info, e.g. epochs.info.
-    forward : dict
-        Forward operator.
-    noise_cov : Covariance
-        The noise covariance.
-    data_cov : Covariance
-        The data covariance.
-    reg : float
-        The regularization for the whitened data covariance.
-    label : Label | None
-        Restricts the solution to a given label.
-    picks : array-like of int | None
-        Indices (in info) of data channels. If None, MEG and EEG data channels
-        (without bad channels) will be used.
-    pick_ori : None | 'normal'
-        If 'normal', rather than pooling the orientations by taking the norm,
-        only the radial component is kept.
-    rank : None | int | dict
-        Specified rank of the noise covariance matrix. If None, the rank is
-        detected automatically. If int, the rank is specified for the MEG
-        channels. A dictionary with entries 'eeg' and/or 'meg' can be used
-        to specify the rank for each modality.
-    verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
-
-    Returns
-    -------
-    stc : SourceEstimate
-        Source power with a single time point representing the entire time
-        window for which data covariance was calculated.
-
-    Notes
-    -----
-    The original reference is:
-    Van Veen et al. Localization of brain electrical activity via linearly
-    constrained minimum variance spatial filtering.
-    Biomedical Engineering (1997) vol. 44 (9) pp. 867--880
-    """
     if picks is None:
         picks = pick_types(info, meg=True, eeg=True, ref_meg=False,
                            exclude='bads')
@@ -585,22 +1018,26 @@ def _lcmv_source_power(info, forward, noise_cov, data_cov, reg=0.01,
     info = pick_info(
         info, [info['ch_names'].index(k) for k in ch_names
                if k in info['ch_names']])
-    whitener, _ = compute_whitener(noise_cov, info, picks, rank=rank)
 
-    # whiten the leadfield
-    G = np.dot(whitener, G)
+    if noise_cov is not None:
+        whitener, _ = compute_whitener(noise_cov, info, picks, rank=rank)
+
+        # whiten the leadfield
+        G = np.dot(whitener, G)
 
     # Apply SSPs + whitener to data covariance
     data_cov = pick_channels_cov(data_cov, include=ch_names)
     Cm = data_cov['data']
     if info['projs']:
         Cm = np.dot(proj, np.dot(Cm, proj.T))
-    Cm = np.dot(whitener, np.dot(Cm, whitener.T))
 
-    # Calculating regularized inverse, equivalent to an inverse operation after
-    # the following regularization:
-    # Cm += reg * np.trace(Cm) / len(Cm) * np.eye(len(Cm))
-    Cm_inv = linalg.pinv(Cm, reg)
+    if noise_cov is not None:
+        Cm = np.dot(whitener, np.dot(Cm, whitener.T))
+
+    # Tikhonov regularization using reg parameter to control for
+    # trade-off between spatial resolution and noise sensitivity
+    # This modifies Cm inplace, regularizing it
+    Cm_inv, d = _reg_pinv(Cm, reg)
 
     # Compute spatial filters
     W = np.dot(G.T, Cm_inv)
@@ -619,13 +1056,15 @@ def _lcmv_source_power(info, forward, noise_cov, data_cov, reg=0.01,
             # Fixed source orientation
             Wk /= Ck
 
-        # Noise normalization
-        noise_norm = np.dot(Wk, Wk.T)
-        noise_norm = noise_norm.trace()
+        if weight_norm == 'unit-noise-gain':
+            # Noise normalization
+            noise_norm = np.dot(Wk, Wk.T)
+            noise_norm = noise_norm.trace()
 
         # Calculating source power
         sp_temp = np.dot(np.dot(Wk, Cm), Wk.T)
-        sp_temp /= max(noise_norm, 1e-40)  # Avoid division by 0
+        if weight_norm == 'unit-noise-gain':
+            sp_temp /= max(noise_norm, 1e-40)  # Avoid division by 0
 
         if pick_ori == 'normal':
             source_power[k, 0] = sp_temp[2, 2]
@@ -641,27 +1080,34 @@ def _lcmv_source_power(info, forward, noise_cov, data_cov, reg=0.01,
 
 @verbose
 def tf_lcmv(epochs, forward, noise_covs, tmin, tmax, tstep, win_lengths,
-            freq_bins, subtract_evoked=False, reg=0.01, label=None,
-            pick_ori=None, n_jobs=1, picks=None, rank=None, verbose=None):
+            freq_bins, subtract_evoked=False, reg=0.05, label=None,
+            pick_ori=None, n_jobs=1, picks=None, rank=None,
+            weight_norm='unit-noise-gain', raw=None, verbose=None):
     """5D time-frequency beamforming based on LCMV.
 
     Calculate source power in time-frequency windows using a spatial filter
     based on the Linearly Constrained Minimum Variance (LCMV) beamforming
-    approach. Band-pass filtered epochs are divided into time windows from
-    which covariance is computed and used to create a beamformer spatial
-    filter.
+    approach [1]_. Band-pass filtered epochs are divided into time windows
+    from which covariance is computed and used to create a beamformer
+    spatial filter.
 
-    NOTE : This implementation has not been heavily tested so please
-    report any issues or suggestions.
+    .. note:: This implementation has not been heavily tested so please
+              report any issues or suggestions.
 
     Parameters
     ----------
     epochs : Epochs
-        Single trial epochs.
+        Single trial epochs. It is recommended to pass epochs that have
+        been constructed with ``preload=False`` (i.e., not preloaded or
+        read from disk) so that the parameter ``raw=None`` can be used
+        below, as this ensures the correct :class:`mne.io.Raw` instance is
+        used for band-pass filtering.
     forward : dict
         Forward operator.
-    noise_covs : list of instances of Covariance
-        Noise covariance for each frequency bin.
+    noise_covs : list of instances of Covariance | None
+        Noise covariance for each frequency bin. If provided, whitening will be
+        done. Providing noise covariances is mandatory if you mix sensor types,
+        e.g., gradiometers with magnetometers or EEG with MEG.
     tmin : float
         Minimum time instant to consider.
     tmax : float
@@ -684,19 +1130,34 @@ def tf_lcmv(epochs, forward, noise_covs, tmin, tmax, tstep, win_lengths,
     pick_ori : None | 'normal'
         If 'normal', rather than pooling the orientations by taking the norm,
         only the radial component is kept.
+        If None, the solution depends on the forward model: if the orientation
+        is fixed, a scalar beamformer is computed. If the forward model has
+        free orientation, a vector beamformer is computed, combining the output
+        for all source orientations.
     n_jobs : int | str
         Number of jobs to run in parallel. Can be 'cuda' if scikits.cuda
         is installed properly and CUDA is initialized.
     picks : array-like of int
         Channel indices to use for beamforming (if None all channels
         are used except bad channels).
+        picks is deprecated and will be removed in 0.16, use pick_channels
+        or pick_types instead.
     rank : None | int | dict
         Specified rank of the noise covariance matrix. If None, the rank is
         detected automatically. If int, the rank is specified for the MEG
         channels. A dictionary with entries 'eeg' and/or 'meg' can be used
         to specify the rank for each modality.
+    weight_norm : 'unit-noise-gain' | None
+        If 'unit-noise-gain', the unit-noise gain minimum variance beamformer
+        will be computed (Borgiotti-Kaplan beamformer) [2]_,
+        if None, the unit-gain LCMV beamformer [2]_ will be computed.
+    raw : instance of Raw | None
+        The raw instance used to construct the epochs.
+        Must be provided unless epochs are constructed with
+        ``preload=False``.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -704,19 +1165,23 @@ def tf_lcmv(epochs, forward, noise_covs, tmin, tmax, tstep, win_lengths,
         Source power at each time window. One SourceEstimate object is returned
         for each frequency bin.
 
-    Notes
-    -----
-    The original reference is:
-    Dalal et al. Five-dimensional neuroimaging: Localization of the
-    time-frequency dynamics of cortical activity.
-    NeuroImage (2008) vol. 40 (4) pp. 1686-1700
+    References
+    ----------
+    .. [1] Dalal et al. Five-dimensional neuroimaging: Localization of the
+           time-frequency dynamics of cortical activity.
+           NeuroImage (2008) vol. 40 (4) pp. 1686-1700
+    .. [2] Sekihara & Nagarajan. Adaptive spatial filters for electromagnetic
+           brain imaging (2008) Springer Science & Business Media
     """
+    info = epochs.info
+    info = _deprecate_picks(info, picks)
+
     _check_reference(epochs)
 
     if pick_ori not in [None, 'normal']:
         raise ValueError('Unrecognized orientation option in pick_ori, '
                          'available choices are None and normal')
-    if len(noise_covs) != len(freq_bins):
+    if noise_covs is not None and len(noise_covs) != len(freq_bins):
         raise ValueError('One noise covariance object expected per frequency '
                          'bin')
     if len(win_lengths) != len(freq_bins):
@@ -726,15 +1191,23 @@ def tf_lcmv(epochs, forward, noise_covs, tmin, tmax, tstep, win_lengths,
                          'window lengths')
 
     # Extract raw object from the epochs object
-    raw = epochs._raw
+    raw = epochs._raw if raw is None else raw
     if raw is None:
         raise ValueError('The provided epochs object does not contain the '
                          'underlying raw object. Please use preload=False '
-                         'when constructing the epochs object')
+                         'when constructing the epochs object or pass the '
+                         'underlying raw instance to this function')
 
-    picks = _setup_picks(picks, epochs.info, forward, noise_covs[0])
+    if noise_covs is None:
+        picks = _setup_picks(info, forward, data_cov=None)
+    else:
+        picks = _setup_picks(info, forward, data_cov=None,
+                             noise_cov=noise_covs[0])
     ch_names = [epochs.ch_names[k] for k in picks]
 
+    # check number of sensor types present in the data
+    _check_one_ch_type(info, picks, noise_covs)
+
     # Use picks from epochs for picking channels in the raw object
     raw_picks = [raw.ch_names.index(c) for c in ch_names]
 
@@ -744,6 +1217,10 @@ def tf_lcmv(epochs, forward, noise_covs, tmin, tmax, tstep, win_lengths,
     # Multiplying by 1e3 to avoid numerical issues, e.g. 0.3 // 0.05 == 5
     n_time_steps = int(((tmax - tmin) * 1e3) // (tstep * 1e3))
 
+    # create a list to iterate over if no noise covariances are given
+    if noise_covs is None:
+        noise_covs = [None] * len(win_lengths)
+
     sol_final = []
     for (l_freq, h_freq), win_length, noise_cov in \
             zip(freq_bins, win_lengths, noise_covs):
@@ -796,7 +1273,9 @@ def tf_lcmv(epochs, forward, noise_covs, tmin, tmax, tstep, win_lengths,
 
                 stc = _lcmv_source_power(epochs_band.info, forward, noise_cov,
                                          data_cov, reg=reg, label=label,
-                                         pick_ori=pick_ori, verbose=verbose)
+                                         pick_ori=pick_ori,
+                                         weight_norm=weight_norm,
+                                         verbose=verbose)
                 sol_single.append(stc.data[:, 0])
 
             # Average over all time windows that contain the current time
diff --git a/mne/beamformer/_rap_music.py b/mne/beamformer/_rap_music.py
index 5e96da7..7d3315c 100644
--- a/mne/beamformer/_rap_music.py
+++ b/mne/beamformer/_rap_music.py
@@ -1,6 +1,4 @@
-"""Compute a Recursively Applied and Projected MUltiple
-Signal Classification (RAP-MUSIC).
-"""
+"""Compute a Recursively Applied and Projected MUltiple Signal Classification (RAP-MUSIC)."""  # noqa
 
 # Authors: Yousra Bekhti <yousra.bekhti at gmail.com>
 #          Alexandre Gramfort <alexandre.gramfort at telecom-paristech.fr>
@@ -14,12 +12,12 @@ from ..io.pick import pick_channels_evoked
 from ..cov import compute_whitener
 from ..utils import logger, verbose
 from ..dipole import Dipole
-from ._lcmv import _prepare_beamformer_input, _setup_picks
+from ._lcmv import _prepare_beamformer_input, _setup_picks, _deprecate_picks
 
 
 def _apply_rap_music(data, info, times, forward, noise_cov, n_dipoles=2,
-                     picks=None, return_explained_data=False):
-    """RAP-MUSIC for evoked data
+                     picks=None):
+    """RAP-MUSIC for evoked data.
 
     Parameters
     ----------
@@ -38,8 +36,6 @@ def _apply_rap_music(data, info, times, forward, noise_cov, n_dipoles=2,
     picks : array-like of int | None
         Indices (in info) of data channels. If None, MEG and EEG data channels
         (without bad channels) will be used.
-    return_explained_data : bool
-        If True, the explained data is returned as an array.
 
     Returns
     -------
@@ -50,7 +46,6 @@ def _apply_rap_music(data, info, times, forward, noise_cov, n_dipoles=2,
         selected active dipoles and their estimated orientation.
         Computed only if return_explained_data is True.
     """
-
     is_free_ori, ch_names, proj, vertno, G = _prepare_beamformer_input(
         info, forward, label=None, picks=picks, pick_ori=None)
 
@@ -106,13 +101,12 @@ def _apply_rap_music(data, info, times, forward, noise_cov, n_dipoles=2,
 
         A[:, k] = Ak.ravel()
 
-        if return_explained_data:
-            gain_k = gain[:, idx_k]
-            if n_orient == 3:
-                gain_k = np.dot(gain_k,
-                                np.dot(forward['source_nn'][idx_k],
-                                       source_ori))
-            gain_dip[:, k] = gain_k.ravel()
+        gain_k = gain[:, idx_k]
+        if n_orient == 3:
+            gain_k = np.dot(gain_k,
+                            np.dot(forward['source_nn'][idx_k],
+                                   source_ori))
+        gain_dip[:, k] = gain_k.ravel()
 
         oris[k] = source_ori
         poss[k] = source_pos
@@ -127,18 +121,15 @@ def _apply_rap_music(data, info, times, forward, noise_cov, n_dipoles=2,
 
     sol = linalg.lstsq(A, data)[0]
 
-    gof, explained_data = [], None
-    if return_explained_data:
-        explained_data = np.dot(gain_dip, sol)
-        gof = (linalg.norm(np.dot(whitener, explained_data)) /
-               linalg.norm(data))
-
+    explained_data = np.dot(gain_dip, sol)
+    residual = data - np.dot(whitener, explained_data)
+    gof = 1. - np.sum(residual ** 2, axis=0) / np.sum(data ** 2, axis=0)
     return _make_dipoles(times, poss,
                          oris, sol, gof), explained_data
 
 
 def _make_dipoles(times, poss, oris, sol, gof):
-    """Instanciates a list of Dipoles
+    """Instantiate a list of Dipoles.
 
     Parameters
     ----------
@@ -159,33 +150,37 @@ def _make_dipoles(times, poss, oris, sol, gof):
     dipoles : list
         The list of Dipole instances.
     """
-    amplitude = sol * 1e9
     oris = np.array(oris)
 
     dipoles = []
     for i_dip in range(poss.shape[0]):
         i_pos = poss[i_dip][np.newaxis, :].repeat(len(times), axis=0)
         i_ori = oris[i_dip][np.newaxis, :].repeat(len(times), axis=0)
-        dipoles.append(Dipole(times, i_pos, amplitude[i_dip],
-                              i_ori, gof))
+        dipoles.append(Dipole(times, i_pos, sol[i_dip], i_ori, gof))
 
     return dipoles
 
 
 def _compute_subcorr(G, phi_sig):
-    """ Compute the subspace correlation
-    """
+    """Compute the subspace correlation."""
     Ug, Sg, Vg = linalg.svd(G, full_matrices=False)
+    # Now we look at the actual rank of the forward fields
+    # in G and handle the fact that it might be rank defficient
+    # eg. when using MEG and a sphere model for which the
+    # radial component will be truly 0.
+    rank = np.sum(Sg > (Sg[0] * 1e-6))
+    if rank == 0:
+        return 0, np.zeros(len(G))
+    rank = max(rank, 2)  # rank cannot be 1
+    Ug, Sg, Vg = Ug[:, :rank], Sg[:rank], Vg[:rank]
     tmp = np.dot(Ug.T.conjugate(), phi_sig)
-    Uc, Sc, Vc = linalg.svd(tmp, full_matrices=False)
-    X = np.dot(np.dot(Vg.T, np.diag(1. / Sg)), Uc)  # subcorr
-    return Sc[0], X[:, 0] / linalg.norm(X[:, 0])
+    Uc, Sc, _ = linalg.svd(tmp, full_matrices=False)
+    X = np.dot(Vg.T / Sg[None, :], Uc[:, 0])  # subcorr
+    return Sc[0], X / linalg.norm(X)
 
 
 def _compute_proj(A):
-    """ Compute the orthogonal projection operation for
-    a manifold vector A.
-    """
+    """Compute the orthogonal projection operation for a manifold vector A."""
     U, _, _ = linalg.svd(A, full_matrices=False)
     return np.identity(A.shape[0]) - np.dot(U, U.T.conjugate())
 
@@ -198,6 +193,9 @@ def rap_music(evoked, forward, noise_cov, n_dipoles=5, return_residual=False,
     Compute Recursively Applied and Projected MUltiple SIgnal Classification
     (RAP-MUSIC) on evoked data.
 
+    .. note:: The goodness of fit (GOF) of all the returned dipoles is the
+              same and corresponds to the GOF of the full set of dipoles.
+
     Parameters
     ----------
     evoked : instance of Evoked
@@ -213,8 +211,11 @@ def rap_music(evoked, forward, noise_cov, n_dipoles=5, return_residual=False,
     picks : array-like of int | None
         Indices (in info) of data channels. If None, MEG and EEG data channels
         (without bad channels) will be used.
+        picks is deprecated and will be removed in 0.16, specify the selection
+        of channels in info instead.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -244,18 +245,19 @@ def rap_music(evoked, forward, noise_cov, n_dipoles=5, return_residual=False,
 
     .. versionadded:: 0.9.0
     """
-
     info = evoked.info
     data = evoked.data
     times = evoked.times
 
-    picks = _setup_picks(picks, info, forward, noise_cov)
+    info = _deprecate_picks(info, picks)
+
+    picks = _setup_picks(info, forward, data_cov=None, noise_cov=noise_cov)
 
     data = data[picks]
 
     dipoles, explained_data = _apply_rap_music(data, info, times, forward,
                                                noise_cov, n_dipoles,
-                                               picks, return_residual)
+                                               picks)
 
     if return_residual:
         residual = evoked.copy()
diff --git a/mne/beamformer/tests/test_dics.py b/mne/beamformer/tests/test_dics.py
index ccdd50c..b6dcda4 100644
--- a/mne/beamformer/tests/test_dics.py
+++ b/mne/beamformer/tests/test_dics.py
@@ -3,7 +3,7 @@ import warnings
 import os.path as op
 import copy as cp
 
-from nose.tools import assert_true, assert_raises
+from nose.tools import assert_true, assert_raises, assert_equal
 import numpy as np
 from numpy.testing import assert_array_equal, assert_array_almost_equal
 
@@ -26,28 +26,29 @@ fname_fwd_vol = op.join(data_path, 'MEG', 'sample',
                         'sample_audvis_trunc-meg-vol-7-fwd.fif')
 fname_event = op.join(data_path, 'MEG', 'sample',
                       'sample_audvis_trunc_raw-eve.fif')
-label = 'Aud-lh'
-fname_label = op.join(data_path, 'MEG', 'sample', 'labels', '%s.label' % label)
+fname_label = op.join(data_path, 'MEG', 'sample', 'labels', 'Aud-lh.label')
 
 
-def read_forward_solution_meg(*args, **kwargs):
-    fwd = mne.read_forward_solution(*args, **kwargs)
+def _read_forward_solution_meg(*args, **kwargs):
+    fwd = mne.read_forward_solution(*args)
+    fwd = mne.convert_forward_solution(fwd, **kwargs)
     return mne.pick_types_forward(fwd, meg=True, eeg=False)
 
 
 def _get_data(tmin=-0.11, tmax=0.15, read_all_forward=True, compute_csds=True):
-    """Read in data used in tests
-    """
+    """Read in data used in tests."""
     label = mne.read_label(fname_label)
     events = mne.read_events(fname_event)[:10]
-    raw = mne.io.read_raw_fif(fname_raw, preload=False, add_eeg_ref=False)
+    raw = mne.io.read_raw_fif(fname_raw, preload=False)
     raw.add_proj([], remove_existing=True)  # we'll subselect so remove proj
     forward = mne.read_forward_solution(fname_fwd)
     if read_all_forward:
-        forward_surf_ori = read_forward_solution_meg(fname_fwd, surf_ori=True)
-        forward_fixed = read_forward_solution_meg(fname_fwd, force_fixed=True,
-                                                  surf_ori=True)
-        forward_vol = mne.read_forward_solution(fname_fwd_vol, surf_ori=True)
+        forward_surf_ori = _read_forward_solution_meg(
+            fname_fwd, surf_ori=True)
+        forward_fixed = _read_forward_solution_meg(
+            fname_fwd, force_fixed=True, use_cps=False)
+        forward_vol = mne.read_forward_solution(fname_fwd_vol)
+        forward_vol = mne.convert_forward_solution(forward_vol, surf_ori=True)
     else:
         forward_surf_ori = None
         forward_fixed = None
@@ -67,10 +68,9 @@ def _get_data(tmin=-0.11, tmax=0.15, read_all_forward=True, compute_csds=True):
     # Read epochs
     epochs = mne.Epochs(raw, events, event_id, tmin, tmax, proj=True,
                         picks=picks, baseline=(None, 0), preload=True,
-                        reject=dict(grad=4000e-13, mag=4e-12, eog=150e-6),
-                        add_eeg_ref=False)
+                        reject=dict(grad=4000e-13, mag=4e-12, eog=150e-6))
     epochs.resample(200, npad=0, n_jobs=2)
-    evoked = epochs.average()
+    evoked = epochs.average().crop(0, None)
 
     # Computing the data and noise cross-spectral density matrices
     if compute_csds:
@@ -89,28 +89,32 @@ def _get_data(tmin=-0.11, tmax=0.15, read_all_forward=True, compute_csds=True):
 
 @testing.requires_testing_data
 def test_dics():
-    """Test DICS with evoked data and single trials
-    """
+    """Test DICS with evoked data and single trials."""
     raw, epochs, evoked, data_csd, noise_csd, label, forward,\
         forward_surf_ori, forward_fixed, forward_vol = _get_data()
+    epochs.crop(0, None)
+    reg = 0.5  # Heavily regularize due to low SNR
 
-    stc = dics(evoked, forward, noise_csd=noise_csd, data_csd=data_csd,
-               label=label)
+    for real_filter in (True, False):
+        stc = dics(evoked, forward, noise_csd=noise_csd, data_csd=data_csd,
+                   label=label, real_filter=real_filter, reg=reg)
+        stc_pow = np.sum(stc.data, axis=1)
+        idx = np.argmax(stc_pow)
+        max_stc = stc.data[idx]
+        tmax = stc.times[np.argmax(max_stc)]
 
-    stc.crop(0, None)
-    stc_pow = np.sum(stc.data, axis=1)
-    idx = np.argmax(stc_pow)
-    max_stc = stc.data[idx]
-    tmax = stc.times[np.argmax(max_stc)]
-
-    # Incorrect due to limited number of epochs
-    assert_true(0.04 < tmax < 0.05)
-    assert_true(10 < np.max(max_stc) < 13)
+        # Incorrect due to limited number of epochs
+        assert_true(0.04 < tmax < 0.06, msg=tmax)
+        assert_true(3. < np.max(max_stc) < 6., msg=np.max(max_stc))
 
     # Test picking normal orientation
     stc_normal = dics(evoked, forward_surf_ori, noise_csd, data_csd,
-                      pick_ori="normal", label=label)
-    stc_normal.crop(0, None)
+                      pick_ori="normal", label=label, real_filter=True,
+                      reg=reg)
+    assert_true(stc_normal.data.min() < 0)  # this doesn't take abs
+    stc_normal = dics(evoked, forward_surf_ori, noise_csd, data_csd,
+                      pick_ori="normal", label=label, reg=reg)
+    assert_true(stc_normal.data.min() >= 0)  # this does take abs
 
     # The amplitude of normal orientation results should always be smaller than
     # free orientation results
@@ -133,11 +137,10 @@ def test_dics():
 
     # Now test single trial using fixed orientation forward solution
     # so we can compare it to the evoked solution
-    stcs = dics_epochs(epochs, forward_fixed, noise_csd, data_csd, reg=0.01,
-                       label=label)
+    stcs = dics_epochs(epochs, forward_fixed, noise_csd, data_csd, label=label)
 
     # Testing returning of generator
-    stcs_ = dics_epochs(epochs, forward_fixed, noise_csd, data_csd, reg=0.01,
+    stcs_ = dics_epochs(epochs, forward_fixed, noise_csd, data_csd,
                         return_generator=True, label=label)
     assert_array_equal(stcs[0].data, advance_iterator(stcs_).data)
 
@@ -148,38 +151,39 @@ def test_dics():
     # Average the single trial estimates
     stc_avg = np.zeros_like(stc.data)
     for this_stc in stcs:
-        stc_avg += this_stc.crop(0, None).data
+        stc_avg += this_stc.data
     stc_avg /= len(stcs)
 
     idx = np.argmax(np.max(stc_avg, axis=1))
     max_stc = stc_avg[idx]
     tmax = stc.times[np.argmax(max_stc)]
 
-    assert_true(0.045 < tmax < 0.06)  # incorrect due to limited # of epochs
+    assert_true(0.120 < tmax < 0.150, msg=tmax)  # incorrect due to limited #
     assert_true(12 < np.max(max_stc) < 18.5)
 
 
 @testing.requires_testing_data
 def test_dics_source_power():
-    """Test DICS source power computation
-    """
+    """Test DICS source power computation."""
     raw, epochs, evoked, data_csd, noise_csd, label, forward,\
         forward_surf_ori, forward_fixed, forward_vol = _get_data()
+    epochs.crop(0, None)
+    reg = 0.05
 
     stc_source_power = dics_source_power(epochs.info, forward, noise_csd,
-                                         data_csd, label=label)
+                                         data_csd, label=label, reg=reg)
 
     max_source_idx = np.argmax(stc_source_power.data)
     max_source_power = np.max(stc_source_power.data)
 
     # TODO: Maybe these could be more directly compared to dics() results?
-    assert_true(max_source_idx == 0)
-    assert_true(0.5 < max_source_power < 1.15)
+    assert_true(max_source_idx == 1)
+    assert_true(0.004 < max_source_power < 0.005, msg=max_source_power)
 
     # Test picking normal orientation and using a list of CSD matrices
     stc_normal = dics_source_power(epochs.info, forward_surf_ori,
                                    [noise_csd] * 2, [data_csd] * 2,
-                                   pick_ori="normal", label=label)
+                                   pick_ori="normal", label=label, reg=reg)
 
     assert_true(stc_normal.data.shape == (stc_source_power.data.shape[0], 2))
 
@@ -208,8 +212,8 @@ def test_dics_source_power():
                   [noise_csd] * 2, [data_csd] * 3)
 
     # Test detection of different frequencies in noise and data CSD objects
-    noise_csd.frequencies = [1, 2]
-    data_csd.frequencies = [1, 2, 3]
+    noise_csd.freqs = [1, 2]
+    data_csd.freqs = [1, 2, 3]
     assert_raises(ValueError, dics_source_power, epochs.info, forward,
                   noise_csd, data_csd)
 
@@ -217,24 +221,23 @@ def test_dics_source_power():
     data_csds = [cp.deepcopy(data_csd) for i in range(3)]
     frequencies = [1, 3, 4]
     for freq, data_csd in zip(frequencies, data_csds):
-        data_csd.frequencies = [freq]
+        data_csd.freqs = [freq]
     noise_csds = data_csds
     with warnings.catch_warnings(record=True) as w:
         dics_source_power(epochs.info, forward, noise_csds, data_csds)
-    assert len(w) == 1
+    assert_equal(len(w), 1)
 
 
 @testing.requires_testing_data
 def test_tf_dics():
-    """Test TF beamforming based on DICS
-    """
+    """Test TF beamforming based on DICS."""
     tmin, tmax, tstep = -0.2, 0.2, 0.1
     raw, epochs, _, _, _, label, forward, _, _, _ =\
         _get_data(tmin, tmax, read_all_forward=False, compute_csds=False)
 
     freq_bins = [(4, 20), (30, 55)]
     win_lengths = [0.2, 0.2]
-    reg = 0.001
+    reg = 0.05
 
     noise_csds = []
     for freq_bin, win_length in zip(freq_bins, win_lengths):
@@ -249,6 +252,8 @@ def test_tf_dics():
 
     assert_true(len(stcs) == len(freq_bins))
     assert_true(stcs[0].shape[1] == 4)
+    assert_true(2.2 < stcs[0].data.max() < 2.3)
+    assert_true(0.94 < stcs[0].data.min() < 0.95)
 
     # Manually calculating source power in several time windows to compare
     # results and test overlapping
diff --git a/mne/beamformer/tests/test_lcmv.py b/mne/beamformer/tests/test_lcmv.py
index ff463b9..9a80e73 100644
--- a/mne/beamformer/tests/test_lcmv.py
+++ b/mne/beamformer/tests/test_lcmv.py
@@ -1,17 +1,21 @@
+from copy import deepcopy
 import os.path as op
 
+import pytest
 from nose.tools import assert_true, assert_raises
 import numpy as np
-from numpy.testing import assert_array_almost_equal, assert_array_equal
+from numpy.testing import (assert_array_almost_equal, assert_array_equal,
+                           assert_almost_equal)
 import warnings
 
 import mne
 from mne import compute_covariance
 from mne.datasets import testing
-from mne.beamformer import lcmv, lcmv_epochs, lcmv_raw, tf_lcmv
-from mne.beamformer._lcmv import _lcmv_source_power
+from mne.beamformer import (make_lcmv, apply_lcmv, apply_lcmv_raw, lcmv,
+                            lcmv_epochs, lcmv_raw, tf_lcmv)
+from mne.beamformer._lcmv import _lcmv_source_power, _reg_pinv, _eig_inv
 from mne.externals.six import advance_iterator
-from mne.utils import run_tests_if_main, slow_test
+from mne.utils import run_tests_if_main
 
 
 data_path = testing.data_path(download=False)
@@ -23,30 +27,30 @@ fname_fwd_vol = op.join(data_path, 'MEG', 'sample',
                         'sample_audvis_trunc-meg-vol-7-fwd.fif')
 fname_event = op.join(data_path, 'MEG', 'sample',
                       'sample_audvis_trunc_raw-eve.fif')
-label = 'Aud-lh'
-fname_label = op.join(data_path, 'MEG', 'sample', 'labels', '%s.label' % label)
+fname_label = op.join(data_path, 'MEG', 'sample', 'labels', 'Aud-lh.label')
 
 warnings.simplefilter('always')  # enable b/c these tests throw warnings
 
 
-def read_forward_solution_meg(*args, **kwargs):
-    fwd = mne.read_forward_solution(*args, **kwargs)
+def _read_forward_solution_meg(*args, **kwargs):
+    fwd = mne.read_forward_solution(*args)
+    fwd = mne.convert_forward_solution(fwd, **kwargs)
     return mne.pick_types_forward(fwd, meg=True, eeg=False)
 
 
 def _get_data(tmin=-0.1, tmax=0.15, all_forward=True, epochs=True,
               epochs_preload=True, data_cov=True):
-    """Read in data used in tests
-    """
+    """Read in data used in tests."""
     label = mne.read_label(fname_label)
     events = mne.read_events(fname_event)
-    raw = mne.io.read_raw_fif(fname_raw, preload=True, add_eeg_ref=False)
+    raw = mne.io.read_raw_fif(fname_raw, preload=True)
     forward = mne.read_forward_solution(fname_fwd)
     if all_forward:
-        forward_surf_ori = read_forward_solution_meg(fname_fwd, surf_ori=True)
-        forward_fixed = read_forward_solution_meg(fname_fwd, force_fixed=True,
-                                                  surf_ori=True)
-        forward_vol = read_forward_solution_meg(fname_fwd_vol, surf_ori=True)
+        forward_surf_ori = _read_forward_solution_meg(
+            fname_fwd, surf_ori=True)
+        forward_fixed = _read_forward_solution_meg(
+            fname_fwd, force_fixed=True, surf_ori=True, use_cps=False)
+        forward_vol = _read_forward_solution_meg(fname_fwd_vol)
     else:
         forward_surf_ori = None
         forward_fixed = None
@@ -69,10 +73,10 @@ def _get_data(tmin=-0.1, tmax=0.15, all_forward=True, epochs=True,
         epochs = mne.Epochs(
             raw, events, event_id, tmin, tmax, proj=True,
             baseline=(None, 0), preload=epochs_preload,
-            reject=dict(grad=4000e-13, mag=4e-12, eog=150e-6),
-            add_eeg_ref=False)
+            reject=dict(grad=4000e-13, mag=4e-12, eog=150e-6))
         if epochs_preload:
             epochs.resample(200, npad=0, n_jobs=2)
+        epochs.crop(0, None)
         evoked = epochs.average()
         info = evoked.info
     else:
@@ -81,6 +85,7 @@ def _get_data(tmin=-0.1, tmax=0.15, all_forward=True, epochs=True,
         info = raw.info
 
     noise_cov = mne.read_cov(fname_cov)
+    noise_cov['projs'] = []  # avoid warning
     with warnings.catch_warnings(record=True):  # bad proj
         noise_cov = mne.cov.regularize(noise_cov, info, mag=0.05, grad=0.05,
                                        eeg=0.1, proj=True)
@@ -94,19 +99,19 @@ def _get_data(tmin=-0.1, tmax=0.15, all_forward=True, epochs=True,
         forward_surf_ori, forward_fixed, forward_vol
 
 
- at slow_test
+ at pytest.mark.slowtest
 @testing.requires_testing_data
 def test_lcmv():
-    """Test LCMV with evoked data and single trials
-    """
+    """Test LCMV with evoked data and single trials."""
     raw, epochs, evoked, data_cov, noise_cov, label, forward,\
         forward_surf_ori, forward_fixed, forward_vol = _get_data()
 
     for fwd in [forward, forward_vol]:
-        stc = lcmv(evoked, fwd, noise_cov, data_cov, reg=0.01)
+        stc = lcmv(evoked, fwd, noise_cov, data_cov, reg=0.01,
+                   max_ori_out='signed')
         stc.crop(0.02, None)
 
-        stc_pow = np.sum(stc.data, axis=1)
+        stc_pow = np.sum(np.abs(stc.data), axis=1)
         idx = np.argmax(stc_pow)
         max_stc = stc.data[idx]
         tmax = stc.times[np.argmax(max_stc)]
@@ -117,7 +122,8 @@ def test_lcmv():
         if fwd is forward:
             # Test picking normal orientation (surface source space only)
             stc_normal = lcmv(evoked, forward_surf_ori, noise_cov,
-                              data_cov, reg=0.01, pick_ori="normal")
+                              data_cov, reg=0.01, pick_ori="normal",
+                              max_ori_out='signed')
             stc_normal.crop(0.02, None)
 
             stc_pow = np.sum(np.abs(stc_normal.data), axis=1)
@@ -134,26 +140,77 @@ def test_lcmv():
 
         # Test picking source orientation maximizing output source power
         stc_max_power = lcmv(evoked, fwd, noise_cov, data_cov, reg=0.01,
-                             pick_ori="max-power")
+                             pick_ori="max-power", max_ori_out='signed')
         stc_max_power.crop(0.02, None)
-        stc_pow = np.sum(stc_max_power.data, axis=1)
+        stc_pow = np.sum(np.abs(stc_max_power.data), axis=1)
         idx = np.argmax(stc_pow)
-        max_stc = stc_max_power.data[idx]
+        max_stc = np.abs(stc_max_power.data[idx])
         tmax = stc.times[np.argmax(max_stc)]
 
-        assert_true(0.09 < tmax < 0.11, tmax)
+        assert_true(0.08 < tmax < 0.11, tmax)
         assert_true(0.8 < np.max(max_stc) < 3., np.max(max_stc))
 
-        # Maximum output source power orientation results should be similar to
-        # free orientation results
-        assert_true((stc_max_power.data - stc.data < 1).all())
+        stc_max_power.data[:, :] = np.abs(stc_max_power.data)
+
+        if fwd is forward:
+            # Maximum output source power orientation results should be
+            # similar to free orientation results in areas with channel
+            # coverage
+            label = mne.read_label(fname_label)
+            mean_stc = stc.extract_label_time_course(label, fwd['src'],
+                                                     mode='mean')
+            mean_stc_max_pow = \
+                stc_max_power.extract_label_time_course(label, fwd['src'],
+                                                        mode='mean')
+            assert_true((np.abs(mean_stc - mean_stc_max_pow) < 0.5).all())
+
+        # Test NAI weight normalization:
+        stc_nai = lcmv(evoked, fwd, noise_cov=noise_cov, data_cov=data_cov,
+                       reg=0.01, pick_ori='max-power', weight_norm='nai',
+                       max_ori_out='signed')
+        stc_nai.crop(0.02, None)
+
+        # Test whether unit-noise-gain solution is a scaled version of NAI
+        pearsoncorr = np.corrcoef(np.concatenate(np.abs(stc_nai.data)),
+                                  np.concatenate(stc_max_power.data))
+        assert_almost_equal(pearsoncorr[0, 1], 1.)
+
+    # Test sphere head model with unit-noise gain beamformer and orientation
+    # selection and rank reduction of the leadfield
+    sphere = mne.make_sphere_model(r0=(0., 0., 0.), head_radius=0.080)
+    src = mne.setup_volume_source_space(subject=None, pos=15., mri=None,
+                                        sphere=(0.0, 0.0, 0.0, 80.0),
+                                        bem=None, mindist=5.0, exclude=2.0)
+
+    fwd_sphere = mne.make_forward_solution(evoked.info, trans=None, src=src,
+                                           bem=sphere, eeg=False, meg=True)
+
+    # Test that we get an error is not reducing rank
+    assert_raises(ValueError, lcmv, evoked, fwd_sphere, noise_cov, data_cov,
+                  reg=0.1, weight_norm='unit-noise-gain', pick_ori="max-power",
+                  reduce_rank=False)
+
+    # Now let's reduce it
+    stc_sphere = lcmv(evoked, fwd_sphere, noise_cov, data_cov, reg=0.1,
+                      weight_norm='unit-noise-gain', pick_ori="max-power",
+                      reduce_rank=True, max_ori_out='signed')
+    stc_sphere = np.abs(stc_sphere)
+    stc_sphere.crop(0.02, None)
+
+    stc_pow = np.sum(stc_sphere.data, axis=1)
+    idx = np.argmax(stc_pow)
+    max_stc = stc_sphere.data[idx]
+    tmax = stc_sphere.times[np.argmax(max_stc)]
+
+    assert_true(0.08 < tmax < 0.11, tmax)
+    assert_true(0.4 < np.max(max_stc) < 2., np.max(max_stc))
 
     # Test if fixed forward operator is detected when picking normal or
     # max-power orientation
     assert_raises(ValueError, lcmv, evoked, forward_fixed, noise_cov, data_cov,
                   reg=0.01, pick_ori="normal")
     assert_raises(ValueError, lcmv, evoked, forward_fixed, noise_cov, data_cov,
-                  reg=0.01, pick_ori="max-power")
+                  reg=0.01, pick_ori="max-power", max_ori_out='signed')
 
     # Test if non-surface oriented forward operator is detected when picking
     # normal orientation
@@ -165,12 +222,74 @@ def test_lcmv():
     assert_raises(ValueError, lcmv, evoked, forward_vol, noise_cov, data_cov,
                   reg=0.01, pick_ori="normal")
 
+    # Test if missing of data covariance matrix is detected
+    assert_raises(ValueError, lcmv, evoked, forward_vol, noise_cov=noise_cov,
+                  data_cov=None, reg=0.01, pick_ori="max-power",
+                  max_ori_out='signed')
+
+    # Test if missing of noise covariance matrix is detected when more than
+    # one channel type is present in the data
+    assert_raises(ValueError, lcmv, evoked, forward_vol, noise_cov=None,
+                  data_cov=data_cov, reg=0.01, pick_ori="max-power",
+                  max_ori_out='signed')
+
+    # Test if not-yet-implemented orientation selections raise error with
+    # neural activity index
+    assert_raises(NotImplementedError, lcmv, evoked, forward_surf_ori,
+                  noise_cov, data_cov, reg=0.01, pick_ori="normal",
+                  weight_norm='nai')
+    assert_raises(NotImplementedError, lcmv, evoked, forward_vol, noise_cov,
+                  data_cov, reg=0.01, pick_ori=None, weight_norm='nai')
+
+    # Test if no weight-normalization and max-power source orientation throw
+    # an error
+    assert_raises(NotImplementedError, lcmv, evoked, forward_vol, noise_cov,
+                  data_cov, reg=0.01, pick_ori="max-power", weight_norm=None,
+                  max_ori_out='signed')
+
+    # Test if wrong channel selection is detected in application of filter
+    evoked_ch = deepcopy(evoked)
+    evoked_ch.pick_channels(evoked_ch.ch_names[1:])
+    filters = make_lcmv(evoked.info, forward_vol, data_cov, reg=0.01,
+                        noise_cov=noise_cov)
+    assert_raises(ValueError, apply_lcmv, evoked_ch, filters,
+                  max_ori_out='signed')
+
+    # Test if discrepancies in channel selection of data and fwd model are
+    # handled correctly in apply_lcmv
+    # make filter with data where first channel was removed
+    filters = make_lcmv(evoked_ch.info, forward_vol, data_cov, reg=0.01,
+                        noise_cov=noise_cov)
+    # applying that filter to the full data set should automatically exclude
+    # this channel from the data
+    stc = apply_lcmv(evoked, filters, max_ori_out='signed')
+    # the result should be equal to applying this filter to a dataset without
+    # this channel:
+    stc_ch = apply_lcmv(evoked_ch, filters, max_ori_out='signed')
+    assert_array_almost_equal(stc.data, stc_ch.data)
+
+    # Test if non-matching SSP projection is detected in application of filter
+    raw_proj = deepcopy(raw)
+    raw_proj.del_proj()
+    assert_raises(ValueError, apply_lcmv_raw, raw_proj, filters,
+                  max_ori_out='signed')
+
+    # Test if setting reduce_rank to True returns a NotImplementedError
+    # when no orientation selection is done or pick_ori='normal'
+    assert_raises(NotImplementedError, lcmv, evoked, forward_vol, noise_cov,
+                  data_cov, pick_ori=None, weight_norm='nai', reduce_rank=True,
+                  max_ori_out='signed')
+    assert_raises(NotImplementedError, lcmv, evoked, forward_surf_ori,
+                  noise_cov, data_cov, pick_ori='normal', weight_norm='nai',
+                  reduce_rank=True, max_ori_out='signed')
+
     # Now test single trial using fixed orientation forward solution
     # so we can compare it to the evoked solution
     stcs = lcmv_epochs(epochs, forward_fixed, noise_cov, data_cov,
-                       reg=0.01)
+                       reg=0.01, max_ori_out='signed')
     stcs_ = lcmv_epochs(epochs, forward_fixed, noise_cov, data_cov,
-                        reg=0.01, return_generator=True)
+                        reg=0.01, return_generator=True,
+                        max_ori_out='signed')
     assert_array_equal(stcs[0].data, advance_iterator(stcs_).data)
 
     epochs.drop_bad()
@@ -183,21 +302,21 @@ def test_lcmv():
     stc_avg /= len(stcs)
 
     # compare it to the solution using evoked with fixed orientation
-    stc_fixed = lcmv(evoked, forward_fixed, noise_cov, data_cov, reg=0.01)
+    stc_fixed = lcmv(evoked, forward_fixed, noise_cov, data_cov, reg=0.01,
+                     max_ori_out='signed')
     assert_array_almost_equal(stc_avg, stc_fixed.data)
 
     # use a label so we have few source vertices and delayed computation is
     # not used
     stcs_label = lcmv_epochs(epochs, forward_fixed, noise_cov, data_cov,
-                             reg=0.01, label=label)
+                             reg=0.01, label=label, max_ori_out='signed')
 
     assert_array_almost_equal(stcs_label[0].data, stcs[0].in_label(label).data)
 
 
 @testing.requires_testing_data
 def test_lcmv_raw():
-    """Test LCMV with raw data
-    """
+    """Test LCMV with raw data."""
     raw, _, _, _, noise_cov, label, forward, _, _, _ =\
         _get_data(all_forward=False, epochs=False, data_cov=False)
 
@@ -207,7 +326,8 @@ def test_lcmv_raw():
     # use only the left-temporal MEG channels for LCMV
     data_cov = mne.compute_raw_covariance(raw, tmin=tmin, tmax=tmax)
     stc = lcmv_raw(raw, forward, noise_cov, data_cov, reg=0.01,
-                   label=label, start=start, stop=stop)
+                   label=label, start=start, stop=stop,
+                   max_ori_out='signed')
 
     assert_array_almost_equal(np.array([tmin, tmax]),
                               np.array([stc.times[0], stc.times[-1]]),
@@ -222,13 +342,13 @@ def test_lcmv_raw():
 
 @testing.requires_testing_data
 def test_lcmv_source_power():
-    """Test LCMV source power computation
-    """
+    """Test LCMV source power computation."""
     raw, epochs, evoked, data_cov, noise_cov, label, forward,\
         forward_surf_ori, forward_fixed, forward_vol = _get_data()
 
     stc_source_power = _lcmv_source_power(epochs.info, forward, noise_cov,
-                                          data_cov, label=label)
+                                          data_cov, label=label,
+                                          weight_norm='unit-noise-gain')
 
     max_source_idx = np.argmax(stc_source_power.data)
     max_source_power = np.max(stc_source_power.data)
@@ -239,7 +359,7 @@ def test_lcmv_source_power():
     # Test picking normal orientation and using a list of CSD matrices
     stc_normal = _lcmv_source_power(
         epochs.info, forward_surf_ori, noise_cov, data_cov,
-        pick_ori="normal", label=label)
+        pick_ori="normal", label=label, weight_norm='unit-noise-gain')
 
     # The normal orientation results should always be smaller than free
     # orientation results
@@ -264,11 +384,10 @@ def test_lcmv_source_power():
 
 @testing.requires_testing_data
 def test_tf_lcmv():
-    """Test TF beamforming based on LCMV
-    """
+    """Test TF beamforming based on LCMV."""
     label = mne.read_label(fname_label)
     events = mne.read_events(fname_event)
-    raw = mne.io.read_raw_fif(fname_raw, preload=True, add_eeg_ref=False)
+    raw = mne.io.read_raw_fif(fname_raw, preload=True)
     forward = mne.read_forward_solution(fname_fwd)
 
     event_id, tmin, tmax = 1, -0.2, 0.2
@@ -288,9 +407,8 @@ def test_tf_lcmv():
     # Read epochs
     epochs = mne.Epochs(raw, events, event_id, tmin, tmax, proj=True,
                         baseline=None, preload=False,
-                        reject=dict(grad=4000e-13, mag=4e-12, eog=150e-6),
-                        add_eeg_ref=False)
-    epochs.drop_bad()
+                        reject=dict(grad=4000e-13, mag=4e-12, eog=150e-6))
+    epochs.load_data()
 
     freq_bins = [(4, 12), (15, 40)]
     time_windows = [(-0.1, 0.1), (0.0, 0.2)]
@@ -306,7 +424,7 @@ def test_tf_lcmv():
                         iir_params=dict(output='ba'))
         epochs_band = mne.Epochs(
             raw_band, epochs.events, epochs.event_id, tmin=tmin, tmax=tmax,
-            baseline=None, proj=True, add_eeg_ref=False)
+            baseline=None, proj=True)
         with warnings.catch_warnings(record=True):  # not enough samples
             noise_cov = compute_covariance(epochs_band, tmin=tmin, tmax=tmin +
                                            win_length)
@@ -327,12 +445,13 @@ def test_tf_lcmv():
                 with warnings.catch_warnings(record=True):  # bad proj
                     stc_source_power = _lcmv_source_power(
                         epochs.info, forward, noise_cov, data_cov,
-                        reg=reg, label=label)
+                        reg=reg, label=label, weight_norm='unit-noise-gain')
                 source_power.append(stc_source_power.data)
 
-    with warnings.catch_warnings(record=True):
-        stcs = tf_lcmv(epochs, forward, noise_covs, tmin, tmax, tstep,
-                       win_lengths, freq_bins, reg=reg, label=label)
+    assert_raises(ValueError, tf_lcmv, epochs, forward, noise_covs, tmin, tmax,
+                  tstep, win_lengths, freq_bins, reg=reg, label=label)
+    stcs = tf_lcmv(epochs, forward, noise_covs, tmin, tmax, tstep,
+                   win_lengths, freq_bins, reg=reg, label=label, raw=raw)
 
     assert_true(len(stcs) == len(freq_bins))
     assert_true(stcs[0].shape[1] == 4)
@@ -364,11 +483,20 @@ def test_tf_lcmv():
     assert_raises(ValueError, tf_lcmv, epochs, forward, noise_covs, tmin, tmax,
                   tstep=0.15, win_lengths=[0.2, 0.1], freq_bins=freq_bins)
 
+    # Test if missing of noise covariance matrix is detected when more than
+    # one channel type is present in the data
+    assert_raises(ValueError, tf_lcmv, epochs, forward, noise_covs=None,
+                  tmin=tmin, tmax=tmax, tstep=tstep, win_lengths=win_lengths,
+                  freq_bins=freq_bins)
+
+    # Test if unsupported weight normalization specification is detected
+    assert_raises(ValueError, tf_lcmv, epochs, forward, noise_covs, tmin, tmax,
+                  tstep, win_lengths, freq_bins, weight_norm='nai')
+
     # Test correct detection of preloaded epochs objects that do not contain
     # the underlying raw object
     epochs_preloaded = mne.Epochs(raw, events, event_id, tmin, tmax, proj=True,
-                                  baseline=(None, 0), preload=True,
-                                  add_eeg_ref=False)
+                                  baseline=(None, 0), preload=True)
     epochs_preloaded._raw = None
     with warnings.catch_warnings(record=True):  # not enough samples
         assert_raises(ValueError, tf_lcmv, epochs_preloaded, forward,
@@ -379,9 +507,33 @@ def test_tf_lcmv():
         # responses yields zeros
         stcs = tf_lcmv(epochs[0], forward, noise_covs, tmin, tmax, tstep,
                        win_lengths, freq_bins, subtract_evoked=True, reg=reg,
-                       label=label)
+                       label=label, raw=raw)
 
     assert_array_almost_equal(stcs[0].data, np.zeros_like(stcs[0].data))
 
 
+def test_reg_pinv():
+    """Test regularization and inversion of covariance matrix."""
+    # create rank-deficient array
+    a = np.array([[1., 0., 1.], [0., 1., 0.], [1., 0., 1.]])
+
+    # Test if rank-deficient matrix without regularization throws
+    # specific warning
+    with warnings.catch_warnings(record=True) as w:
+        _reg_pinv(a, reg=0.)
+    assert_true(any('deficient' in str(ww.message) for ww in w))
+
+
+def test_eig_inv():
+    """Test matrix pseudoinversion with setting smallest eigenvalue to zero."""
+    # create rank-deficient array
+    a = np.array([[1., 0., 1.], [0., 1., 0.], [1., 0., 1.]])
+
+    # test inversion
+    a_inv = np.linalg.pinv(a)
+    a_inv_eig = _eig_inv(a, 2)
+
+    assert_almost_equal(a_inv, a_inv_eig)
+
+
 run_tests_if_main()
diff --git a/mne/beamformer/tests/test_rap_music.py b/mne/beamformer/tests/test_rap_music.py
index ce73f0b..474aaac 100644
--- a/mne/beamformer/tests/test_rap_music.py
+++ b/mne/beamformer/tests/test_rap_music.py
@@ -8,11 +8,13 @@ import numpy as np
 from scipy import linalg
 
 import warnings
-from nose.tools import assert_true
+from nose.tools import assert_true, assert_equal
+from numpy.testing import assert_array_equal
 
 import mne
 from mne.datasets import testing
 from mne.beamformer import rap_music
+from mne.cov import regularize
 from mne.utils import run_tests_if_main
 
 
@@ -25,33 +27,27 @@ fname_fwd = op.join(data_path, 'MEG', 'sample',
 warnings.simplefilter('always')  # enable b/c these tests throw warnings
 
 
-def _read_forward_solution_meg(fname_fwd, **kwargs):
-    fwd = mne.read_forward_solution(fname_fwd, **kwargs)
-    return mne.pick_types_forward(fwd, meg=True, eeg=False,
-                                  exclude=['MEG 2443'])
-
-
-def _get_data(event_id=1):
-    """Read in data used in tests
-    """
+def _get_data(ch_decim=1):
+    """Read in data used in tests."""
     # Read evoked
-    evoked = mne.read_evokeds(fname_ave, event_id)
-    evoked.pick_types(meg=True, eeg=False)
-    evoked.crop(0, 0.3)
-
-    forward = mne.read_forward_solution(fname_fwd)
-
-    forward_surf_ori = _read_forward_solution_meg(fname_fwd, surf_ori=True)
-    forward_fixed = _read_forward_solution_meg(fname_fwd, force_fixed=True,
-                                               surf_ori=True)
+    evoked = mne.read_evokeds(fname_ave, 0, baseline=(None, 0))
+    evoked.info['bads'] = ['MEG 2443']
+    evoked.info['lowpass'] = 20  # fake for decim
+    evoked.decimate(12)
+    evoked.crop(0.0, 0.3)
+    picks = mne.pick_types(evoked.info, meg=True, eeg=False)
+    picks = picks[::ch_decim]
+    evoked.pick_channels([evoked.ch_names[pick] for pick in picks])
+    evoked.info.normalize_proj()
 
     noise_cov = mne.read_cov(fname_cov)
+    noise_cov['projs'] = []
+    noise_cov = regularize(noise_cov, evoked.info)
+    return evoked, noise_cov
 
-    return evoked, noise_cov, forward, forward_surf_ori, forward_fixed
 
-
-def simu_data(evoked, forward, noise_cov, n_dipoles, times):
-    """Simulate an evoked dataset with 2 sources
+def simu_data(evoked, forward, noise_cov, n_dipoles, times, nave=1):
+    """Simulate an evoked dataset with 2 sources.
 
     One source is put in each hemisphere.
     """
@@ -61,8 +57,8 @@ def simu_data(evoked, forward, noise_cov, n_dipoles, times):
                                                    (2 * sigma ** 2))
 
     mu, sigma = 0.075, 0.008
-    s2 = 1 / (sigma * np.sqrt(2 * np.pi)) * np.exp(-(times - mu) ** 2 /
-                                                   (2 * sigma ** 2))
+    s2 = -1 / (sigma * np.sqrt(2 * np.pi)) * np.exp(-(times - mu) ** 2 /
+                                                    (2 * sigma ** 2))
     data = np.array([s1, s2]) * 1e-9
 
     src = forward['src']
@@ -79,7 +75,7 @@ def simu_data(evoked, forward, noise_cov, n_dipoles, times):
     stc = mne.SourceEstimate(data, vertices=vertices, tmin=tmin, tstep=tstep)
 
     sim_evoked = mne.simulation.simulate_evoked(forward, stc, evoked.info,
-                                                noise_cov, snr=20,
+                                                noise_cov, nave=nave,
                                                 random_state=rng)
 
     return sim_evoked, stc
@@ -122,31 +118,66 @@ def _check_dipoles(dipoles, fwd, stc, evoked, residual=None):
 
 @testing.requires_testing_data
 def test_rap_music_simulated():
-    """Test RAP-MUSIC with simulated evoked
-    """
-    evoked, noise_cov, forward, forward_surf_ori, forward_fixed =\
-        _get_data()
+    """Test RAP-MUSIC with simulated evoked."""
+    evoked, noise_cov = _get_data(ch_decim=16)
+    forward = mne.read_forward_solution(fname_fwd)
+    forward = mne.pick_channels_forward(forward, evoked.ch_names)
+    forward_surf_ori = mne.convert_forward_solution(forward, surf_ori=True)
+    forward_fixed = mne.convert_forward_solution(forward, force_fixed=True,
+                                                 surf_ori=True, use_cps=True)
 
     n_dipoles = 2
     sim_evoked, stc = simu_data(evoked, forward_fixed, noise_cov,
-                                n_dipoles, evoked.times)
+                                n_dipoles, evoked.times, nave=evoked.nave)
     # Check dipoles for fixed ori
     dipoles = rap_music(sim_evoked, forward_fixed, noise_cov,
                         n_dipoles=n_dipoles)
-    _check_dipoles(dipoles, forward_fixed, stc, evoked)
+    _check_dipoles(dipoles, forward_fixed, stc, sim_evoked)
+    assert_true(0.98 < dipoles[0].gof.max() < 1.)
+    assert_true(dipoles[0].gof.min() >= 0.)
+    assert_array_equal(dipoles[0].gof, dipoles[1].gof)
 
+    nave = 100000  # add a tiny amount of noise to the simulated evokeds
+    sim_evoked, stc = simu_data(evoked, forward_fixed, noise_cov,
+                                n_dipoles, evoked.times, nave=nave)
     dipoles, residual = rap_music(sim_evoked, forward_fixed, noise_cov,
                                   n_dipoles=n_dipoles, return_residual=True)
-    _check_dipoles(dipoles, forward_fixed, stc, evoked, residual)
+    _check_dipoles(dipoles, forward_fixed, stc, sim_evoked, residual)
 
     # Check dipoles for free ori
     dipoles, residual = rap_music(sim_evoked, forward, noise_cov,
                                   n_dipoles=n_dipoles, return_residual=True)
-    _check_dipoles(dipoles, forward_fixed, stc, evoked, residual)
+    _check_dipoles(dipoles, forward_fixed, stc, sim_evoked, residual)
 
     # Check dipoles for free surface ori
     dipoles, residual = rap_music(sim_evoked, forward_surf_ori, noise_cov,
                                   n_dipoles=n_dipoles, return_residual=True)
-    _check_dipoles(dipoles, forward_fixed, stc, evoked, residual)
+    _check_dipoles(dipoles, forward_fixed, stc, sim_evoked, residual)
+
+
+ at testing.requires_testing_data
+def test_rap_music_sphere():
+    """Test RAP-MUSIC with real data, sphere model, MEG only."""
+    evoked, noise_cov = _get_data(ch_decim=8)
+    sphere = mne.make_sphere_model(r0=(0., 0., 0.04))
+    src = mne.setup_volume_source_space(subject=None, pos=10.,
+                                        sphere=(0.0, 0.0, 40, 65.0),
+                                        mindist=5.0, exclude=0.0)
+    forward = mne.make_forward_solution(evoked.info, trans=None, src=src,
+                                        bem=sphere)
+
+    dipoles = rap_music(evoked, forward, noise_cov, n_dipoles=2)
+    # Test that there is one dipole on each hemisphere
+    pos = np.array([dip.pos[0] for dip in dipoles])
+    assert_equal(pos.shape, (2, 3))
+    assert_equal((pos[:, 0] < 0).sum(), 1)
+    assert_equal((pos[:, 0] > 0).sum(), 1)
+    # Check the amplitude scale
+    assert_true(1e-10 < dipoles[0].amplitude[0] < 1e-7)
+    # Check the orientation
+    dip_fit = mne.fit_dipole(evoked, noise_cov, sphere)[0]
+    assert_true(np.max(np.abs(np.dot(dip_fit.ori, dipoles[0].ori[0]))) > 0.99)
+    assert_true(np.max(np.abs(np.dot(dip_fit.ori, dipoles[1].ori[0]))) > 0.99)
+
 
 run_tests_if_main()
diff --git a/mne/bem.py b/mne/bem.py
index 625bd48..fc1df98 100644
--- a/mne/bem.py
+++ b/mne/bem.py
@@ -10,12 +10,11 @@ import glob
 import os
 import os.path as op
 import shutil
-import sys
+from copy import deepcopy
 
 import numpy as np
 from scipy import linalg
 
-from .utils import verbose, logger, run_subprocess, get_subjects_dir, warn
 from .transforms import _ensure_trans, apply_trans
 from .io import Info
 from .io.constants import FIFF
@@ -25,15 +24,16 @@ from .io.write import (start_file, start_block, write_float, write_int,
 from .io.tag import find_tag
 from .io.tree import dir_tree_find
 from .io.open import fiff_open
+from .surface import (read_surface, write_surface, complete_surface_info,
+                      _compute_nearest, _get_ico_surface, read_tri,
+                      _fast_cross_nd_sum, _get_solids)
+from .utils import verbose, logger, run_subprocess, get_subjects_dir, warn, _pl
 from .externals.six import string_types
 
 
 # ############################################################################
 # Compute BEM solution
 
-# define VEC_DIFF(from,to,diff) {\
-# (diff)[X] = (to)[X] - (from)[X];\
-
 # The following approach is based on:
 #
 # de Munck JC: "A linear discretization of the volume conductor boundary
@@ -43,33 +43,39 @@ from .externals.six import string_types
 
 
 class ConductorModel(dict):
-    """BEM or sphere model"""
-    def __repr__(self):
+    """BEM or sphere model."""
+
+    def __repr__(self):  # noqa: D105
         if self['is_sphere']:
             center = ', '.join('%0.1f' % (x * 1000.) for x in self['r0'])
-            pl = '' if len(self['layers']) == 1 else 's'
             rad = self.radius
             if rad is None:  # no radius / MEG only
                 extra = 'Sphere (no layers): r0=[%s] mm' % center
             else:
                 extra = ('Sphere (%s layer%s): r0=[%s] R=%1.f mm'
-                         % (len(self['layers']) - 1, pl, center, rad * 1000.))
+                         % (len(self['layers']) - 1, _pl(self['layers']),
+                            center, rad * 1000.))
         else:
-            pl = '' if len(self['surfs']) == 1 else 's'
-            extra = ('BEM (%s layer%s)' % (len(self['surfs']), pl))
+            extra = ('BEM (%s layer%s)' % (len(self['surfs']),
+                                           _pl(self['surfs'])))
         return '<ConductorModel  |  %s>' % extra
 
+    def copy(self):
+        """Return copy of ConductorModel instance."""
+        return deepcopy(self)
+
     @property
     def radius(self):
+        """Sphere radius if an EEG sphere model."""
         if not self['is_sphere']:
             raise RuntimeError('radius undefined for BEM')
         return None if len(self['layers']) == 0 else self['layers'][-1]['rad']
 
 
 def _calc_beta(rk, rk_norm, rk1, rk1_norm):
-    """These coefficients are used to calculate the magic vector omega"""
+    """Compute coefficients for calculating the magic vector omega."""
     rkk1 = rk1[0] - rk[0]
-    size = np.sqrt(np.dot(rkk1, rkk1))
+    size = np.linalg.norm(rkk1)
     rkk1 /= size
     num = rk_norm + np.dot(rk, rkk1)
     den = rk1_norm + np.dot(rk1, rkk1)
@@ -78,22 +84,22 @@ def _calc_beta(rk, rk_norm, rk1, rk1_norm):
 
 
 def _lin_pot_coeff(fros, tri_rr, tri_nn, tri_area):
-    """The linear potential matrix element computations"""
-    from .source_space import _fast_cross_nd_sum
+    """Compute the linear potential matrix element computations."""
     omega = np.zeros((len(fros), 3))
 
     # we replicate a little bit of the _get_solids code here for speed
+    # (we need some of the intermediate values later)
     v1 = tri_rr[np.newaxis, 0, :] - fros
     v2 = tri_rr[np.newaxis, 1, :] - fros
     v3 = tri_rr[np.newaxis, 2, :] - fros
     triples = _fast_cross_nd_sum(v1, v2, v3)
-    l1 = np.sqrt(np.sum(v1 * v1, axis=1))
-    l2 = np.sqrt(np.sum(v2 * v2, axis=1))
-    l3 = np.sqrt(np.sum(v3 * v3, axis=1))
-    ss = (l1 * l2 * l3 +
-          np.sum(v1 * v2, axis=1) * l3 +
-          np.sum(v1 * v3, axis=1) * l2 +
-          np.sum(v2 * v3, axis=1) * l1)
+    l1 = np.linalg.norm(v1, axis=1)
+    l2 = np.linalg.norm(v2, axis=1)
+    l3 = np.linalg.norm(v3, axis=1)
+    ss = l1 * l2 * l3
+    ss += np.einsum('ij,ij,i->i', v1, v2, l3)
+    ss += np.einsum('ij,ij,i->i', v1, v3, l2)
+    ss += np.einsum('ij,ij,i->i', v2, v3, l1)
     solids = np.arctan2(triples, ss)
 
     # We *could* subselect the good points from v1, v2, v3, triples, solids,
@@ -131,7 +137,7 @@ def _lin_pot_coeff(fros, tri_rr, tri_nn, tri_area):
 
 
 def _correct_auto_elements(surf, mat):
-    """Improve auto-element approximation..."""
+    """Improve auto-element approximation."""
     pi2 = 2.0 * np.pi
     tris_flat = surf['tris'].ravel()
     misses = pi2 - mat.sum(axis=1)
@@ -154,7 +160,7 @@ def _correct_auto_elements(surf, mat):
 
 
 def _fwd_bem_lin_pot_coeff(surfs):
-    """Calculate the coefficients for linear collocation approach"""
+    """Calculate the coefficients for linear collocation approach."""
     # taken from fwd_bem_linear_collocation.c
     nps = [surf['np'] for surf in surfs]
     np_tot = sum(nps)
@@ -194,13 +200,12 @@ def _fwd_bem_lin_pot_coeff(surfs):
 
 
 def _fwd_bem_multi_solution(solids, gamma, nps):
-    """Do multi surface solution
-
-      * Invert I - solids/(2*M_PI)
-      * Take deflation into account
-      * The matrix is destroyed after inversion
-      * This is the general multilayer case
+    """Do multi surface solution.
 
+    * Invert I - solids/(2*M_PI)
+    * Take deflation into account
+    * The matrix is destroyed after inversion
+    * This is the general multilayer case
     """
     pi2 = 1.0 / (2 * np.pi)
     n_tot = np.sum(nps)
@@ -220,12 +225,12 @@ def _fwd_bem_multi_solution(solids, gamma, nps):
 
 
 def _fwd_bem_homog_solution(solids, nps):
-    """Helper to make a homogeneous solution"""
+    """Make a homogeneous solution."""
     return _fwd_bem_multi_solution(solids, None, nps)
 
 
 def _fwd_bem_ip_modify_solution(solution, ip_solution, ip_mult, n_tri):
-    """Modify the solution according to the IP approach"""
+    """Modify the solution according to the IP approach."""
     n_last = n_tri[-1]
     mult = (1.0 + ip_mult) / ip_mult
 
@@ -247,11 +252,10 @@ def _fwd_bem_ip_modify_solution(solution, ip_solution, ip_mult, n_tri):
 
 
 def _fwd_bem_linear_collocation_solution(m):
-    """Compute the linear collocation potential solution"""
+    """Compute the linear collocation potential solution."""
     # first, add surface geometries
-    from .surface import _complete_surface_info
     for surf in m['surfs']:
-        _complete_surface_info(surf, verbose=False)
+        complete_surface_info(surf, copy=False, verbose=False)
 
     logger.info('Computing the linear collocation solution...')
     logger.info('    Matrix coefficients...')
@@ -279,14 +283,15 @@ def _fwd_bem_linear_collocation_solution(m):
 
 @verbose
 def make_bem_solution(surfs, verbose=None):
-    """Create a BEM solution using the linear collocation approach
+    """Create a BEM solution using the linear collocation approach.
 
     Parameters
     ----------
     surfs : list of dict
         The BEM surfaces to use (`from make_bem_model`)
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -328,8 +333,7 @@ def make_bem_solution(surfs, verbose=None):
 # Make BEM model
 
 def _ico_downsample(surf, dest_grade):
-    """Downsample the surface if isomorphic to a subdivided icosahedron"""
-    from .surface import _get_ico_surface
+    """Downsample the surface if isomorphic to a subdivided icosahedron."""
     n_tri = surf['ntri']
     found = -1
     bad_msg = ("A surface with %d triangles cannot be isomorphic with a "
@@ -364,8 +368,7 @@ def _ico_downsample(surf, dest_grade):
 
 
 def _get_ico_map(fro, to):
-    """Helper to get a mapping between ico surfaces"""
-    from .surface import _compute_nearest
+    """Get a mapping between ico surfaces."""
     nearest, dists = _compute_nearest(fro['rr'], to['rr'], return_dists=True)
     n_bads = (dists > 5e-3).sum()
     if n_bads > 0:
@@ -375,7 +378,7 @@ def _get_ico_map(fro, to):
 
 
 def _order_surfaces(surfs):
-    """Reorder the surfaces"""
+    """Reorder the surfaces."""
     if len(surfs) != 3:
         return surfs
     # we have three surfaces
@@ -391,9 +394,8 @@ def _order_surfaces(surfs):
 
 
 def _assert_complete_surface(surf):
-    """Check the sum of solid angles as seen from inside"""
+    """Check the sum of solid angles as seen from inside."""
     # from surface_checks.c
-    from .source_space import _get_solids
     tot_angle = 0.
     # Center of mass....
     cm = surf['rr'].mean(axis=0)
@@ -416,9 +418,8 @@ _surf_name = {
 
 
 def _assert_inside(fro, to):
-    """Helper to check one set of points is inside a surface"""
+    """Check one set of points is inside a surface."""
     # this is "is_inside" in surface_checks.c
-    from .source_space import _get_solids
     tot_angle = _get_solids(to['rr'][to['tris']], fro['rr'])
     if (np.abs(tot_angle / (2 * np.pi) - 1.0) > 1e-5).any():
         raise RuntimeError('Surface %s is not completely inside surface %s'
@@ -426,7 +427,7 @@ def _assert_inside(fro, to):
 
 
 def _check_surfaces(surfs):
-    """Check that the surfaces are complete and non-intersecting"""
+    """Check that the surfaces are complete and non-intersecting."""
     for surf in surfs:
         _assert_complete_surface(surf)
     # Then check the topology
@@ -437,7 +438,7 @@ def _check_surfaces(surfs):
 
 
 def _check_surface_size(surf):
-    """Check that the coordinate limits are reasonable"""
+    """Check that the coordinate limits are reasonable."""
     sizes = surf['rr'].max(axis=0) - surf['rr'].min(axis=0)
     if (sizes < 0.05).any():
         raise RuntimeError('Dimensions of the surface %s seem too small '
@@ -447,8 +448,7 @@ def _check_surface_size(surf):
 
 
 def _check_thicknesses(surfs):
-    """How close are we?"""
-    from .surface import _compute_nearest
+    """Compute how close we are."""
     for surf_1, surf_2 in zip(surfs[:-1], surfs[1:]):
         min_dist = _compute_nearest(surf_1['rr'], surf_2['rr'],
                                     return_dists=True)[0]
@@ -461,25 +461,30 @@ def _check_thicknesses(surfs):
                      1000 * min_dist))
 
 
-def _surfaces_to_bem(fname_surfs, ids, sigmas, ico=None):
-    """Convert surfaces to a BEM
-    """
-    from .surface import _read_surface_geom
+def _surfaces_to_bem(surfs, ids, sigmas, ico=None, rescale=True):
+    """Convert surfaces to a BEM."""
     # equivalent of mne_surf2bem
-    surfs = list()
-    assert len(fname_surfs) in (1, 3)
-    for fname in fname_surfs:
-        surfs.append(_read_surface_geom(fname, patch_stats=False,
-                                        verbose=False))
-        surfs[-1]['rr'] /= 1000.
+    # surfs can be strings (filenames) or surface dicts
+    if len(surfs) not in (1, 3) or not (len(surfs) == len(ids) ==
+                                        len(sigmas)):
+        raise ValueError('surfs, ids, and sigmas must all have the same '
+                         'number of elements (1 or 3)')
+    surf = list(surfs)
+    for si, surf in enumerate(surfs):
+        if isinstance(surf, string_types):
+            surfs[si] = read_surface(surf, return_dict=True)[-1]
     # Downsampling if the surface is isomorphic with a subdivided icosahedron
     if ico is not None:
         for si, surf in enumerate(surfs):
             surfs[si] = _ico_downsample(surf, ico)
     for surf, id_ in zip(surfs, ids):
         surf['id'] = id_
+        surf['coord_frame'] = surf.get('coord_frame', FIFF.FIFFV_COORD_MRI)
+        surf.update(np=len(surf['rr']), ntri=len(surf['tris']))
+        if rescale:
+            surf['rr'] /= 1000.  # convert to meters
 
-    # Shifting surfaces is not implemented here
+    # Shifting surfaces is not implemented here...
 
     # Order the surfaces for the benefit of the topology checks
     for surf, sigma in zip(surfs, sigmas):
@@ -498,7 +503,7 @@ def _surfaces_to_bem(fname_surfs, ids, sigmas, ico=None):
 @verbose
 def make_bem_model(subject, ico=4, conductivity=(0.3, 0.006, 0.3),
                    subjects_dir=None, verbose=None):
-    """Create a BEM model for a subject
+    """Create a BEM model for a subject.
 
     .. note:: To get a single layer bem corresponding to the --homog flag in
               the command line tool set the ``conductivity`` parameter
@@ -519,7 +524,8 @@ def make_bem_model(subject, ico=4, conductivity=(0.3, 0.006, 0.3),
     subjects_dir : string, or None
         Path to SUBJECTS_DIR if it is not set in the environment.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -566,7 +572,7 @@ def make_bem_model(subject, ico=4, conductivity=(0.3, 0.006, 0.3),
 # Compute EEG sphere model
 
 def _fwd_eeg_get_multi_sphere_model_coeffs(m, n_terms):
-    """Get the model depended weighting factor for n"""
+    """Get the model depended weighting factor for n."""
     nlayer = len(m['layers'])
     if nlayer in (0, 1):
         return 1.
@@ -601,6 +607,7 @@ def _fwd_eeg_get_multi_sphere_model_coeffs(m, n_terms):
 
 
 def _compose_linear_fitting_data(mu, u):
+    """Get the linear fitting data."""
     # y is the data to be fitted (nterms-1 x 1)
     # M is the model matrix      (nterms-1 x nfit-1)
     for k in range(u['nterms'] - 1):
@@ -612,7 +619,7 @@ def _compose_linear_fitting_data(mu, u):
 
 
 def _compute_linear_parameters(mu, u):
-    """Compute the best-fitting linear parameters"""
+    """Compute the best-fitting linear parameters."""
     _compose_linear_fitting_data(mu, u)
     uu, sing, vv = linalg.svd(u['M'], full_matrices=False)
 
@@ -638,7 +645,7 @@ def _compute_linear_parameters(mu, u):
 
 
 def _one_step(mu, u):
-    """Evaluate the residual sum of squares fit for one set of mu values"""
+    """Evaluate the residual sum of squares fit for one set of mu values."""
     if np.abs(mu).max() > 1.0:
         return 1.0
 
@@ -656,7 +663,7 @@ def _one_step(mu, u):
 
 
 def _fwd_eeg_fit_berg_scherg(m, nterms, nfit):
-    """Fit the Berg-Scherg equivalent spherical model dipole parameters"""
+    """Fit the Berg-Scherg equivalent spherical model dipole parameters."""
     from scipy.optimize import fmin_cobyla
     assert nfit >= 2
     u = dict(y=np.zeros(nterms - 1), resi=np.zeros(nterms - 1),
@@ -698,7 +705,7 @@ def _fwd_eeg_fit_berg_scherg(m, nterms, nfit):
 def make_sphere_model(r0=(0., 0., 0.04), head_radius=0.09, info=None,
                       relative_radii=(0.90, 0.92, 0.97, 1.0),
                       sigmas=(0.33, 1.0, 0.004, 0.33), verbose=None):
-    """Create a spherical model for forward solution calculation
+    """Create a spherical model for forward solution calculation.
 
     Parameters
     ----------
@@ -708,7 +715,7 @@ def make_sphere_model(r0=(0., 0., 0.04), head_radius=0.09, info=None,
     head_radius : float | str | None
         If float, compute spherical shells for EEG using the given radius.
         If 'auto', estimate an approriate radius from the dig points in Info,
-        If None, exclude shells.
+        If None, exclude shells (single layer sphere model).
     info : instance of Info | None
         Measurement info. Only needed if ``r0`` or ``head_radius`` are
         ``'auto'``.
@@ -717,7 +724,8 @@ def make_sphere_model(r0=(0., 0., 0.04), head_radius=0.09, info=None,
     sigmas : array-like
         Sigma values for the spherical shells.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -801,7 +809,7 @@ def make_sphere_model(r0=(0., 0., 0.04), head_radius=0.09, info=None,
 
 
 # #############################################################################
-# Helpers
+# Sphere fitting
 
 _dig_kind_dict = {
     'cardinal': FIFF.FIFFV_POINT_CARDINAL,
@@ -815,7 +823,7 @@ _dig_kind_ints = tuple(_dig_kind_dict.values())
 
 @verbose
 def fit_sphere_to_headshape(info, dig_kinds='auto', units='m', verbose=None):
-    """Fit a sphere to the headshape points to determine head center
+    """Fit a sphere to the headshape points to determine head center.
 
     Parameters
     ----------
@@ -825,14 +833,16 @@ def fit_sphere_to_headshape(info, dig_kinds='auto', units='m', verbose=None):
         Kind of digitization points to use in the fitting. These can be any
         combination of ('cardinal', 'hpi', 'eeg', 'extra'). Can also
         be 'auto' (default), which will use only the 'extra' points if
-        enough are available, and if not, uses 'extra' and 'eeg' points.
+        enough (more than 10) are available, and if not, uses 'extra' and
+        'eeg' points.
     units : str
         Can be "m" (default) or "mm".
 
         .. versionadded:: 0.12
 
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -850,6 +860,44 @@ def fit_sphere_to_headshape(info, dig_kinds='auto', units='m', verbose=None):
     """
     if not isinstance(units, string_types) or units not in ('m', 'mm'):
         raise ValueError('units must be a "m" or "mm"')
+    radius, origin_head, origin_device = _fit_sphere_to_headshape(
+        info, dig_kinds)
+    if units == 'mm':
+        radius *= 1e3
+        origin_head *= 1e3
+        origin_device *= 1e3
+    return radius, origin_head, origin_device
+
+
+ at verbose
+def get_fitting_dig(info, dig_kinds='auto', verbose=None):
+    """Get digitization points suitable for sphere fitting.
+
+    Parameters
+    ----------
+    info : instance of Info
+        The measurement info.
+    dig_kinds : list of str | str
+        Kind of digitization points to use in the fitting. These can be any
+        combination of ('cardinal', 'hpi', 'eeg', 'extra'). Can also
+        be 'auto' (default), which will use only the 'extra' points if
+        enough (more than 10) are available, and if not, uses 'extra' and
+        'eeg' points.
+    verbose : bool, str or None
+        If not None, override default verbose level
+
+    Returns
+    -------
+    dig : array, shape (n_pts, 3)
+        The digitization points (in head coordinates) to use for fitting.
+
+    Notes
+    -----
+    This will exclude digitization locations that have ``z < 0 and y > 0``,
+    i.e. points on the nose and below the nose on the face.
+
+    .. versionadded:: 0.14
+    """
     if not isinstance(info, Info):
         raise TypeError('info must be an instance of Info not %s' % type(info))
     if info['dig'] is None:
@@ -859,10 +907,10 @@ def fit_sphere_to_headshape(info, dig_kinds='auto', units='m', verbose=None):
         if dig_kinds == 'auto':
             # try "extra" first
             try:
-                return fit_sphere_to_headshape(info, 'extra', units=units)
+                return get_fitting_dig(info, 'extra')
             except ValueError:
                 pass
-            return fit_sphere_to_headshape(info, ('extra', 'eeg'), units=units)
+            return get_fitting_dig(info, ('extra', 'eeg'))
         else:
             dig_kinds = (dig_kinds,)
     # convert string args to ints (first make dig_kinds mutable in case tuple)
@@ -880,51 +928,49 @@ def fit_sphere_to_headshape(info, dig_kinds='auto', units='m', verbose=None):
                            'contact mne-python developers')
 
     # exclude some frontal points (nose etc.)
-    hsp = [p for p in hsp if not (p[2] < 0 and p[1] > 0)]
+    hsp = np.array([p for p in hsp if not (p[2] < -1e-6 and p[1] > 1e-6)])
 
     if len(hsp) <= 10:
         kinds_str = ', '.join(['"%s"' % _dig_kind_rev[d]
                                for d in sorted(dig_kinds)])
         msg = ('Only %s head digitization points of the specified kind%s (%s,)'
-               % (len(hsp), 's' if len(dig_kinds) != 1 else '', kinds_str))
+               % (len(hsp), _pl(dig_kinds), kinds_str))
         if len(hsp) < 4:
             raise ValueError(msg + ', at least 4 required')
         else:
             warn(msg + ', fitting may be inaccurate')
+    return hsp
+
 
+ at verbose
+def _fit_sphere_to_headshape(info, dig_kinds, verbose=None):
+    """Fit a sphere to the given head shape."""
+    hsp = get_fitting_dig(info, dig_kinds)
     radius, origin_head = _fit_sphere(np.array(hsp), disp=False)
     # compute origin in device coordinates
     head_to_dev = _ensure_trans(info['dev_head_t'], 'head', 'meg')
     origin_device = apply_trans(head_to_dev, origin_head)
-    radius *= 1e3
-    origin_head *= 1e3
-    origin_device *= 1e3
-
-    logger.info('Fitted sphere radius:'.ljust(30) + '%0.1f mm' % radius)
+    logger.info('Fitted sphere radius:'.ljust(30) + '%0.1f mm'
+                % (radius * 1e3,))
     # 99th percentile on Wikipedia for Giabella to back of head is 21.7cm,
     # i.e. 108mm "radius", so let's go with 110mm
     # en.wikipedia.org/wiki/Human_head#/media/File:HeadAnthropometry.JPG
-    if radius > 110.:
+    if radius > 0.110:
         warn('Estimated head size (%0.1f mm) exceeded 99th '
-             'percentile for adult head size' % (radius,))
+             'percentile for adult head size' % (1e3 * radius,))
     # > 2 cm away from head center in X or Y is strange
-    if np.sqrt(np.sum(origin_head[:2] ** 2)) > 20:
+    if np.linalg.norm(origin_head[:2]) > 0.02:
         warn('(X, Y) fit (%0.1f, %0.1f) more than 20 mm from '
-             'head frame origin' % tuple(origin_head[:2]))
+             'head frame origin' % tuple(1e3 * origin_head[:2]))
     logger.info('Origin head coordinates:'.ljust(30) +
-                '%0.1f %0.1f %0.1f mm' % tuple(origin_head))
+                '%0.1f %0.1f %0.1f mm' % tuple(1e3 * origin_head))
     logger.info('Origin device coordinates:'.ljust(30) +
-                '%0.1f %0.1f %0.1f mm' % tuple(origin_device))
-    if units == 'm':
-        radius /= 1e3
-        origin_head /= 1e3
-        origin_device /= 1e3
-
+                '%0.1f %0.1f %0.1f mm' % tuple(1e3 * origin_device))
     return radius, origin_head, origin_device
 
 
 def _fit_sphere(points, disp='auto'):
-    """Aux function to fit a sphere to an arbitrary set of points"""
+    """Fit a sphere to an arbitrary set of points."""
     from scipy.optimize import fmin_cobyla
     if isinstance(disp, string_types) and disp == 'auto':
         disp = True if logger.level <= 20 else False
@@ -937,9 +983,9 @@ def _fit_sphere(points, disp='auto'):
     x0 = np.concatenate([center_init, [radius_init]])
 
     def cost_fun(center_rad):
-        d = points - center_rad[:3]
-        d = (np.sqrt(np.sum(d * d, axis=1)) - center_rad[3])
-        return np.sum(d * d)
+        d = np.linalg.norm(points - center_rad[:3], axis=1) - center_rad[3]
+        d *= d
+        return d.sum()
 
     def constraint(center_rad):
         return center_rad[3]  # radius must be >= 0
@@ -953,7 +999,7 @@ def _fit_sphere(points, disp='auto'):
 
 
 def _check_origin(origin, info, coord_frame='head', disp=False):
-    """Helper to check or auto-determine the origin"""
+    """Check or auto-determine the origin."""
     if isinstance(origin, string_types):
         if origin != 'auto':
             raise ValueError('origin must be a numerical array, or "auto", '
@@ -983,8 +1029,7 @@ def _check_origin(origin, info, coord_frame='head', disp=False):
 def make_watershed_bem(subject, subjects_dir=None, overwrite=False,
                        volume='T1', atlas=False, gcaatlas=False, preflood=None,
                        show=False, verbose=None):
-    """
-    Create BEM surfaces using the watershed algorithm included with FreeSurfer
+    """Create BEM surfaces using the FreeSurfer watershed algorithm.
 
     Parameters
     ----------
@@ -1015,11 +1060,9 @@ def make_watershed_bem(subject, subjects_dir=None, overwrite=False,
     -----
     .. versionadded:: 0.10
     """
-    from .surface import read_surface, write_surface, _read_surface_geom
     from .viz.misc import plot_bem
     env, mri_dir = _prepare_env(subject, subjects_dir,
-                                requires_freesurfer=True,
-                                requires_mne=True)[:2]
+                                requires_freesurfer=True)[:2]
 
     subjects_dir = env['SUBJECTS_DIR']
     subject_dir = op.join(subjects_dir, subject)
@@ -1062,7 +1105,7 @@ def make_watershed_bem(subject, subjects_dir=None, overwrite=False,
                 'SUBJECT = %s\n'
                 'Results dir = %s\n' % (subjects_dir, subject, ws_dir))
     os.makedirs(op.join(ws_dir, 'ws'))
-    run_subprocess(cmd, env=env, stdout=sys.stdout, stderr=sys.stderr)
+    run_subprocess(cmd, env=env)
 
     if op.isfile(T1_mgz):
         new_info = _extract_volume_info(T1_mgz)
@@ -1074,11 +1117,11 @@ def make_watershed_bem(subject, subjects_dir=None, overwrite=False,
         for s in surfs:
             surf_ws_out = op.join(ws_dir, '%s_%s_surface' % (subject, s))
 
-            surf, volume_info = _read_surface_geom(surf_ws_out,
-                                                   read_metadata=True)
+            rr, tris, volume_info = read_surface(surf_ws_out,
+                                                 read_metadata=True)
             volume_info.update(new_info)  # replace volume info, 'head' stays
 
-            write_surface(s, surf['rr'], surf['tris'], volume_info=volume_info)
+            write_surface(s, rr, tris, volume_info=volume_info)
             # Create symbolic links
             surf_out = op.join(bem_dir, '%s.surf' % s)
             if not overwrite and op.exists(surf_out):
@@ -1106,12 +1149,8 @@ def make_watershed_bem(subject, subjects_dir=None, overwrite=False,
     if op.isfile(fname_head):
         os.remove(fname_head)
 
-    # run the equivalent of mne_surf2bem
-    points, tris = read_surface(op.join(ws_dir,
-                                        subject + '_outer_skin_surface'))
-    points *= 1e-3
-    surf = dict(coord_frame=5, id=4, nn=None, np=len(points),
-                ntri=len(tris), rr=points, sigma=1, tris=tris)
+    surf = _surfaces_to_bem([op.join(ws_dir, subject + '_outer_skin_surface')],
+                            [FIFF.FIFFV_BEM_SURF_ID_HEAD], sigmas=[1])
     write_bem_surfaces(fname_head, surf)
 
     # Show computed BEM surfaces
@@ -1123,25 +1162,25 @@ def make_watershed_bem(subject, subjects_dir=None, overwrite=False,
 
 
 def _extract_volume_info(mgz, raise_error=True):
-    """Helper for extracting volume info from a mgz file."""
+    """Extract volume info from a mgz file."""
     try:
         import nibabel as nib
     except ImportError:
         return  # warning raised elsewhere
     header = nib.load(mgz).header
-    new_info = dict()
+    vol_info = dict()
     version = header['version']
     if version == 1:
         version = '%s  # volume info valid' % version
     else:
         raise ValueError('Volume info invalid.')
-    new_info['valid'] = version
-    new_info['filename'] = mgz
-    new_info['volume'] = header['dims'][:3]
-    new_info['voxelsize'] = header['delta']
-    new_info['xras'], new_info['yras'], new_info['zras'] = header['Mdc'].T
-    new_info['cras'] = header['Pxyz_c']
-    return new_info
+    vol_info['valid'] = version
+    vol_info['filename'] = mgz
+    vol_info['volume'] = header['dims'][:3]
+    vol_info['voxelsize'] = header['delta']
+    vol_info['xras'], vol_info['yras'], vol_info['zras'] = header['Mdc'].T
+    vol_info['cras'] = header['Pxyz_c']
+    return vol_info
 
 
 # ############################################################################
@@ -1149,7 +1188,7 @@ def _extract_volume_info(mgz, raise_error=True):
 
 @verbose
 def read_bem_surfaces(fname, patch_stats=False, s_id=None, verbose=None):
-    """Read the BEM surfaces from a FIF file
+    """Read the BEM surfaces from a FIF file.
 
     Parameters
     ----------
@@ -1162,7 +1201,8 @@ def read_bem_surfaces(fname, patch_stats=False, s_id=None, verbose=None):
         An error will be raised if it doesn't exist. If None, all
         surfaces are read and returned.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -1174,7 +1214,6 @@ def read_bem_surfaces(fname, patch_stats=False, s_id=None, verbose=None):
     --------
     write_bem_surfaces, write_bem_solution, make_bem_model
     """
-    from .surface import _complete_surface_info
     # Default coordinate frame
     coord_frame = FIFF.FIFFV_COORD_MRI
     # Open the file, create directory
@@ -1211,15 +1250,14 @@ def read_bem_surfaces(fname, patch_stats=False, s_id=None, verbose=None):
                 surf.append(this)
                 logger.info('[done]')
             logger.info('    %d BEM surfaces read' % len(surf))
-        if patch_stats:
-            for this in surf:
-                _complete_surface_info(this)
+        for this in surf:
+            if patch_stats or this['nn'] is None:
+                complete_surface_info(this, copy=False)
     return surf[0] if s_id is not None else surf
 
 
 def _read_bem_surface(fid, this, def_coord_frame, s_id=None):
-    """Read one bem surface
-    """
+    """Read one bem surface."""
     # fid should be open as a context manager here
     res = dict()
     # Read all the interesting stuff
@@ -1270,7 +1308,7 @@ def _read_bem_surface(fid, this, def_coord_frame, s_id=None):
     if tag is None:
         tag = find_tag(fid, this, FIFF.FIFF_BEM_SURF_NORMALS)
     if tag is None:
-        res['nn'] = list()
+        res['nn'] = None
     else:
         res['nn'] = tag.data
         if res['nn'].shape[0] != res['np']:
@@ -1289,14 +1327,15 @@ def _read_bem_surface(fid, this, def_coord_frame, s_id=None):
 
 @verbose
 def read_bem_solution(fname, verbose=None):
-    """Read the BEM solution from a file
+    """Read the BEM solution from a file.
 
     Parameters
     ----------
     fname : string
         The file containing the BEM solution.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -1383,7 +1422,7 @@ def read_bem_solution(fname, verbose=None):
 
 
 def _add_gamma_multipliers(bem):
-    """Helper to add gamma and multipliers in-place"""
+    """Add gamma and multipliers in-place."""
     bem['sigma'] = np.array([surf['sigma'] for surf in bem['surfs']])
     # Dirty trick for the zero conductivity outside
     sigma = np.r_[0.0, bem['sigma']]
@@ -1401,7 +1440,7 @@ _surf_dict = {'inner_skull': FIFF.FIFFV_BEM_SURF_ID_BRAIN,
 
 
 def _bem_find_surface(bem, id_):
-    """Find surface from already-loaded BEM"""
+    """Find surface from already-loaded BEM."""
     if isinstance(id_, string_types):
         name = id_
         id_ = _surf_dict[id_]
@@ -1415,7 +1454,7 @@ def _bem_find_surface(bem, id_):
 
 
 def _bem_explain_surface(id_):
-    """Return a string corresponding to the given surface ID"""
+    """Return a string corresponding to the given surface ID."""
     _rev_dict = dict((val, key) for key, val in _surf_dict.items())
     return _rev_dict[id_]
 
@@ -1424,7 +1463,7 @@ def _bem_explain_surface(id_):
 # Write
 
 def write_bem_surfaces(fname, surfs):
-    """Write BEM surfaces to a fiff file
+    """Write BEM surfaces to a fiff file.
 
     Parameters
     ----------
@@ -1444,7 +1483,7 @@ def write_bem_surfaces(fname, surfs):
 
 
 def _write_bem_surfaces_block(fid, surfs):
-    """Helper to actually write bem surfaces"""
+    """Write bem surfaces to open file handle."""
     for surf in surfs:
         start_block(fid, FIFF.FIFFB_BEM_SURF)
         write_float(fid, FIFF.FIFF_BEM_SIGMA, surf['sigma'])
@@ -1462,7 +1501,7 @@ def _write_bem_surfaces_block(fid, surfs):
 
 
 def write_bem_solution(fname, bem):
-    """Write a BEM model with solution
+    """Write a BEM model with solution.
 
     Parameters
     ----------
@@ -1497,15 +1536,12 @@ def write_bem_solution(fname, bem):
 # #############################################################################
 # Create 3-Layers BEM model from Flash MRI images
 
-def _prepare_env(subject, subjects_dir, requires_freesurfer, requires_mne):
-    """Helper to prepare an env object for subprocess calls"""
+def _prepare_env(subject, subjects_dir, requires_freesurfer):
+    """Prepare an env object for subprocess calls."""
     env = os.environ.copy()
     if requires_freesurfer and not os.environ.get('FREESURFER_HOME'):
         raise RuntimeError('I cannot find freesurfer. The FREESURFER_HOME '
                            'environment variable is not set.')
-    if requires_mne and not os.environ.get('MNE_ROOT'):
-        raise RuntimeError('I cannot find the MNE command line tools. The '
-                           'MNE_ROOT environment variable is not set.')
 
     if not isinstance(subject, string_types):
         raise TypeError('The subject argument must be set')
@@ -1528,7 +1564,7 @@ def _prepare_env(subject, subjects_dir, requires_freesurfer, requires_mne):
 @verbose
 def convert_flash_mris(subject, flash30=True, convert=True, unwarp=False,
                        subjects_dir=None, verbose=None):
-    """Convert DICOM files for use with make_flash_bem
+    """Convert DICOM files for use with make_flash_bem.
 
     Parameters
     ----------
@@ -1546,7 +1582,8 @@ def convert_flash_mris(subject, flash30=True, convert=True, unwarp=False,
     subjects_dir : string, or None
         Path to SUBJECTS_DIR if it is not set in the environment.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Notes
     -----
@@ -1575,8 +1612,7 @@ def convert_flash_mris(subject, flash30=True, convert=True, unwarp=False,
     should be, as usual, in the subject's mri directory.
     """
     env, mri_dir = _prepare_env(subject, subjects_dir,
-                                requires_freesurfer=True,
-                                requires_mne=False)[:2]
+                                requires_freesurfer=True)[:2]
     curdir = os.getcwd()
     # Step 1a : Data conversion to mgz format
     if not op.exists(op.join(mri_dir, 'flash', 'parameter_maps')):
@@ -1616,8 +1652,7 @@ def convert_flash_mris(subject, flash30=True, convert=True, unwarp=False,
                     logger.info("The file %s is already there")
                 else:
                     cmd = ['mri_convert', sample_file, dest_file]
-                    run_subprocess(cmd, env=env, stdout=sys.stdout,
-                                   stderr=sys.stderr)
+                    run_subprocess(cmd, env=env)
                     echos_done += 1
     # Step 1b : Run grad_unwarp on converted files
     os.chdir(op.join(mri_dir, "flash"))
@@ -1628,7 +1663,7 @@ def convert_flash_mris(subject, flash30=True, convert=True, unwarp=False,
             outfile = infile.replace(".mgz", "u.mgz")
             cmd = ['grad_unwarp', '-i', infile, '-o', outfile, '-unwarp',
                    'true']
-            run_subprocess(cmd, env=env, stdout=sys.stdout, stderr=sys.stderr)
+            run_subprocess(cmd, env=env)
     # Clear parameter maps if some of the data were reconverted
     if echos_done > 0 and op.exists("parameter_maps"):
         shutil.rmtree("parameter_maps")
@@ -1642,7 +1677,7 @@ def convert_flash_mris(subject, flash30=True, convert=True, unwarp=False,
             files = glob.glob("mef05*u.mgz")
         if len(os.listdir('parameter_maps')) == 0:
             cmd = ['mri_ms_fitparms'] + files + ['parameter_maps']
-            run_subprocess(cmd, env=env, stdout=sys.stdout, stderr=sys.stderr)
+            run_subprocess(cmd, env=env)
         else:
             logger.info("Parameter maps were already computed")
         # Step 3 : Synthesize the flash 5 images
@@ -1651,7 +1686,7 @@ def convert_flash_mris(subject, flash30=True, convert=True, unwarp=False,
         if not op.exists('flash5.mgz'):
             cmd = ['mri_synthesize', '20 5 5', 'T1.mgz', 'PD.mgz',
                    'flash5.mgz']
-            run_subprocess(cmd, env=env, stdout=sys.stdout, stderr=sys.stderr)
+            run_subprocess(cmd, env=env)
             os.remove('flash5_reg.mgz')
         else:
             logger.info("Synthesized flash 5 volume is already there")
@@ -1663,7 +1698,7 @@ def convert_flash_mris(subject, flash30=True, convert=True, unwarp=False,
         else:
             files = glob.glob("mef05*.mgz")
         cmd = ['mri_average', '-noconform', files, 'flash5.mgz']
-        run_subprocess(cmd, env=env, stdout=sys.stdout)
+        run_subprocess(cmd, env=env)
         if op.exists('flash5_reg.mgz'):
             os.remove('flash5_reg.mgz')
 
@@ -1674,10 +1709,10 @@ def convert_flash_mris(subject, flash30=True, convert=True, unwarp=False,
 @verbose
 def make_flash_bem(subject, overwrite=False, show=True, subjects_dir=None,
                    flash_path=None, verbose=None):
-    """Create 3-Layer BEM model from prepared flash MRI images
+    """Create 3-Layer BEM model from prepared flash MRI images.
 
     Parameters
-    -----------
+    ----------
     subject : str
         Subject name.
     overwrite : bool
@@ -1693,7 +1728,8 @@ def make_flash_bem(subject, overwrite=False, show=True, subjects_dir=None,
         .. versionadded:: 0.13.0
 
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Notes
     -----
@@ -1708,13 +1744,11 @@ def make_flash_bem(subject, overwrite=False, show=True, subjects_dir=None,
     convert_flash_mris
     """
     from .viz.misc import plot_bem
-    from .surface import write_surface, read_tri
 
     is_test = os.environ.get('MNE_SKIP_FS_FLASH_CALL', False)
 
     env, mri_dir, bem_dir = _prepare_env(subject, subjects_dir,
-                                         requires_freesurfer=True,
-                                         requires_mne=False)
+                                         requires_freesurfer=True)
 
     if flash_path is None:
         flash_path = op.join(mri_dir, 'flash', 'parameter_maps')
@@ -1740,7 +1774,7 @@ def make_flash_bem(subject, overwrite=False, show=True, subjects_dir=None,
             ref_volume = op.join(mri_dir, 'T1')
         cmd = ['fsl_rigid_register', '-r', ref_volume, '-i', flash5,
                '-o', flash5_reg]
-        run_subprocess(cmd, env=env, stdout=sys.stdout, stderr=sys.stderr)
+        run_subprocess(cmd, env=env)
     else:
         logger.info("Registered flash 5 image is already there")
     # Step 5a : Convert flash5 into COR
@@ -1749,7 +1783,7 @@ def make_flash_bem(subject, overwrite=False, show=True, subjects_dir=None,
     os.makedirs(op.join(mri_dir, 'flash5'))
     if not is_test:  # CIs don't have freesurfer, skipped when testing.
         cmd = ['mri_convert', flash5_reg, op.join(mri_dir, 'flash5')]
-        run_subprocess(cmd, env=env, stdout=sys.stdout, stderr=sys.stderr)
+        run_subprocess(cmd, env=env)
     # Step 5b and c : Convert the mgz volumes into COR
     os.chdir(mri_dir)
     convert_T1 = False
@@ -1764,7 +1798,7 @@ def make_flash_bem(subject, overwrite=False, show=True, subjects_dir=None,
             raise RuntimeError("Both T1 mgz and T1 COR volumes missing.")
         os.makedirs('T1')
         cmd = ['mri_convert', 'T1.mgz', 'T1']
-        run_subprocess(cmd, env=env, stdout=sys.stdout, stderr=sys.stderr)
+        run_subprocess(cmd, env=env)
     else:
         logger.info("T1 volume is already in COR format")
     logger.info("\n---- Converting brain volume into COR format ----")
@@ -1773,14 +1807,14 @@ def make_flash_bem(subject, overwrite=False, show=True, subjects_dir=None,
             raise RuntimeError("Both brain mgz and brain COR volumes missing.")
         os.makedirs('brain')
         cmd = ['mri_convert', 'brain.mgz', 'brain']
-        run_subprocess(cmd, env=env, stdout=sys.stdout, stderr=sys.stderr)
+        run_subprocess(cmd, env=env)
     else:
         logger.info("Brain volume is already in COR format")
     # Finally ready to go
     if not is_test:  # CIs don't have freesurfer, skipped when testing.
         logger.info("\n---- Creating the BEM surfaces ----")
         cmd = ['mri_make_bem_surfaces', subject]
-        run_subprocess(cmd, env=env, stdout=sys.stdout, stderr=sys.stderr)
+        run_subprocess(cmd, env=env)
 
     logger.info("\n---- Converting the tri files into surf files ----")
     os.chdir(bem_dir)
@@ -1846,16 +1880,15 @@ def make_flash_bem(subject, overwrite=False, show=True, subjects_dir=None,
 
 
 def _check_bem_size(surfs):
-    """Helper for checking bem surface sizes."""
-    if surfs[0]['np'] > 10000:
-        msg = ('The bem surface has %s data points. 5120 (ico grade=4) should '
-               'be enough.' % surfs[0]['np'])
-        if len(surfs) == 3:
-            msg += ' Dense 3-layer bems may not save properly.'
-        warn(msg)
+    """Check bem surface sizes."""
+    if len(surfs) > 1 and surfs[0]['np'] > 10000:
+        warn('The bem surfaces have %s data points. 5120 (ico grade=4) '
+             'should be enough. Dense 3-layer bems may not save properly.' %
+             surfs[0]['np'])
 
 
 def _symlink(src, dest):
+    """Create a symlink."""
     try:
         os.symlink(src, dest)
     except OSError:
diff --git a/mne/channels/__init__.py b/mne/channels/__init__.py
index 025538f..fedcae9 100644
--- a/mne/channels/__init__.py
+++ b/mne/channels/__init__.py
@@ -1,11 +1,12 @@
-"""
-Module dedicated to the manipulation of channels,
-setting of sensors locations used for processing and plotting.
+"""Module dedicated to manipulation of channels.
+
+Can be used for setting of sensor locations used for processing and plotting.
 """
 
 from .layout import (Layout, make_eeg_layout, make_grid_layout, read_layout,
                      find_layout, generate_2d_layout)
-from .montage import read_montage, read_dig_montage, Montage, DigMontage
-
+from .montage import (read_montage, read_dig_montage, Montage, DigMontage,
+                      get_builtin_montages)
 from .channels import (equalize_channels, rename_channels, fix_mag_coil_types,
-                       read_ch_connectivity, _get_ch_type)
+                       read_ch_connectivity, _get_ch_type,
+                       find_ch_connectivity)
diff --git a/mne/channels/channels.py b/mne/channels/channels.py
index 1d55dc6..c842c3e 100644
--- a/mne/channels/channels.py
+++ b/mne/channels/channels.py
@@ -13,17 +13,18 @@ import numpy as np
 from scipy import sparse
 
 from ..externals.six import string_types
-
 from ..utils import verbose, logger, warn, copy_function_doc_to_method_doc
+from ..utils import _check_preload
 from ..io.compensator import get_current_comp
 from ..io.constants import FIFF
-from ..io.meas_info import anonymize_info
-from ..io.pick import (channel_type, pick_info, pick_types,
-                       _check_excludes_includes, _PICK_TYPES_KEYS)
+from ..io.meas_info import anonymize_info, Info
+from ..io.pick import (channel_type, pick_info, pick_types, _picks_by_type,
+                       _check_excludes_includes, _PICK_TYPES_KEYS,
+                       channel_indices_by_type)
 
 
 def _get_meg_system(info):
-    """Educated guess for the helmet type based on channels"""
+    """Educated guess for the helmet type based on channels."""
     system = '306m'
     for ch in info['chs']:
         if ch['kind'] == FIFF.FIFFV_MEG_CH:
@@ -54,10 +55,10 @@ def _get_meg_system(info):
 
 
 def _contains_ch_type(info, ch_type):
-    """Check whether a certain channel type is in an info object
+    """Check whether a certain channel type is in an info object.
 
     Parameters
-    ---------
+    ----------
     info : instance of Info
         The measurement information.
     ch_type : str
@@ -86,14 +87,18 @@ def _contains_ch_type(info, ch_type):
 
 
 def _get_ch_type(inst, ch_type):
-    """Helper to choose a single channel type (usually for plotting)
+    """Choose a single channel type (usually for plotting).
 
     Usually used in plotting to plot a single datatype, e.g. look for mags,
     then grads, then ... to plot.
     """
     if ch_type is None:
         for type_ in ['mag', 'grad', 'planar1', 'planar2', 'eeg']:
-            if type_ in inst:
+            if isinstance(inst, Info):
+                if _contains_ch_type(inst, type_):
+                    ch_type = type_
+                    break
+            elif type_ in inst:
                 ch_type = type_
                 break
         else:
@@ -103,28 +108,29 @@ def _get_ch_type(inst, ch_type):
 
 @verbose
 def equalize_channels(candidates, verbose=None):
-    """Equalize channel picks for a collection of MNE-Python objects
+    """Equalize channel picks for a collection of MNE-Python objects.
 
     Parameters
     ----------
     candidates : list
         list Raw | Epochs | Evoked | AverageTFR
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Notes
     -----
     This function operates inplace.
     """
-    from ..io.base import _BaseRaw
-    from ..epochs import _BaseEpochs
+    from ..io.base import BaseRaw
+    from ..epochs import BaseEpochs
     from ..evoked import Evoked
     from ..time_frequency import AverageTFR
 
-    if not all(isinstance(c, (_BaseRaw, _BaseEpochs, Evoked, AverageTFR))
+    if not all(isinstance(c, (BaseRaw, BaseEpochs, Evoked, AverageTFR))
                for c in candidates):
-        valid = ['Raw', 'Epochs', 'Evoked', 'AverageTFR']
-        raise ValueError('candidates must be ' + ' or '.join(valid))
+        raise ValueError('candidates must be Raw, Epochs, Evoked, or '
+                         'AverageTFR')
 
     chan_max_idx = np.argmax([c.info['nchan'] for c in candidates])
     chan_template = candidates[chan_max_idx].ch_names
@@ -145,10 +151,10 @@ def equalize_channels(candidates, verbose=None):
 
 
 class ContainsMixin(object):
-    """Mixin class for Raw, Evoked, Epochs
-    """
+    """Mixin class for Raw, Evoked, Epochs."""
+
     def __contains__(self, ch_type):
-        """Check channel type membership
+        """Check channel type membership.
 
         Parameters
         ----------
@@ -179,7 +185,7 @@ class ContainsMixin(object):
 
     @property
     def compensation_grade(self):
-        """The current gradient compensation grade"""
+        """The current gradient compensation grade."""
         return get_current_comp(self.info)
 
 
@@ -222,7 +228,7 @@ _unit2human = {FIFF.FIFF_UNIT_V: 'V',
 
 
 def _check_set(ch, projs, ch_type):
-    """Helper to make sure type change is compatible with projectors"""
+    """Ensure type change is compatible with projectors."""
     new_kind = _human2fiff[ch_type]
     if ch['kind'] != new_kind:
         for proj in projs:
@@ -236,29 +242,87 @@ def _check_set(ch, projs, ch_type):
 class SetChannelsMixin(object):
     """Mixin class for Raw, Evoked, Epochs."""
 
-    def set_eeg_reference(self, ref_channels=None):
-        """Rereference EEG channels to new reference channel(s).
-
-        If multiple reference channels are specified, they will be averaged. If
-        no reference channels are specified, an average reference will be
-        applied.
+    @verbose
+    def set_eeg_reference(self, ref_channels='average', projection=None,
+                          verbose=None):
+        """Specify which reference to use for EEG data.
+
+        By default, MNE-Python will automatically re-reference the EEG signal
+        to use an average reference (see below). Use this function to
+        explicitly specify the desired reference for EEG. This can be either an
+        existing electrode or a new virtual channel. This function will
+        re-reference the data according to the desired reference and prevent
+        MNE-Python from automatically adding an average reference projection.
+
+        Some common referencing schemes and the corresponding value for the
+        ``ref_channels`` parameter:
+
+        No re-referencing:
+            If the EEG data is already using the proper reference, set
+            ``ref_channels=[]``. This will prevent MNE-Python from
+            automatically adding an average reference projection.
+
+        Average reference:
+            A new virtual reference electrode is created by averaging the
+            current EEG signal by setting ``ref_channels='average'``. Bad EEG
+            channels are automatically excluded if they are properly set in
+            ``info['bads']``.
+
+        A single electrode:
+            Set ``ref_channels`` to a list containing the name of the channel
+            that will act as the new reference, for example
+            ``ref_channels=['Cz']``.
+
+        The mean of multiple electrodes:
+            A new virtual reference electrode is created by computing the
+            average of the current EEG signal recorded from two or more
+            selected channels. Set ``ref_channels`` to a list of channel names,
+            indicating which channels to use. For example, to apply an average
+            mastoid reference, when using the 10-20 naming scheme, set
+            ``ref_channels=['M1', 'M2']``.
+
+        .. note:: In case of ``ref_channels='average'`` in combination with
+                  ``projection=True``, the reference is added as a projection
+                  and it is not applied automatically. For it to take effect,
+                  apply with method :meth:`apply_proj <mne.io.Raw.apply_proj>`.
+                  Other references are directly applied (this behavior will
+                  change in MNE 0.16).
 
         Parameters
         ----------
-        ref_channels : list of str | None
-            The names of the channels to use to construct the reference. If
-            None (default), an average reference will be added as an SSP
-            projector but not immediately applied to the data. If an empty list
-            is specified, the data is assumed to already have a proper
-            reference and MNE will not attempt any re-referencing of the data.
-            Defaults to an average reference (None).
+        ref_channels : list of str | str
+            The name(s) of the channel(s) used to construct the reference. To
+            apply an average reference, specify ``'average'`` here (default).
+            If an empty list is specified, the data is assumed to already have
+            a proper reference and MNE will not attempt any re-referencing of
+            the data. Defaults to an average reference.
+        projection : bool | None
+            If ``ref_channels='average'`` this argument specifies if the
+            average reference should be computed as a projection (True) or not
+            (False). If ``projection=True``, the average reference is added as
+            a projection and is not applied to the data (it can be applied
+            afterwards with the ``apply_proj`` method). If
+            ``projection=False``, the average reference is directly applied to
+            the data. Defaults to None, which means ``projection=True``, but
+            will change to ``projection=False`` in the next release.
+            If ``ref_channels`` is not ``'average'``, ``projection`` must be
+            set to ``False`` (the default in this case).
+        verbose : bool, str, int, or None
+            If not None, override default verbose level (see
+            :func:`mne.verbose` and :ref:`Logging documentation <tut_logging>`
+            for more).
 
         Returns
         -------
         inst : instance of Raw | Epochs | Evoked
-            Data with EEG channels re-referenced. For ``ref_channels=None``,
-            an average projector will be added instead of directly subtarcting
-            data.
+            Data with EEG channels re-referenced. If ``ref_channels='average'``
+            and ``projection=True`` a projection will be added instead of
+            directly re-referencing the data.
+
+        See Also
+        --------
+        mne.set_bipolar_reference : Convenience function for creating bipolar
+                                    references.
 
         Notes
         -----
@@ -268,20 +332,20 @@ class SetChannelsMixin(object):
         2. During source localization, the EEG signal should have an average
            reference.
 
-        3. In order to apply a reference other than an average reference, the
-           data must be preloaded.
+        3. In order to apply a reference, the data must be preloaded. This is
+           not necessary if ``ref_channels='average'`` and ``projection=True``.
 
-        .. versionadded:: 0.13.0
+        4. For an average reference, bad EEG channels are automatically
+           excluded if they are properly set in ``info['bads']``.
 
-        See Also
-        --------
-        mne.set_bipolar_reference
+        .. versionadded:: 0.9.0
         """
         from ..io.reference import set_eeg_reference
-        return set_eeg_reference(self, ref_channels, copy=False)[0]
+        return set_eeg_reference(self, ref_channels=ref_channels, copy=False,
+                                 projection=projection)[0]
 
     def _get_channel_positions(self, picks=None):
-        """Gets channel locations from info
+        """Get channel locations from info.
 
         Parameters
         ----------
@@ -304,7 +368,7 @@ class SetChannelsMixin(object):
         return pos
 
     def _set_channel_positions(self, pos, names):
-        """Update channel locations in info
+        """Update channel locations in info.
 
         Parameters
         ----------
@@ -354,6 +418,7 @@ class SetChannelsMixin(object):
         ch_names = self.info['ch_names']
 
         # first check and assemble clean mappings of index and name
+        unit_changes = dict()
         for ch_name, ch_type in mapping.items():
             if ch_name not in ch_names:
                 raise ValueError("This channel name (%s) doesn't exist in "
@@ -375,8 +440,10 @@ class SetChannelsMixin(object):
                                  "fix the measurement info of your data."
                                  % (ch_name, unit_old))
             if unit_old != _human2unit[ch_type]:
-                warn("The unit for channel %s has changed from %s to %s."
-                     % (ch_name, _unit2human[unit_old], _unit2human[unit_new]))
+                this_change = (_unit2human[unit_old], _unit2human[unit_new])
+                if this_change not in unit_changes:
+                    unit_changes[this_change] = list()
+                unit_changes[this_change].append(ch_name)
             self.info['chs'][c_ind]['unit'] = _human2unit[ch_type]
             if ch_type in ['eeg', 'seeg', 'ecog']:
                 coil_type = FIFF.FIFFV_COIL_EEG
@@ -387,6 +454,9 @@ class SetChannelsMixin(object):
             else:
                 coil_type = FIFF.FIFFV_COIL_NONE
             self.info['chs'][c_ind]['coil_type'] = coil_type
+        msg = "The unit for channel(s) {0} has changed from {1} to {2}."
+        for this_change, names in unit_changes.items():
+            warn(msg.format(", ".join(sorted(names)), *this_change))
 
     def rename_channels(self, mapping):
         """Rename channels.
@@ -405,15 +475,22 @@ class SetChannelsMixin(object):
         rename_channels(self.info, mapping)
 
     @verbose
-    def set_montage(self, montage, verbose=None):
-        """Set EEG sensor configuration
+    def set_montage(self, montage, set_dig=True, verbose=None):
+        """Set EEG sensor configuration and head digitization.
 
         Parameters
         ----------
         montage : instance of Montage or DigMontage
             The montage to use.
+        set_dig : bool
+            If True, update the digitization information (``info['dig']``)
+            in addition to the channel positions (``info['chs'][idx]['loc']``).
+
+            .. versionadded: 0.15
         verbose : bool, str, int, or None
-            If not None, override default verbose level (see mne.verbose).
+            If not None, override default verbose level (see
+            :func:`mne.verbose` and :ref:`Logging documentation <tut_logging>`
+            for more).
 
         Notes
         -----
@@ -422,13 +499,13 @@ class SetChannelsMixin(object):
         .. versionadded:: 0.9.0
         """
         from .montage import _set_montage
-        _set_montage(self.info, montage)
+        _set_montage(self.info, montage, set_dig=set_dig)
+        return self
 
     def plot_sensors(self, kind='topomap', ch_type=None, title=None,
-                     show_names=False, ch_groups=None, axes=None, block=False,
-                     show=True):
-        """
-        Plot sensors positions.
+                     show_names=False, ch_groups=None, to_sphere=True,
+                     axes=None, block=False, show=True):
+        """Plot sensor positions.
 
         Parameters
         ----------
@@ -447,8 +524,9 @@ class SetChannelsMixin(object):
         title : str | None
             Title for the figure. If None (default), equals to ``'Sensor
             positions (%s)' % ch_type``.
-        show_names : bool
-            Whether to display all channel names. Defaults to False.
+        show_names : bool | array of str
+            Whether to display all channel names. If an array, only the channel
+            names in the array are shown. Defaults to False.
         ch_groups : 'position' | array of shape (ch_groups, picks) | None
             Channel groups for coloring the sensors. If None (default), default
             coloring scheme is used. If 'position', the sensors are divided
@@ -457,6 +535,13 @@ class SetChannelsMixin(object):
 
             .. versionadded:: 0.13.0
 
+        to_sphere : bool
+            Whether to project the 3d locations to a sphere. When False, the
+            sensor array appears similar as to looking downwards straight above
+            the subject's head. Has no effect when kind='3d'. Defaults to True.
+
+            .. versionadded:: 0.14.0
+
         axes : instance of Axes | instance of Axes3D | None
             Axes to draw the sensors to. If ``kind='3d'``, axes must be an
             instance of Axes3D. If None (default), a new axes will be created.
@@ -487,14 +572,15 @@ class SetChannelsMixin(object):
         -----
         This function plots the sensor locations from the info structure using
         matplotlib. For drawing the sensors using mayavi see
-        :func:`mne.viz.plot_trans`.
+        :func:`mne.viz.plot_alignment`.
 
         .. versionadded:: 0.12.0
         """
         from ..viz.utils import plot_sensors
         return plot_sensors(self.info, kind=kind, ch_type=ch_type, title=title,
                             show_names=show_names, ch_groups=ch_groups,
-                            axes=axes, block=block, show=show)
+                            to_sphere=to_sphere, axes=axes, block=block,
+                            show=show)
 
     @copy_function_doc_to_method_doc(anonymize_info)
     def anonymize(self):
@@ -506,14 +592,14 @@ class SetChannelsMixin(object):
 
 
 class UpdateChannelsMixin(object):
-    """Mixin class for Raw, Evoked, Epochs, AverageTFR
-    """
+    """Mixin class for Raw, Evoked, Epochs, AverageTFR."""
+
     def pick_types(self, meg=True, eeg=False, stim=False, eog=False,
                    ecg=False, emg=False, ref_meg='auto', misc=False,
                    resp=False, chpi=False, exci=False, ias=False, syst=False,
                    seeg=False, dipole=False, gof=False, bio=False, ecog=False,
-                   fnirs=False, include=[], exclude='bads', selection=None):
-        """Pick some channels by type and names
+                   fnirs=False, include=(), exclude='bads', selection=None):
+        """Pick some channels by type and names.
 
         Parameters
         ----------
@@ -591,7 +677,7 @@ class UpdateChannelsMixin(object):
         return self
 
     def pick_channels(self, ch_names):
-        """Pick some channels
+        """Pick some channels.
 
         Parameters
         ----------
@@ -617,7 +703,7 @@ class UpdateChannelsMixin(object):
         return self
 
     def drop_channels(self, ch_names):
-        """Drop some channels
+        """Drop some channels.
 
         Parameters
         ----------
@@ -661,38 +747,25 @@ class UpdateChannelsMixin(object):
 
     def _pick_drop_channels(self, idx):
         # avoid circular imports
-        from ..io.base import _BaseRaw
-        from ..epochs import _BaseEpochs
-        from ..evoked import Evoked
-        from ..time_frequency import AverageTFR
+        from ..time_frequency import AverageTFR, EpochsTFR
 
-        if isinstance(self, (_BaseRaw, _BaseEpochs)):
-            if not self.preload:
-                raise RuntimeError('If Raw or Epochs, data must be preloaded '
-                                   'to drop or pick channels')
+        _check_preload(self, 'adding or dropping channels')
 
-        def inst_has(attr):
-            return getattr(self, attr, None) is not None
-
-        if inst_has('picks'):
+        if getattr(self, 'picks', None) is not None:
             self.picks = self.picks[idx]
 
-        if inst_has('_cals'):
+        if hasattr(self, '_cals'):
             self._cals = self._cals[idx]
 
         pick_info(self.info, idx, copy=False)
 
-        if inst_has('_projector'):
+        if getattr(self, '_projector', None) is not None:
             self._projector = self._projector[idx][:, idx]
 
-        if isinstance(self, _BaseRaw) and inst_has('_data'):
-            self._data = self._data.take(idx, axis=0)
-        elif isinstance(self, _BaseEpochs) and inst_has('_data'):
-            self._data = self._data.take(idx, axis=1)
-        elif isinstance(self, AverageTFR) and inst_has('data'):
-            self.data = self.data.take(idx, axis=0)
-        elif isinstance(self, Evoked):
-            self.data = self.data.take(idx, axis=0)
+        if self.preload:
+            # All others (Evoked, Epochs, Raw) have chs axis=-2
+            axis = -3 if isinstance(self, (AverageTFR, EpochsTFR)) else -2
+            self._data = self._data.take(idx, axis=axis)
 
     def add_channels(self, add_list, force_update_info=False):
         """Append new channels to the instance.
@@ -715,30 +788,27 @@ class UpdateChannelsMixin(object):
             The modified instance.
         """
         # avoid circular imports
-        from ..io import _BaseRaw, _merge_info
-        from ..epochs import _BaseEpochs
+        from ..io import BaseRaw, _merge_info
+        from ..epochs import BaseEpochs
 
         if not isinstance(add_list, (list, tuple)):
             raise AssertionError('Input must be a list or tuple of objs')
 
         # Object-specific checks
-        if isinstance(self, (_BaseRaw, _BaseEpochs)):
-            if not all([inst.preload for inst in add_list] + [self.preload]):
-                raise AssertionError('All data must be preloaded')
-            data_name = '_data'
-            if isinstance(self, _BaseRaw):
-                con_axis = 0
-                comp_class = _BaseRaw
-            elif isinstance(self, _BaseEpochs):
-                con_axis = 1
-                comp_class = _BaseEpochs
+        if not all([inst.preload for inst in add_list] + [self.preload]):
+            raise AssertionError('All data must be preloaded')
+        if isinstance(self, BaseRaw):
+            con_axis = 0
+            comp_class = BaseRaw
+        elif isinstance(self, BaseEpochs):
+            con_axis = 1
+            comp_class = BaseEpochs
         else:
-            data_name = 'data'
             con_axis = 0
             comp_class = type(self)
         if not all(isinstance(inst, comp_class) for inst in add_list):
             raise AssertionError('All input data must be of same type')
-        data = [getattr(inst, data_name) for inst in [self] + add_list]
+        data = [inst._data for inst in [self] + add_list]
 
         # Make sure that all dimensions other than channel axis are the same
         compare_axes = [i for i in range(data[0].ndim) if i != con_axis]
@@ -752,19 +822,20 @@ class UpdateChannelsMixin(object):
         new_info = _merge_info(infos, force_update_to_first=force_update_info)
 
         # Now update the attributes
-        setattr(self, data_name, data)
+        self._data = data
         self.info = new_info
-        if isinstance(self, _BaseRaw):
+        if isinstance(self, BaseRaw):
             self._cals = np.concatenate([getattr(inst, '_cals')
                                          for inst in [self] + add_list])
         return self
 
 
 class InterpolationMixin(object):
-    """Mixin class for Raw, Evoked, Epochs
-    """
+    """Mixin class for Raw, Evoked, Epochs."""
 
-    def interpolate_bads(self, reset_bads=True, mode='accurate'):
+    @verbose
+    def interpolate_bads(self, reset_bads=True, mode='accurate',
+                         verbose=None):
         """Interpolate bad MEG and EEG channels.
 
         Operates in place.
@@ -774,9 +845,13 @@ class InterpolationMixin(object):
         reset_bads : bool
             If True, remove the bads from info.
         mode : str
-            Either `'accurate'` or `'fast'`, determines the quality of the
+            Either ``'accurate'`` or ``'fast'``, determines the quality of the
             Legendre polynomial expansion used for interpolation of MEG
             channels.
+        verbose : bool, str, int, or None
+            If not None, override default verbose level (see
+            :func:`mne.verbose` and :ref:`Logging documentation <tut_logging>`
+            for more).
 
         Returns
         -------
@@ -804,6 +879,8 @@ class InterpolationMixin(object):
 def rename_channels(info, mapping):
     """Rename channels.
 
+    .. warning::  The channel names must have at most 15 characters
+
     Parameters
     ----------
     info : dict
@@ -838,6 +915,12 @@ def rename_channels(info, mapping):
            for new_name in new_names):
         raise ValueError('New channel mapping must only be to strings')
 
+    bad_new_names = [name for _, name in new_names if len(name) > 15]
+    if len(bad_new_names):
+        raise ValueError('Channel names cannot be longer than 15 '
+                         'characters. These channel names are not '
+                         'valid : %s' % new_names)
+
     # do the remapping locally
     for c_ind, new_name in new_names:
         for bi, bad in enumerate(bads):
@@ -858,17 +941,18 @@ def rename_channels(info, mapping):
 
 
 def _recursive_flatten(cell, dtype):
-    """Helper to unpack mat files in Python"""
-    while not isinstance(cell[0], dtype):
-        cell = [c for d in cell for c in d]
+    """Unpack mat files in Python."""
+    if len(cell) > 0:
+        while not isinstance(cell[0], dtype):
+            cell = [c for d in cell for c in d]
     return cell
 
 
 def read_ch_connectivity(fname, picks=None):
-    """Parse FieldTrip neighbors .mat file
+    """Parse FieldTrip neighbors .mat file.
 
-    More information on these neighbor definitions can be found on the
-    related FieldTrip documentation pages:
+    More information on these neighbor definitions can be found on the related
+    FieldTrip documentation pages:
     http://fieldtrip.fcdonders.nl/template/neighbours
 
     Parameters
@@ -882,10 +966,21 @@ def read_ch_connectivity(fname, picks=None):
 
     Returns
     -------
-    ch_connectivity : scipy.sparse matrix
+    ch_connectivity : scipy.sparse matrix, shape (n_channels, n_channels)
         The connectivity matrix.
     ch_names : list
         The list of channel names present in connectivity matrix.
+
+    See Also
+    --------
+    find_ch_connectivity
+
+    Notes
+    -----
+    This function is closely related to :func:`find_ch_connectivity`. If you
+    don't know the correct file for the neighbor definitions,
+    :func:`find_ch_connectivity` can compute the connectivity matrix from 2d
+    sensor locations.
     """
     from scipy.io import loadmat
     if not op.isabs(fname):
@@ -924,7 +1019,7 @@ def read_ch_connectivity(fname, picks=None):
 
 
 def _ch_neighbor_connectivity(ch_names, neighbors):
-    """Compute sensor connectivity matrix
+    """Compute sensor connectivity matrix.
 
     Parameters
     ----------
@@ -944,7 +1039,7 @@ def _ch_neighbor_connectivity(ch_names, neighbors):
         raise ValueError('`ch_names` and `neighbors` must '
                          'have the same length')
     set_neighbors = set([c for d in neighbors for c in d])
-    rest = set(ch_names) - set_neighbors
+    rest = set_neighbors - set(ch_names)
     if len(rest) > 0:
         raise ValueError('Some of your neighbors are not present in the '
                          'list of channel names')
@@ -957,13 +1052,149 @@ def _ch_neighbor_connectivity(ch_names, neighbors):
     ch_connectivity = np.eye(len(ch_names), dtype=bool)
     for ii, neigbs in enumerate(neighbors):
         ch_connectivity[ii, [ch_names.index(i) for i in neigbs]] = True
-
     ch_connectivity = sparse.csr_matrix(ch_connectivity)
     return ch_connectivity
 
 
+def find_ch_connectivity(info, ch_type):
+    """Find the connectivity matrix for the given channels.
+
+    This function tries to infer the appropriate connectivity matrix template
+    for the given channels. If a template is not found, the connectivity matrix
+    is computed using Delaunay triangulation based on 2d sensor locations.
+
+    Parameters
+    ----------
+    info : instance of Info
+        The measurement info.
+    ch_type : str | None
+        The channel type for computing the connectivity matrix. Currently
+        supports 'mag', 'grad', 'eeg' and None. If None, the info must contain
+        only one channel type.
+
+    Returns
+    -------
+    ch_connectivity : scipy.sparse matrix, shape (n_channels, n_channels)
+        The connectivity matrix.
+    ch_names : list
+        The list of channel names present in connectivity matrix.
+
+    See Also
+    --------
+    read_ch_connectivity
+
+    Notes
+    -----
+    .. versionadded:: 0.15
+
+    Automatic detection of an appropriate connectivity matrix template only
+    works for MEG data at the moment. This means that the connectivity matrix
+    is always computed for EEG data and never loaded from a template file. If
+    you want to load a template for a given montage use
+    :func:`read_ch_connectivity` directly.
+    """
+    if ch_type is None:
+        picks = channel_indices_by_type(info)
+        if sum([len(p) != 0 for p in picks.values()]) != 1:
+            raise ValueError('info must contain only one channel type if '
+                             'ch_type is None.')
+        ch_type = channel_type(info, 0)
+    elif ch_type not in ['mag', 'grad', 'eeg']:
+        raise ValueError("ch_type must be 'mag', 'grad' or 'eeg'. "
+                         "Got %s." % ch_type)
+    (has_vv_mag, has_vv_grad, is_old_vv, has_4D_mag, ctf_other_types,
+     has_CTF_grad, n_kit_grads, has_any_meg, has_eeg_coils,
+     has_eeg_coils_and_meg, has_eeg_coils_only) = _get_ch_info(info)
+    conn_name = None
+    if has_vv_mag and ch_type == 'mag':
+        conn_name = 'neuromag306mag'
+    elif has_vv_grad and ch_type == 'grad':
+        conn_name = 'neuromag306planar'
+    elif has_4D_mag:
+        if 'MEG 248' in info['ch_names']:
+            idx = info['ch_names'].index('MEG 248')
+            grad = info['chs'][idx]['coil_type'] == FIFF.FIFFV_COIL_MAGNES_GRAD
+            mag = info['chs'][idx]['coil_type'] == FIFF.FIFFV_COIL_MAGNES_MAG
+            if ch_type == 'grad' and grad:
+                conn_name = 'bti248grad'
+            elif ch_type == 'mag' and mag:
+                conn_name = 'bti248'
+        elif 'MEG 148' in info['ch_names'] and ch_type == 'mag':
+            idx = info['ch_names'].index('MEG 148')
+            if info['chs'][idx]['coil_type'] == FIFF.FIFFV_COIL_MAGNES_MAG:
+                conn_name = 'bti148'
+    elif has_CTF_grad and ch_type == 'mag':
+        if info['nchan'] < 100:
+            conn_name = 'ctf64'
+        elif info['nchan'] > 200:
+            conn_name = 'ctf275'
+        else:
+            conn_name = 'ctf151'
+
+    if conn_name is not None:
+        logger.info('Reading connectivity matrix for %s.' % conn_name)
+        return read_ch_connectivity(conn_name)
+    logger.info('Could not find a connectivity matrix for the data. '
+                'Computing connectivity based on Delaunay triangulations.')
+    return _compute_ch_connectivity(info, ch_type)
+
+
+def _compute_ch_connectivity(info, ch_type):
+    """Compute channel connectivity matrix using Delaunay triangulations.
+
+    Parameters
+    ----------
+    info : instance of mne.measuerment_info.Info
+        The measurement info.
+    ch_type : str
+        The channel type for computing the connectivity matrix. Currently
+        supports 'mag', 'grad' and 'eeg'.
+
+    Returns
+    -------
+    ch_connectivity : scipy.sparse matrix, shape (n_channels, n_channels)
+        The connectivity matrix.
+    ch_names : list
+        The list of channel names present in connectivity matrix.
+    """
+    from scipy.spatial import Delaunay
+    from .. import spatial_tris_connectivity
+    from ..channels.layout import _auto_topomap_coords, _pair_grad_sensors
+    combine_grads = (ch_type == 'grad' and FIFF.FIFFV_COIL_VV_PLANAR_T1 in
+                     np.unique([ch['coil_type'] for ch in info['chs']]))
+
+    picks = dict(_picks_by_type(info, exclude=[]))[ch_type]
+    ch_names = [info['ch_names'][pick] for pick in picks]
+    if combine_grads:
+        pairs = _pair_grad_sensors(info, topomap_coords=False, exclude=[])
+        if len(pairs) != len(picks):
+            raise RuntimeError('Cannot find a pair for some of the '
+                               'gradiometers. Cannot compute connectivity '
+                               'matrix.')
+        xy = _auto_topomap_coords(info, picks[::2])  # only for one of the pair
+    else:
+        xy = _auto_topomap_coords(info, picks)
+    tri = Delaunay(xy)
+    neighbors = spatial_tris_connectivity(tri.simplices)
+
+    if combine_grads:
+        ch_connectivity = np.eye(len(picks), dtype=bool)
+        for idx, neigbs in zip(neighbors.row, neighbors.col):
+            for ii in range(2):  # make sure each pair is included
+                for jj in range(2):
+                    ch_connectivity[idx * 2 + ii, neigbs * 2 + jj] = True
+                    ch_connectivity[idx * 2 + ii, idx * 2 + jj] = True  # pair
+        ch_connectivity = sparse.csr_matrix(ch_connectivity)
+    else:
+        ch_connectivity = sparse.lil_matrix(neighbors)
+        ch_connectivity.setdiag(np.repeat(1, ch_connectivity.shape[0]))
+        ch_connectivity = ch_connectivity.tocsr()
+
+    return ch_connectivity, ch_names
+
+
 def fix_mag_coil_types(info):
-    """Fix magnetometer coil types
+    """Fix magnetometer coil types.
 
     Parameters
     ----------
@@ -1000,7 +1231,7 @@ def fix_mag_coil_types(info):
 
 
 def _get_T1T2_mag_inds(info):
-    """Helper to find T1/T2 magnetometer coil types"""
+    """Find T1/T2 magnetometer coil types."""
     picks = pick_types(info, meg='mag')
     old_mag_inds = []
     for ii in picks:
@@ -1009,3 +1240,43 @@ def _get_T1T2_mag_inds(info):
                                FIFF.FIFFV_COIL_VV_MAG_T2):
             old_mag_inds.append(ii)
     return old_mag_inds
+
+
+def _get_ch_info(info):
+    """Get channel info for inferring acquisition device."""
+    chs = info['chs']
+    # Only take first 16 bits, as higher bits store CTF comp order
+    coil_types = set([ch['coil_type'] & 0xFFFF for ch in chs])
+    channel_types = set([ch['kind'] for ch in chs])
+
+    has_vv_mag = any(k in coil_types for k in
+                     [FIFF.FIFFV_COIL_VV_MAG_T1, FIFF.FIFFV_COIL_VV_MAG_T2,
+                      FIFF.FIFFV_COIL_VV_MAG_T3])
+    has_vv_grad = any(k in coil_types for k in [FIFF.FIFFV_COIL_VV_PLANAR_T1,
+                                                FIFF.FIFFV_COIL_VV_PLANAR_T2,
+                                                FIFF.FIFFV_COIL_VV_PLANAR_T3])
+
+    is_old_vv = ' ' in chs[0]['ch_name']
+
+    has_4D_mag = FIFF.FIFFV_COIL_MAGNES_MAG in coil_types
+    ctf_other_types = (FIFF.FIFFV_COIL_CTF_REF_MAG,
+                       FIFF.FIFFV_COIL_CTF_REF_GRAD,
+                       FIFF.FIFFV_COIL_CTF_OFFDIAG_REF_GRAD)
+    has_CTF_grad = (FIFF.FIFFV_COIL_CTF_GRAD in coil_types or
+                    (FIFF.FIFFV_MEG_CH in channel_types and
+                     any(k in ctf_other_types for k in coil_types)))
+    # hack due to MNE-C bug in IO of CTF
+    # only take first 16 bits, as higher bits store CTF comp order
+    n_kit_grads = sum(ch['coil_type'] & 0xFFFF == FIFF.FIFFV_COIL_KIT_GRAD
+                      for ch in chs)
+
+    has_any_meg = any([has_vv_mag, has_vv_grad, has_4D_mag, has_CTF_grad,
+                       n_kit_grads])
+    has_eeg_coils = (FIFF.FIFFV_COIL_EEG in coil_types and
+                     FIFF.FIFFV_EEG_CH in channel_types)
+    has_eeg_coils_and_meg = has_eeg_coils and has_any_meg
+    has_eeg_coils_only = has_eeg_coils and not has_any_meg
+
+    return (has_vv_mag, has_vv_grad, is_old_vv, has_4D_mag, ctf_other_types,
+            has_CTF_grad, n_kit_grads, has_any_meg, has_eeg_coils,
+            has_eeg_coils_and_meg, has_eeg_coils_only)
diff --git a/mne/channels/data/montages/10-5-System_Mastoids_EGI129.csd b/mne/channels/data/montages/10-5-System_Mastoids_EGI129.csd
deleted file mode 100755
index 3db91f3..0000000
--- a/mne/channels/data/montages/10-5-System_Mastoids_EGI129.csd
+++ /dev/null
@@ -1,467 +0,0 @@
-// MatLab   Sphere coordinates [degrees]         Cartesian coordinates
-// Label       Theta       Phi    Radius         X         Y         Z       off sphere surface
-    Nose      90.000   -33.750     1.000    0.0000    0.8315   -0.5556     -0.00000000000000005
-      Nz      90.000   -22.500     1.000    0.0000    0.9239   -0.3827     -0.00000000000000002
-    NFpz      90.000   -11.250     1.000    0.0000    0.9808   -0.1951     -0.00000000000000004
-     Fpz      90.000     0.000     1.000    0.0000    1.0000    0.0000      0.00000000000000000
-    AFpz      90.000    11.250     1.000    0.0000    0.9808    0.1951     -0.00000000000000004
-     AFz      90.000    22.500     1.000    0.0000    0.9239    0.3827     -0.00000000000000002
-    AFFz      90.000    33.750     1.000    0.0000    0.8315    0.5556     -0.00000000000000005
-      Fz      90.000    45.000     1.000    0.0000    0.7071    0.7071     -0.00000000000000002
-    FFCz      90.000    56.250     1.000    0.0000    0.5556    0.8315      0.00000000000000007
-     FCz      90.000    67.500     1.000    0.0000    0.3827    0.9239      0.00000000000000002
-    FCCz      90.000    78.750     1.000    0.0000    0.1951    0.9808     -0.00000000000000001
-      Cz       0.000    90.000     1.000    0.0000    0.0000    1.0000      0.00000000000000000
-    CCPz     -90.000    78.750     1.000    0.0000   -0.1951    0.9808     -0.00000000000000001
-     CPz     -90.000    67.500     1.000    0.0000   -0.3827    0.9239      0.00000000000000002
-    CPPz     -90.000    56.250     1.000    0.0000   -0.5556    0.8315      0.00000000000000007
-      Pz     -90.000    45.000     1.000    0.0000   -0.7071    0.7071     -0.00000000000000002
-    PPOz     -90.000    33.750     1.000    0.0000   -0.8315    0.5556     -0.00000000000000005
-     POz     -90.000    22.500     1.000    0.0000   -0.9239    0.3827     -0.00000000000000002
-    POOz     -90.000    11.250     1.000    0.0000   -0.9808    0.1951     -0.00000000000000004
-      Oz     -90.000     0.000     1.000    0.0000   -1.0000    0.0000      0.00000000000000000
-     OIz     -90.000   -11.250     1.000    0.0000   -0.9808   -0.1951     -0.00000000000000004
-      Iz     -90.000   -22.500     1.000    0.0000   -0.9239   -0.3827     -0.00000000000000002
-     N1h      99.000   -22.500     1.000   -0.1445    0.9125   -0.3827      0.00000000000000011
-      N1     108.000   -22.500     1.000   -0.2855    0.8787   -0.3827     -0.00000000000000004
-    AFp9     117.000   -22.500     1.000   -0.4194    0.8232   -0.3827      0.00000000000000003
-     AF9     126.000   -22.500     1.000   -0.5430    0.7474   -0.3827      0.00000000000000001
-    AFF9     135.000   -22.500     1.000   -0.6533    0.6533   -0.3827     -0.00000000000000007
-      F9     144.000   -22.500     1.000   -0.7474    0.5430   -0.3827     -0.00000000000000003
-    FFT9     153.000   -22.500     1.000   -0.8232    0.4194   -0.3827     -0.00000000000000001
-     FT9     162.000   -22.500     1.000   -0.8787    0.2855   -0.3827      0.00000000000000006
-    FTT9     171.000   -22.500     1.000   -0.9125    0.1445   -0.3827     -0.00000000000000004
-      T9     180.000   -22.500     1.000   -0.9239    0.0000   -0.3827     -0.00000000000000002
-    TTP9    -171.000   -22.500     1.000   -0.9125   -0.1445   -0.3827     -0.00000000000000004
-     TP9    -162.000   -22.500     1.000   -0.8787   -0.2855   -0.3827      0.00000000000000006
-    TPP9    -153.000   -22.500     1.000   -0.8232   -0.4194   -0.3827     -0.00000000000000001
-      P9    -144.000   -22.500     1.000   -0.7474   -0.5430   -0.3827     -0.00000000000000003
-    PPO9    -135.000   -22.500     1.000   -0.6533   -0.6533   -0.3827     -0.00000000000000007
-     PO9    -126.000   -22.500     1.000   -0.5430   -0.7474   -0.3827      0.00000000000000001
-    POO9    -117.000   -22.500     1.000   -0.4194   -0.8232   -0.3827      0.00000000000000003
-      I1    -108.000   -22.500     1.000   -0.2855   -0.8787   -0.3827     -0.00000000000000004
-     I1h     -99.000   -22.500     1.000   -0.1445   -0.9125   -0.3827      0.00000000000000011
-   NFp1h      99.000   -11.250     1.000   -0.1534    0.9687   -0.1951     -0.00000000000000004
-    NFp1     108.000   -11.250     1.000   -0.3031    0.9328   -0.1951     -0.00000000000000009
-   AFp9h     117.000   -11.250     1.000   -0.4453    0.8739   -0.1951     -0.00000000000000008
-    AF9h     126.000   -11.250     1.000   -0.5765    0.7935   -0.1951     -0.00000000000000010
-   AFF9h     135.000   -11.250     1.000   -0.6935    0.6935   -0.1951     -0.00000000000000007
-     F9h     144.000   -11.250     1.000   -0.7935    0.5765   -0.1951      0.00000000000000003
-   FFT9h     153.000   -11.250     1.000   -0.8739    0.4453   -0.1951      0.00000000000000007
-    FT9h     162.000   -11.250     1.000   -0.9328    0.3031   -0.1951      0.00000000000000001
-   FTT9h     171.000   -11.250     1.000   -0.9687    0.1534   -0.1951      0.00000000000000001
-     T9h     180.000   -11.250     1.000   -0.9808    0.0000   -0.1951     -0.00000000000000004
-   TTP9h    -171.000   -11.250     1.000   -0.9687   -0.1534   -0.1951      0.00000000000000001
-    TP9h    -162.000   -11.250     1.000   -0.9328   -0.3031   -0.1951      0.00000000000000001
-   TPP9h    -153.000   -11.250     1.000   -0.8739   -0.4453   -0.1951      0.00000000000000007
-     P9h    -144.000   -11.250     1.000   -0.7935   -0.5765   -0.1951      0.00000000000000003
-   PPO9h    -135.000   -11.250     1.000   -0.6935   -0.6935   -0.1951     -0.00000000000000007
-    PO9h    -126.000   -11.250     1.000   -0.5765   -0.7935   -0.1951     -0.00000000000000010
-   POO9h    -117.000   -11.250     1.000   -0.4453   -0.8739   -0.1951     -0.00000000000000008
-     OI1    -108.000   -11.250     1.000   -0.3031   -0.9328   -0.1951     -0.00000000000000009
-    OI1h     -99.000   -11.250     1.000   -0.1534   -0.9687   -0.1951     -0.00000000000000004
-    Fp1h      99.000     0.000     1.000   -0.1564    0.9877    0.0000      0.00000000000000007
-     Fp1     108.000     0.000     1.000   -0.3090    0.9511    0.0000      0.00000000000000008
-    AFp7     117.000     0.000     1.000   -0.4540    0.8910    0.0000     -0.00000000000000002
-     AF7     126.000     0.000     1.000   -0.5878    0.8090    0.0000     -0.00000000000000008
-    AFF7     135.000     0.000     1.000   -0.7071    0.7071    0.0000     -0.00000000000000002
-      F7     144.000     0.000     1.000   -0.8090    0.5878    0.0000      0.00000000000000000
-    FFT7     153.000     0.000     1.000   -0.8910    0.4540    0.0000     -0.00000000000000007
-     FT7     162.000     0.000     1.000   -0.9511    0.3090    0.0000     -0.00000000000000003
-    FTT7     171.000     0.000     1.000   -0.9877    0.1564    0.0000     -0.00000000000000010
-      T7     180.000     0.000     1.000   -1.0000    0.0000    0.0000      0.00000000000000000
-    TTP7    -171.000     0.000     1.000   -0.9877   -0.1564    0.0000     -0.00000000000000010
-     TP7    -162.000     0.000     1.000   -0.9511   -0.3090    0.0000     -0.00000000000000003
-    TPP7    -153.000     0.000     1.000   -0.8910   -0.4540    0.0000     -0.00000000000000007
-      P7    -144.000     0.000     1.000   -0.8090   -0.5878    0.0000      0.00000000000000000
-    PPO7    -135.000     0.000     1.000   -0.7071   -0.7071    0.0000     -0.00000000000000002
-     PO7    -126.000     0.000     1.000   -0.5878   -0.8090    0.0000     -0.00000000000000008
-    POO7    -117.000     0.000     1.000   -0.4540   -0.8910    0.0000     -0.00000000000000002
-      O1    -108.000     0.000     1.000   -0.3090   -0.9511    0.0000      0.00000000000000008
-     O1h     -99.000     0.000     1.000   -0.1564   -0.9877    0.0000      0.00000000000000007
-     N2h      81.000   -22.500     1.000    0.1445    0.9125   -0.3827     -0.00000000000000006
-      N2      72.000   -22.500     1.000    0.2855    0.8787   -0.3827      0.00000000000000003
-   AFp10      63.000   -22.500     1.000    0.4194    0.8232   -0.3827     -0.00000000000000005
-    AF10      54.000   -22.500     1.000    0.5430    0.7474   -0.3827     -0.00000000000000003
-   AFF10      45.000   -22.500     1.000    0.6533    0.6533   -0.3827      0.00000000000000007
-     F10      36.000   -22.500     1.000    0.7474    0.5430   -0.3827      0.00000000000000013
-   FFT10      27.000   -22.500     1.000    0.8232    0.4194   -0.3827      0.00000000000000008
-    FT10      18.000   -22.500     1.000    0.8787    0.2855   -0.3827     -0.00000000000000000
-   FTT10       9.000   -22.500     1.000    0.9125    0.1445   -0.3827     -0.00000000000000008
-     T10       0.000   -22.500     1.000    0.9239    0.0000   -0.3827     -0.00000000000000002
-   TTP10      -9.000   -22.500     1.000    0.9125   -0.1445   -0.3827     -0.00000000000000008
-    TP10     -18.000   -22.500     1.000    0.8787   -0.2855   -0.3827     -0.00000000000000000
-   TPP10     -27.000   -22.500     1.000    0.8232   -0.4194   -0.3827      0.00000000000000008
-     P10     -36.000   -22.500     1.000    0.7474   -0.5430   -0.3827      0.00000000000000013
-   PPO10     -45.000   -22.500     1.000    0.6533   -0.6533   -0.3827      0.00000000000000007
-    PO10     -54.000   -22.500     1.000    0.5430   -0.7474   -0.3827     -0.00000000000000003
-   POO10     -63.000   -22.500     1.000    0.4194   -0.8232   -0.3827     -0.00000000000000005
-      I2     -72.000   -22.500     1.000    0.2855   -0.8787   -0.3827      0.00000000000000003
-     I2h     -81.000   -22.500     1.000    0.1445   -0.9125   -0.3827     -0.00000000000000006
-   NFp2h      81.000   -11.250     1.000    0.1534    0.9687   -0.1951     -0.00000000000000000
-    NFp2      72.000   -11.250     1.000    0.3031    0.9328   -0.1951     -0.00000000000000002
-  AFp10h      63.000   -11.250     1.000    0.4453    0.8739   -0.1951      0.00000000000000002
-   AF10h      54.000   -11.250     1.000    0.5765    0.7935   -0.1951      0.00000000000000003
-  AFF10h      45.000   -11.250     1.000    0.6935    0.6935   -0.1951      0.00000000000000008
-    F10h      36.000   -11.250     1.000    0.7935    0.5765   -0.1951      0.00000000000000003
-  FFT10h      27.000   -11.250     1.000    0.8739    0.4453   -0.1951     -0.00000000000000003
-   FT10h      18.000   -11.250     1.000    0.9328    0.3031   -0.1951     -0.00000000000000005
-  FTT10h       9.000   -11.250     1.000    0.9687    0.1534   -0.1951     -0.00000000000000002
-    T10h       0.000   -11.250     1.000    0.9808    0.0000   -0.1951     -0.00000000000000004
-  TTP10h      -9.000   -11.250     1.000    0.9687   -0.1534   -0.1951     -0.00000000000000002
-   TP10h     -18.000   -11.250     1.000    0.9328   -0.3031   -0.1951     -0.00000000000000005
-  TPP10h     -27.000   -11.250     1.000    0.8739   -0.4453   -0.1951     -0.00000000000000003
-    P10h     -36.000   -11.250     1.000    0.7935   -0.5765   -0.1951      0.00000000000000003
-  PPO10h     -45.000   -11.250     1.000    0.6935   -0.6935   -0.1951      0.00000000000000008
-   PO10h     -54.000   -11.250     1.000    0.5765   -0.7935   -0.1951      0.00000000000000003
-  POO10h     -63.000   -11.250     1.000    0.4453   -0.8739   -0.1951      0.00000000000000002
-     OI2     -72.000   -11.250     1.000    0.3031   -0.9328   -0.1951     -0.00000000000000002
-    OI2h     -81.000   -11.250     1.000    0.1534   -0.9687   -0.1951     -0.00000000000000000
-    Fp2h      81.000     0.000     1.000    0.1564    0.9877    0.0000      0.00000000000000010
-     Fp2      72.000     0.000     1.000    0.3090    0.9511    0.0000     -0.00000000000000006
-    AFp8      63.000     0.000     1.000    0.4540    0.8910    0.0000     -0.00000000000000012
-     AF8      54.000     0.000     1.000    0.5878    0.8090    0.0000      0.00000000000000005
-    AFF8      45.000     0.000     1.000    0.7071    0.7071    0.0000     -0.00000000000000002
-      F8      36.000     0.000     1.000    0.8090    0.5878    0.0000      0.00000000000000005
-    FFT8      27.000     0.000     1.000    0.8910    0.4540    0.0000      0.00000000000000003
-     FT8      18.000     0.000     1.000    0.9511    0.3090    0.0000     -0.00000000000000010
-    FTT8       9.000     0.000     1.000    0.9877    0.1564    0.0000      0.00000000000000009
-      T8       0.000     0.000     1.000    1.0000    0.0000    0.0000      0.00000000000000000
-    TTP8      -9.000     0.000     1.000    0.9877   -0.1564    0.0000      0.00000000000000009
-     TP8     -18.000     0.000     1.000    0.9511   -0.3090    0.0000     -0.00000000000000010
-    TPP8     -27.000     0.000     1.000    0.8910   -0.4540    0.0000      0.00000000000000003
-      P8     -36.000     0.000     1.000    0.8090   -0.5878    0.0000      0.00000000000000005
-    PPO8     -45.000     0.000     1.000    0.7071   -0.7071    0.0000     -0.00000000000000002
-     PO8     -54.000     0.000     1.000    0.5878   -0.8090    0.0000      0.00000000000000005
-    POO8     -63.000     0.000     1.000    0.4540   -0.8910    0.0000     -0.00000000000000012
-      O2     -72.000     0.000     1.000    0.3090   -0.9511    0.0000     -0.00000000000000006
-     O2h     -81.000     0.000     1.000    0.1564   -0.9877    0.0000      0.00000000000000010
-     T7h     180.000    11.250     1.000   -0.9808    0.0000    0.1951     -0.00000000000000004
-      C5     180.000    22.500     1.000   -0.9239    0.0000    0.3827     -0.00000000000000002
-     C5h     180.000    33.750     1.000   -0.8315    0.0000    0.5556     -0.00000000000000005
-      C3     180.000    45.000     1.000   -0.7071    0.0000    0.7071     -0.00000000000000002
-     C3h     180.000    56.250     1.000   -0.5556    0.0000    0.8315      0.00000000000000007
-      C1     180.000    67.500     1.000   -0.3827    0.0000    0.9239      0.00000000000000002
-     C1h     180.000    78.750     1.000   -0.1951    0.0000    0.9808     -0.00000000000000001
-     T8h       0.000    11.250     1.000    0.9808    0.0000    0.1951     -0.00000000000000004
-      C6       0.000    22.500     1.000    0.9239    0.0000    0.3827     -0.00000000000000002
-     C6h       0.000    33.750     1.000    0.8315    0.0000    0.5556     -0.00000000000000005
-      C4       0.000    45.000     1.000    0.7071    0.0000    0.7071     -0.00000000000000002
-     C4h       0.000    56.250     1.000    0.5556    0.0000    0.8315      0.00000000000000007
-      C2       0.000    67.500     1.000    0.3827    0.0000    0.9239      0.00000000000000002
-     C2h       0.000    78.750     1.000    0.1951    0.0000    0.9808     -0.00000000000000001
-      F3     129.254    29.833     1.000   -0.5489    0.6717    0.4975      0.00000000000000004
-      F4      50.746    29.833     1.000    0.5489    0.6717    0.4975      0.00000000000000004
-      P3    -129.254    29.833     1.000   -0.5489   -0.6717    0.4975      0.00000000000000004
-      P4     -50.746    29.833     1.000    0.5489   -0.6717    0.4975      0.00000000000000004
-      F5     138.891    15.619     1.000   -0.7256    0.6332    0.2692      0.00000000000000002
-      F6      41.109    15.619     1.000    0.7256    0.6332    0.2692      0.00000000000000002
-      P5    -138.891    15.619     1.000   -0.7256   -0.6332    0.2692      0.00000000000000002
-      P6     -41.109    15.619     1.000    0.7256   -0.6332    0.2692      0.00000000000000002
-      F1     112.953    40.722     1.000   -0.2956    0.6979    0.6524     -0.00000000000000007
-      F2      67.047    40.722     1.000    0.2956    0.6979    0.6524     -0.00000000000000011
-      P1    -112.953    40.722     1.000   -0.2956   -0.6979    0.6524     -0.00000000000000013
-      P2     -67.047    40.722     1.000    0.2956   -0.6979    0.6524     -0.00000000000000011
-     F7h     141.913     7.907     1.000   -0.7796    0.6110    0.1376      0.00000000000000007
-     F8h      38.087     7.907     1.000    0.7796    0.6110    0.1376      0.00000000000000007
-     P7h    -141.913     7.907     1.000   -0.7796   -0.6110    0.1376     -0.00000000000000004
-     P8h     -38.087     7.907     1.000    0.7796   -0.6110    0.1376      0.00000000000000007
-     F5h     134.752    22.998     1.000   -0.6481    0.6537    0.3907      0.00000000000000009
-     F6h      45.248    22.998     1.000    0.6481    0.6537    0.3907     -0.00000000000000005
-     P5h    -134.752    22.998     1.000   -0.6481   -0.6537    0.3907      0.00000000000000009
-     P6h     -45.248    22.998     1.000    0.6481   -0.6537    0.3907     -0.00000000000000005
-     F3h     122.046    35.889     1.000   -0.4299    0.6867    0.5862      0.00000000000000003
-     F4h      57.954    35.889     1.000    0.4299    0.6867    0.5862      0.00000000000000008
-     P3h    -122.046    35.889     1.000   -0.4299   -0.6867    0.5862      0.00000000000000012
-     P4h     -57.954    35.889     1.000    0.4299   -0.6867    0.5862      0.00000000000000008
-     F1h     102.055    43.890     1.000   -0.1505    0.7048    0.6933      0.00000000000000010
-     F2h      77.945    43.890     1.000    0.1505    0.7048    0.6933      0.00000000000000004
-     P1h    -102.055    43.890     1.000   -0.1505   -0.7048    0.6933      0.00000000000000006
-     P2h     -77.945    43.890     1.000    0.1505   -0.7048    0.6933      0.00000000000000004
-     FC3     151.481    40.847     1.000   -0.6647    0.3612    0.6540      0.00000000000000005
-     FC4      28.519    40.847     1.000    0.6647    0.3612    0.6540     -0.00000000000000003
-     CP3    -151.481    40.847     1.000   -0.6647   -0.3612    0.6540     -0.00000000000000000
-     CP4     -28.519    40.847     1.000    0.6647   -0.3612    0.6540     -0.00000000000000003
-     FC5     158.854    20.773     1.000   -0.8720    0.3373    0.3547     -0.00000000000000003
-     FC6      21.146    20.773     1.000    0.8720    0.3373    0.3547     -0.00000000000000008
-     CP5    -158.854    20.773     1.000   -0.8720   -0.3373    0.3547      0.00000000000000011
-     CP6     -21.146    20.773     1.000    0.8720   -0.3373    0.3547     -0.00000000000000008
-     FC1     133.587    58.627     1.000   -0.3589    0.3771    0.8538     -0.00000000000000003
-     FC2      46.413    58.627     1.000    0.3589    0.3771    0.8538     -0.00000000000000003
-     CP1    -133.587    58.627     1.000   -0.3589   -0.3771    0.8538     -0.00000000000000003
-     CP2     -46.413    58.627     1.000    0.3589   -0.3771    0.8538     -0.00000000000000003
-    FT7h     160.798    10.433     1.000   -0.9288    0.3235    0.1811     -0.00000000000000008
-    FT8h      19.202    10.433     1.000    0.9288    0.3235    0.1811     -0.00000000000000002
-    TP7h    -160.798    10.433     1.000   -0.9288   -0.3235    0.1811     -0.00000000000000008
-    TP8h     -19.202    10.433     1.000    0.9288   -0.3235    0.1811     -0.00000000000000002
-    FC5h     155.912    30.952     1.000   -0.7829    0.3500    0.5143      0.00000000000000003
-    FC6h      24.088    30.952     1.000    0.7829    0.3500    0.5143      0.00000000000000003
-    CP5h    -155.912    30.952     1.000   -0.7829   -0.3500    0.5143      0.00000000000000003
-    CP6h     -24.088    30.952     1.000    0.7829   -0.3500    0.5143      0.00000000000000003
-    FC3h     144.625    50.235     1.000   -0.5215    0.3703    0.7687      0.00000000000000004
-    FC4h      35.375    50.235     1.000    0.5215    0.3703    0.7687     -0.00000000000000000
-    CP3h    -144.625    50.235     1.000   -0.5215   -0.3703    0.7687     -0.00000000000000005
-    CP4h     -35.375    50.235     1.000    0.5215   -0.3703    0.7687     -0.00000000000000000
-    FC1h     115.626    64.984     1.000   -0.1829    0.3813    0.9062      0.00000000000000006
-    FC2h      64.374    64.984     1.000    0.1829    0.3813    0.9062      0.00000000000000008
-    CP1h    -115.626    64.984     1.000   -0.1829   -0.3813    0.9062      0.00000000000000006
-    CP2h     -64.374    64.984     1.000    0.1829   -0.3813    0.9062      0.00000000000000008
-     AF3     113.312    15.040     1.000   -0.3822    0.8869    0.2595     -0.00000000000000007
-     AF4      66.688    15.040     1.000    0.3822    0.8869    0.2595      0.00000000000000002
-     PO3    -113.312    15.040     1.000   -0.3822   -0.8869    0.2595     -0.00000000000000007
-     PO4     -66.688    15.040     1.000    0.3822   -0.8869    0.2595      0.00000000000000002
-     AF5     120.854     7.908     1.000   -0.5080    0.8503    0.1376      0.00000000000000011
-     AF6      59.146     7.908     1.000    0.5080    0.8503    0.1376      0.00000000000000003
-     PO5    -120.854     7.908     1.000   -0.5080   -0.8503    0.1376      0.00000000000000007
-     PO6     -59.146     7.908     1.000    0.5080   -0.8503    0.1376      0.00000000000000003
-     AF1     102.721    20.458     1.000   -0.2063    0.9139    0.3495      0.00000000000000005
-     AF2      77.279    20.458     1.000    0.2063    0.9139    0.3495      0.00000000000000009
-     PO1    -102.721    20.458     1.000   -0.2063   -0.9139    0.3495      0.00000000000000002
-     PO2     -77.279    20.458     1.000    0.2063   -0.9139    0.3495      0.00000000000000009
-    AF7h     123.694     4.005     1.000   -0.5534    0.8300    0.0698      0.00000000000000004
-    AF8h      56.306     4.005     1.000    0.5534    0.8300    0.0698     -0.00000000000000008
-    PO7h    -123.694     4.005     1.000   -0.5534   -0.8300    0.0698      0.00000000000000004
-    PO8h     -56.306     4.005     1.000    0.5534   -0.8300    0.0698     -0.00000000000000008
-    AF5h     117.408    11.630     1.000   -0.4509    0.8695    0.2016      0.00000000000000005
-    AF6h      62.592    11.630     1.000    0.4509    0.8695    0.2016     -0.00000000000000004
-    PO5h    -117.408    11.630     1.000   -0.4509   -0.8695    0.2016      0.00000000000000009
-    PO6h     -62.592    11.630     1.000    0.4509   -0.8695    0.2016     -0.00000000000000004
-    AF3h     108.359    18.087     1.000   -0.2994    0.9022    0.3105      0.00000000000000006
-    AF4h      71.641    18.087     1.000    0.2994    0.9022    0.3105     -0.00000000000000000
-    PO3h    -108.359    18.087     1.000   -0.2994   -0.9022    0.3105      0.00000000000000010
-    PO4h     -71.641    18.087     1.000    0.2994   -0.9022    0.3105     -0.00000000000000000
-    AF1h      96.517    21.977     1.000   -0.1052    0.9213    0.3742     -0.00000000000000001
-    AF2h      83.483    21.977     1.000    0.1052    0.9213    0.3742      0.00000000000000001
-    PO1h     -96.517    21.977     1.000   -0.1052   -0.9213    0.3742      0.00000000000000003
-    PO2h     -83.483    21.977     1.000    0.1052   -0.9213    0.3742      0.00000000000000001
-    AFp3     106.794     7.311     1.000   -0.2866    0.9496    0.1273     -0.00000000000000001
-    AFp4      73.206     7.311     1.000    0.2866    0.9496    0.1273     -0.00000000000000007
-    POO3    -106.794     7.311     1.000   -0.2866   -0.9496    0.1273     -0.00000000000000009
-    POO4     -73.206     7.311     1.000    0.2866   -0.9496    0.1273     -0.00000000000000007
-    AFp5     112.551     3.800     1.000   -0.3827    0.9215    0.0663      0.00000000000000000
-    AFp6      67.449     3.800     1.000    0.3827    0.9215    0.0663     -0.00000000000000008
-    POO5    -112.551     3.800     1.000   -0.3827   -0.9215    0.0663     -0.00000000000000008
-    POO6     -67.449     3.800     1.000    0.3827   -0.9215    0.0663     -0.00000000000000008
-    AFp1      99.082    10.141     1.000   -0.1554    0.9720    0.1761      0.00000000000000002
-    AFp2      80.918    10.141     1.000    0.1554    0.9720    0.1761      0.00000000000000006
-    POO1     -99.082    10.141     1.000   -0.1554   -0.9720    0.1761      0.00000000000000002
-    POO2     -80.918    10.141     1.000    0.1554   -0.9720    0.1761      0.00000000000000006
-    AFF3     120.670    22.700     1.000   -0.4706    0.7935    0.3859      0.00000000000000003
-    AFF4      59.330    22.700     1.000    0.4706    0.7935    0.3859     -0.00000000000000005
-    PPO3    -120.670    22.700     1.000   -0.4706   -0.7935    0.3859      0.00000000000000003
-    PPO4     -59.330    22.700     1.000    0.4706   -0.7935    0.3859     -0.00000000000000005
-    AFF5     129.623    11.966     1.000   -0.6239    0.7535    0.2073     -0.00000000000000003
-    AFF6      50.377    11.966     1.000    0.6239    0.7535    0.2073      0.00000000000000008
-    PPO5    -129.623    11.966     1.000   -0.6239   -0.7535    0.2073     -0.00000000000000003
-    PPO6     -50.377    11.966     1.000    0.6239   -0.7535    0.2073      0.00000000000000008
-    AFF1     107.147    30.720     1.000   -0.2535    0.8215    0.5108     -0.00000000000000009
-    AFF2      72.853    30.720     1.000    0.2535    0.8215    0.5108     -0.00000000000000003
-    PPO1    -107.147    30.720     1.000   -0.2535   -0.8215    0.5108     -0.00000000000000009
-    PPO2     -72.853    30.720     1.000    0.2535   -0.8215    0.5108     -0.00000000000000003
-   AFF7h     132.702     6.068     1.000   -0.6744    0.7308    0.1057      0.00000000000000006
-   AFF8h      47.298     6.068     1.000    0.6744    0.7308    0.1057     -0.00000000000000004
-   PPO7h    -132.702     6.068     1.000   -0.6744   -0.7308    0.1057     -0.00000000000000003
-   PPO8h     -47.298     6.068     1.000    0.6744   -0.7308    0.1057     -0.00000000000000004
-   AFF5h     125.648    17.573     1.000   -0.5556    0.7747    0.3019     -0.00000000000000004
-   AFF6h      54.352    17.573     1.000    0.5556    0.7747    0.3019      0.00000000000000001
-   PPO5h    -125.648    17.573     1.000   -0.5556   -0.7747    0.3019     -0.00000000000000001
-   PPO6h     -54.352    17.573     1.000    0.5556   -0.7747    0.3019      0.00000000000000001
-   AFF3h     114.474    27.207     1.000   -0.3684    0.8095    0.4572      0.00000000000000001
-   AFF4h      65.526    27.207     1.000    0.3684    0.8095    0.4572      0.00000000000000009
-   PPO3h    -114.474    27.207     1.000   -0.3684   -0.8095    0.4572      0.00000000000000001
-   PPO4h     -65.526    27.207     1.000    0.3684   -0.8095    0.4572      0.00000000000000009
-   AFF1h      98.854    32.973     1.000   -0.1291    0.8289    0.5442      0.00000000000000004
-   AFF2h      81.146    32.973     1.000    0.1291    0.8289    0.5442      0.00000000000000007
-   PPO1h     -98.854    32.973     1.000   -0.1291   -0.8289    0.5442     -0.00000000000000010
-   PPO2h     -81.146    32.973     1.000    0.1291   -0.8289    0.5442      0.00000000000000007
-    FFC3     139.449    36.019     1.000   -0.6146    0.5258    0.5881      0.00000000000000004
-    FFC4      40.551    36.019     1.000    0.6146    0.5258    0.5881      0.00000000000000006
-    CPP3    -139.449    36.019     1.000   -0.6146   -0.5258    0.5881     -0.00000000000000004
-    CPP4     -40.551    36.019     1.000    0.6146   -0.5258    0.5881      0.00000000000000006
-    FFC5     148.658    18.605     1.000   -0.8094    0.4930    0.3190      0.00000000000000011
-    FFC6      31.342    18.605     1.000    0.8094    0.4930    0.3190      0.00000000000000011
-    CPP5    -148.658    18.605     1.000   -0.8094   -0.4930    0.3190      0.00000000000000011
-    CPP6     -31.342    18.605     1.000    0.8094   -0.4930    0.3190      0.00000000000000011
-    FFC1     121.162    50.192     1.000   -0.3313    0.5478    0.7682      0.00000000000000005
-    FFC2      58.838    50.192     1.000    0.3313    0.5478    0.7682      0.00000000000000001
-    CPP1    -121.162    50.192     1.000   -0.3313   -0.5478    0.7682      0.00000000000000005
-    CPP2     -58.838    50.192     1.000    0.3313   -0.5478    0.7682      0.00000000000000001
-   FFT7h     151.293     9.383     1.000   -0.8653    0.4739    0.1630      0.00000000000000001
-   FFT8h      28.707     9.383     1.000    0.8653    0.4739    0.1630     -0.00000000000000004
-   TPP7h    -151.293     9.383     1.000   -0.8653   -0.4739    0.1630      0.00000000000000003
-   TPP8h     -28.707     9.383     1.000    0.8653   -0.4739    0.1630     -0.00000000000000004
-   FFC5h     144.847    27.547     1.000   -0.7249    0.5105    0.4625      0.00000000000000010
-   FFC6h      35.153    27.547     1.000    0.7249    0.5105    0.4625      0.00000000000000005
-   CPP5h    -144.847    27.547     1.000   -0.7249   -0.5105    0.4625      0.00000000000000010
-   CPP6h     -35.153    27.547     1.000    0.7249   -0.5105    0.4625      0.00000000000000005
-   FFC3h     131.815    43.741     1.000   -0.4817    0.5385    0.6914     -0.00000000000000011
-   FFC4h      48.185    43.741     1.000    0.4817    0.5385    0.6914     -0.00000000000000006
-   CPP3h    -131.815    43.741     1.000   -0.4817   -0.5385    0.6914     -0.00000000000000002
-   CPP4h     -48.185    43.741     1.000    0.4817   -0.5385    0.6914     -0.00000000000000006
-   FFC1h     106.951    54.636     1.000   -0.1687    0.5536    0.8155      0.00000000000000003
-   FFC2h      73.049    54.636     1.000    0.1687    0.5536    0.8155      0.00000000000000004
-   CPP1h    -106.951    54.636     1.000   -0.1687   -0.5536    0.8155      0.00000000000000006
-   CPP2h     -73.049    54.636     1.000    0.1687   -0.5536    0.8155      0.00000000000000004
-    FCC3     165.214    43.935     1.000   -0.6963    0.1838    0.6938     -0.00000000000000008
-    FCC4      14.786    43.935     1.000    0.6963    0.1838    0.6938     -0.00000000000000001
-    CCP3    -165.214    43.935     1.000   -0.6963   -0.1838    0.6938     -0.00000000000000008
-    CCP4     -14.786    43.935     1.000    0.6963   -0.1838    0.6938     -0.00000000000000001
-    FCC5     169.351    22.070     1.000   -0.9108    0.1712    0.3757      0.00000000000000001
-    FCC6      10.649    22.070     1.000    0.9108    0.1712    0.3757      0.00000000000000004
-    CCP5    -169.351    22.070     1.000   -0.9108   -0.1712    0.3757     -0.00000000000000007
-    CCP6     -10.649    22.070     1.000    0.9108   -0.1712    0.3757      0.00000000000000004
-    FCC1     152.968    64.990     1.000   -0.3766    0.1922    0.9062      0.00000000000000002
-    FCC2      27.032    64.990     1.000    0.3766    0.1922    0.9062      0.00000000000000000
-    CCP1    -152.968    64.990     1.000   -0.3766   -0.1922    0.9062      0.00000000000000002
-    CCP2     -27.032    64.990     1.000    0.3766   -0.1922    0.9062      0.00000000000000000
-   FTT7h     170.382    11.048     1.000   -0.9677    0.1640    0.1916      0.00000000000000003
-   FTT8h       9.618    11.048     1.000    0.9677    0.1640    0.1916      0.00000000000000004
-   TTP7h    -170.382    11.048     1.000   -0.9677   -0.1640    0.1916      0.00000000000000003
-   TTP8h      -9.618    11.048     1.000    0.9677   -0.1640    0.1916      0.00000000000000004
-   FCC5h     167.745    33.045     1.000   -0.8191    0.1779    0.5453      0.00000000000000001
-   FCC6h      12.255    33.045     1.000    0.8191    0.1779    0.5453     -0.00000000000000002
-   CCP5h    -167.745    33.045     1.000   -0.8191   -0.1779    0.5453      0.00000000000000006
-   CCP6h     -12.255    33.045     1.000    0.8191   -0.1779    0.5453     -0.00000000000000002
-   FCC3h     160.973    54.657     1.000   -0.5469    0.1886    0.8157     -0.00000000000000004
-   FCC4h      19.027    54.657     1.000    0.5469    0.1886    0.8157     -0.00000000000000002
-   CCP3h    -160.973    54.657     1.000   -0.5469   -0.1886    0.8157      0.00000000000000002
-   CCP4h     -19.027    54.657     1.000    0.5469   -0.1886    0.8157     -0.00000000000000002
-   FCC1h     134.645    74.147     1.000   -0.1920    0.1943    0.9620     -0.00000000000000006
-   FCC2h      45.355    74.147     1.000    0.1920    0.1943    0.9620     -0.00000000000000005
-   CCP1h    -134.645    74.147     1.000   -0.1920   -0.1943    0.9620     -0.00000000000000006
-   CCP2h     -45.355    74.147     1.000    0.1920   -0.1943    0.9620     -0.00000000000000005
-      A1     180.000   -22.500     1.000   -0.9239    0.0000   -0.3827     -0.00000000000000002
-      A2       0.000   -22.500     1.000    0.9239    0.0000   -0.3827     -0.00000000000000002
-     LPA     180.000   -22.500     1.000   -0.9239    0.0000   -0.3827     -0.00000000000000002
-     RPA       0.000   -22.500     1.000    0.9239    0.0000   -0.3827     -0.00000000000000002
-      LM    -162.000   -22.500     1.000   -0.8787   -0.2855   -0.3827      0.00000000000000006
-      RM     -18.000   -22.500     1.000    0.8787   -0.2855   -0.3827     -0.00000000000000000
-       1      36.000   -22.000     1.000    0.7501    0.5450   -0.3746      0.00000000000000001
-       2      47.000    -6.000     1.000    0.6783    0.7273   -0.1045     -0.00000000000000009
-       3      56.000    10.000     1.000    0.5507    0.8164    0.1736     -0.00000000000000004
-       4      72.000    26.000     1.000    0.2777    0.8548    0.4384      0.00000000000000001
-       5      78.000    42.000     1.000    0.1545    0.7269    0.6691     -0.00000000000000002
-       6      90.000    58.000     1.000    0.0000    0.5299    0.8480     -0.00000000000000008
-       7     126.000    74.000     1.000   -0.1620    0.2230    0.9613      0.00000000000000006
-       8      54.000   -22.000     1.000    0.5450    0.7501   -0.3746      0.00000000000000001
-       9      64.000    -6.000     1.000    0.4360    0.8939   -0.1045     -0.00000000000000008
-      10      73.000    10.000     1.000    0.2879    0.9418    0.1736     -0.00000000000000001
-      11      90.000    26.000     1.000    0.0000    0.8988    0.4384      0.00000000000000007
-      12     102.000    42.000     1.000   -0.1545    0.7269    0.6691      0.00000000000000001
-      13     126.000    58.000     1.000   -0.3115    0.4287    0.8480     -0.00000000000000004
-      14      72.000   -22.000     1.000    0.2865    0.8818   -0.3746      0.00000000000000006
-      15      81.000    -6.000     1.000    0.1556    0.9823   -0.1045      0.00000000000000000
-      16      90.000    10.000     1.000    0.0000    0.9848    0.1736     -0.00000000000000008
-      17      90.000   -33.750     1.000    0.0000    0.8315   -0.5556     -0.00000000000000005
-      18      99.000    -6.000     1.000   -0.1556    0.9823   -0.1045     -0.00000000000000003
-      19     108.000    10.000     1.000   -0.3043    0.9366    0.1736      0.00000000000000003
-      20     108.000    26.000     1.000   -0.2777    0.8548    0.4384     -0.00000000000000005
-      21     126.000    42.000     1.000   -0.4368    0.6012    0.6691     -0.00000000000000001
-      22     108.000   -22.000     1.000   -0.2865    0.8818   -0.3746     -0.00000000000000000
-      23     116.000    -6.000     1.000   -0.4360    0.8939   -0.1045      0.00000000000000002
-      24     126.000    10.000     1.000   -0.5789    0.7967    0.1736     -0.00000000000000004
-      25     126.000    26.000     1.000   -0.5283    0.7271    0.4384     -0.00000000000000010
-      26     126.000   -22.000     1.000   -0.5450    0.7501   -0.3746      0.00000000000000001
-      27     133.000    -6.000     1.000   -0.6783    0.7273   -0.1045      0.00000000000000007
-      28     142.000    10.000     1.000   -0.7760    0.6063    0.1736      0.00000000000000000
-      29     144.000    26.000     1.000   -0.7271    0.5283    0.4384     -0.00000000000000002
-      30     150.000    42.000     1.000   -0.6436    0.3716    0.6691      0.00000000000000004
-      31     162.000    58.000     1.000   -0.5040    0.1638    0.8480     -0.00000000000000003
-      32    -162.000    74.000     1.000   -0.2621   -0.0852    0.9613      0.00000000000000007
-      33     144.000   -22.000     1.000   -0.7501    0.5450   -0.3746     -0.00000000000000004
-      34     150.000    -6.000     1.000   -0.8613    0.4973   -0.1045      0.00000000000000007
-      35     159.000    10.000     1.000   -0.9194    0.3529    0.1736      0.00000000000000004
-      36     162.000    26.000     1.000   -0.8548    0.2777    0.4384      0.00000000000000004
-      37     174.000    42.000     1.000   -0.7391    0.0777    0.6691      0.00000000000000005
-      38    -162.000    58.000     1.000   -0.5040   -0.1638    0.8480     -0.00000000000000003
-      39     162.000   -22.000     1.000   -0.8818    0.2865   -0.3746      0.00000000000000006
-      40     167.000    -6.000     1.000   -0.9690    0.2237   -0.1045      0.00000000000000008
-      41     176.000    10.000     1.000   -0.9824    0.0687    0.1736      0.00000000000000009
-      42     180.000    26.000     1.000   -0.8988    0.0000    0.4384      0.00000000000000007
-      43    -162.000    42.000     1.000   -0.7068   -0.2296    0.6691     -0.00000000000000003
-      44     150.000   -38.000     1.000   -0.6824    0.3940   -0.6157     -0.00000000000000003
-      45     180.000   -22.000     1.000   -0.9272    0.0000   -0.3746      0.00000000000000003
-      46    -176.000    -6.000     1.000   -0.9921   -0.0694   -0.1045      0.00000000000000002
-      47    -167.000    10.000     1.000   -0.9596   -0.2215    0.1736      0.00000000000000009
-      48    -162.000    26.000     1.000   -0.8548   -0.2777    0.4384      0.00000000000000004
-      49     170.000   -38.000     1.000   -0.7760    0.1368   -0.6157      0.00000000000000005
-      50    -159.000    -6.000     1.000   -0.9285   -0.3564   -0.1045      0.00000000000000003
-      51    -150.000    10.000     1.000   -0.8529   -0.4924    0.1736      0.00000000000000009
-      52    -144.000    26.000     1.000   -0.7271   -0.5283    0.4384     -0.00000000000000002
-      53    -138.000    42.000     1.000   -0.5523   -0.4973    0.6691      0.00000000000000005
-      54    -126.000    58.000     1.000   -0.3115   -0.4287    0.8480     -0.00000000000000004
-      55     -90.000    74.000     1.000    0.0000   -0.2756    0.9613      0.00000000000000005
-      56    -170.000   -38.000     1.000   -0.7760   -0.1368   -0.6157      0.00000000000000005
-      57    -157.000   -22.000     1.000   -0.8535   -0.3623   -0.3746     -0.00000000000000002
-      58    -142.000    -6.000     1.000   -0.7837   -0.6123   -0.1045     -0.00000000000000006
-      59    -133.000    10.000     1.000   -0.6716   -0.7202    0.1736      0.00000000000000008
-      60    -126.000    26.000     1.000   -0.5283   -0.7271    0.4384     -0.00000000000000010
-      61    -114.000    42.000     1.000   -0.3023   -0.6789    0.6691      0.00000000000000006
-      62     -90.000    58.000     1.000    0.0000   -0.5299    0.8480     -0.00000000000000008
-      63    -150.000   -38.000     1.000   -0.6824   -0.3940   -0.6157     -0.00000000000000003
-      64    -139.000   -22.000     1.000   -0.6998   -0.6083   -0.3746      0.00000000000000003
-      65    -125.000    -6.000     1.000   -0.5704   -0.8147   -0.1045     -0.00000000000000002
-      66    -116.000    10.000     1.000   -0.4317   -0.8851    0.1736     -0.00000000000000004
-      67    -108.000    26.000     1.000   -0.2777   -0.8548    0.4384     -0.00000000000000005
-      68     -90.000    42.000     1.000    0.0000   -0.7431    0.6691      0.00000000000000005
-      69    -130.000   -38.000     1.000   -0.5065   -0.6037   -0.6157      0.00000000000000000
-      70    -122.000   -22.000     1.000   -0.4913   -0.7863   -0.3746      0.00000000000000010
-      71    -108.000    -6.000     1.000   -0.3073   -0.9458   -0.1045      0.00000000000000009
-      72     -99.000    10.000     1.000   -0.1541   -0.9727    0.1736     -0.00000000000000007
-      73     -90.000    26.000     1.000    0.0000   -0.8988    0.4384      0.00000000000000007
-      74    -110.000   -38.000     1.000   -0.2695   -0.7405   -0.6157      0.00000000000000002
-      75    -100.000   -22.000     1.000   -0.1610   -0.9131   -0.3746     -0.00000000000000009
-      76     -90.000    -6.000     1.000    0.0000   -0.9945   -0.1045     -0.00000000000000009
-      77     -81.000    10.000     1.000    0.1541   -0.9727    0.1736     -0.00000000000000004
-      78     -72.000    26.000     1.000    0.2777   -0.8548    0.4384      0.00000000000000001
-      79     -66.000    42.000     1.000    0.3023   -0.6789    0.6691      0.00000000000000003
-      80     -54.000    58.000     1.000    0.3115   -0.4287    0.8480     -0.00000000000000005
-      81     -18.000    74.000     1.000    0.2621   -0.0852    0.9613      0.00000000000000006
-      82     -90.000   -38.000     1.000    0.0000   -0.7880   -0.6157     -0.00000000000000007
-      83     -80.000   -22.000     1.000    0.1610   -0.9131   -0.3746     -0.00000000000000006
-      84     -72.000    -6.000     1.000    0.3073   -0.9458   -0.1045     -0.00000000000000005
-      85     -64.000    10.000     1.000    0.4317   -0.8851    0.1736      0.00000000000000006
-      86     -54.000    26.000     1.000    0.5283   -0.7271    0.4384      0.00000000000000002
-      87     -42.000    42.000     1.000    0.5523   -0.4973    0.6691      0.00000000000000004
-      88     -18.000    58.000     1.000    0.5040   -0.1638    0.8480     -0.00000000000000005
-      89     -70.000   -38.000     1.000    0.2695   -0.7405   -0.6157      0.00000000000000008
-      90     -59.000   -22.000     1.000    0.4775   -0.7948   -0.3746     -0.00000000000000006
-      91     -55.000    -6.000     1.000    0.5704   -0.8147   -0.1045      0.00000000000000004
-      92     -47.000    10.000     1.000    0.6716   -0.7202    0.1736     -0.00000000000000008
-      93     -36.000    26.000     1.000    0.7271   -0.5283    0.4384      0.00000000000000002
-      94     -18.000    42.000     1.000    0.7068   -0.2296    0.6691      0.00000000000000009
-      95     -50.000   -38.000     1.000    0.5065   -0.6037   -0.6157      0.00000000000000000
-      96     -41.000   -22.000     1.000    0.6998   -0.6083   -0.3746      0.00000000000000003
-      97     -38.000    -6.000     1.000    0.7837   -0.6123   -0.1045      0.00000000000000011
-      98     -30.000    10.000     1.000    0.8529   -0.4924    0.1736     -0.00000000000000005
-      99     -18.000    26.000     1.000    0.8548   -0.2777    0.4384     -0.00000000000000002
-     100     -30.000   -38.000     1.000    0.6824   -0.3940   -0.6157      0.00000000000000002
-     101     -23.000   -22.000     1.000    0.8535   -0.3623   -0.3746     -0.00000000000000002
-     102     -21.000    -6.000     1.000    0.9285   -0.3564   -0.1045      0.00000000000000007
-     103     -13.000    10.000     1.000    0.9596   -0.2215    0.1736      0.00000000000000001
-     104       0.000    26.000     1.000    0.8988    0.0000    0.4384      0.00000000000000007
-     105       6.000    42.000     1.000    0.7391    0.0777    0.6691      0.00000000000000007
-     106      18.000    58.000     1.000    0.5040    0.1638    0.8480     -0.00000000000000005
-     107      54.000    74.000     1.000    0.1620    0.2230    0.9613      0.00000000000000006
-     108     -10.000   -38.000     1.000    0.7760   -0.1368   -0.6157      0.00000000000000007
-     109      -4.000    -6.000     1.000    0.9921   -0.0694   -0.1045     -0.00000000000000001
-     110       4.000    10.000     1.000    0.9824    0.0687    0.1736      0.00000000000000006
-     111      18.000    26.000     1.000    0.8548    0.2777    0.4384     -0.00000000000000002
-     112      30.000    42.000     1.000    0.6436    0.3716    0.6691     -0.00000000000000006
-     113      54.000    58.000     1.000    0.3115    0.4287    0.8480     -0.00000000000000005
-     114      10.000   -38.000     1.000    0.7760    0.1368   -0.6157      0.00000000000000007
-     115       0.000   -22.000     1.000    0.9272    0.0000   -0.3746      0.00000000000000003
-     116      13.000    -6.000     1.000    0.9690    0.2237   -0.1045     -0.00000000000000001
-     117      21.000    10.000     1.000    0.9194    0.3529    0.1736      0.00000000000000004
-     118      36.000    26.000     1.000    0.7271    0.5283    0.4384      0.00000000000000002
-     119      54.000    42.000     1.000    0.4368    0.6012    0.6691      0.00000000000000004
-     120      30.000   -38.000     1.000    0.6824    0.3940   -0.6157      0.00000000000000002
-     121      18.000   -22.000     1.000    0.8818    0.2865   -0.3746      0.00000000000000003
-     122      30.000    -6.000     1.000    0.8613    0.4973   -0.1045     -0.00000000000000007
-     123      38.000    10.000     1.000    0.7760    0.6063    0.1736      0.00000000000000004
-     124      54.000    26.000     1.000    0.5283    0.7271    0.4384      0.00000000000000002
-     125      50.000   -38.000     1.000    0.5065    0.6037   -0.6157      0.00000000000000000
-     126      70.000   -38.000     1.000    0.2695    0.7405   -0.6157      0.00000000000000008
-     127     110.000   -38.000     1.000   -0.2695    0.7405   -0.6157      0.00000000000000002
-     128     130.000   -38.000     1.000   -0.5065    0.6037   -0.6157      0.00000000000000000
-     129       0.000    90.000     1.000    0.0000    0.0000    1.0000      0.00000000000000000
diff --git a/mne/channels/data/montages/mgh60.elc b/mne/channels/data/montages/mgh60.elc
new file mode 100644
index 0000000..ae55cb2
--- /dev/null
+++ b/mne/channels/data/montages/mgh60.elc
@@ -0,0 +1,132 @@
+# ASA electrode file
+ReferenceLabel	avg
+UnitPosition	mm
+NumberPositions=	63
+Positions
+-86.0761 -19.9897 -47.9860
+85.7939 -20.0093 -48.0310
+0.0083 86.8110 -39.9830
+0.3122 58.5120 66.4620
+29.5142 57.6019 59.5400
+35.7123 77.7259 21.9560
+0.1123 88.2470 -1.7130
+-29.4367 83.9171 -6.9900
+55.7433 69.6568 -10.7550
+-84.0759 14.5673 -50.4290
+-70.2629 42.4743 -11.4200
+-77.2149 18.6433 24.4600
+79.5341 19.9357 24.4380
+-80.7750 14.1203 -11.1350
+-27.4958 56.9311 60.3420
+-54.8397 68.5722 -10.5900
+81.8151 15.4167 -11.3300
+67.9142 49.8297 16.3670
+-64.4658 48.0353 16.9210
+-34.0619 26.0111 79.9870
+34.7841 26.4379 78.8080
+84.1131 14.3647 -50.5380
+-85.8941 -15.8287 -48.2830
+0.4009 -9.1670 100.2440
+51.8362 54.3048 40.8140
+-84.1611 -16.0187 -9.3460
+37.6720 -9.6241 88.4120
+67.1179 -10.9003 63.5800
+-36.1580 -9.9839 89.7520
+-65.3581 -11.6317 64.3580
+73.0431 44.4217 -12.0000
+-50.2438 53.1112 42.1920
+-80.2801 -13.7597 29.1600
+29.8723 84.8959 -7.0800
+-33.7007 76.8371 21.2270
+38.3838 -47.0731 90.6950
+31.9197 -80.4871 76.7160
+29.8426 -112.1560 8.8000
+0.0045 -118.5650 -23.0780
+0.1076 -114.8920 14.6570
+36.7816 -100.8491 36.3970
+-29.4134 -112.4490 8.8390
+73.0557 -73.0683 -2.5400
+55.6666 -97.6251 2.7300
+67.8877 -75.9043 28.0910
+-54.8404 -97.5279 2.7920
+-36.5114 -100.8529 37.1670
+83.4559 -12.7763 29.2080
+-85.6192 -46.5147 -45.7070
+85.5488 -45.5453 -7.1300
+66.6118 -46.6372 65.5800
+55.6667 -78.5602 56.5610
+-63.5562 -47.0088 65.6240
+-35.5131 -47.2919 91.3150
+-84.8302 -46.0217 -7.0560
+-53.0073 -78.7878 55.9400
+0.3247 -81.1150 82.6150
+-28.6203 -80.5249 75.4360
+-72.4343 -73.4527 -2.4870
+-67.2723 -76.2907 28.3820
+86.1618 -47.0353 -45.8690
+85.0799 -15.0203 -9.4900
+85.5599 -16.3613 -48.2710
+Labels
+LPA
+RPA
+Nz
+EEG001
+EEG002
+EEG003
+EEG004
+EEG005
+EEG006
+EEG007
+EEG008
+EEG009
+EEG010
+EEG011
+EEG012
+EEG013
+EEG014
+EEG015
+EEG016
+EEG017
+EEG018
+EEG019
+EEG020
+EEG021
+EEG022
+EEG023
+EEG024
+EEG025
+EEG026
+EEG027
+EEG028
+EEG029
+EEG030
+EEG031
+EEG032
+EEG033
+EEG034
+EEG035
+EEG036
+EEG037
+EEG038
+EEG039
+EEG040
+EEG041
+EEG042
+EEG043
+EEG044
+EEG045
+EEG046
+EEG047
+EEG048
+EEG049
+EEG050
+EEG051
+EEG052
+EEG053
+EEG054
+EEG055
+EEG056
+EEG057
+EEG058
+EEG059
+EEG060
\ No newline at end of file
diff --git a/mne/channels/data/montages/mgh70.elc b/mne/channels/data/montages/mgh70.elc
new file mode 100644
index 0000000..66eb161
--- /dev/null
+++ b/mne/channels/data/montages/mgh70.elc
@@ -0,0 +1,152 @@
+# ASA electrode file
+ReferenceLabel	avg
+UnitPosition	mm
+NumberPositions=	73
+Positions
+-86.0761 -19.9897 -47.9860
+85.7939 -20.0093 -48.0310
+0.0083 86.8110 -39.9830
+-29.4367 83.9171 -6.9900
+0.1123 88.2470 -1.7130
+29.8723 84.8959 -7.0800
+-54.8397 68.5722 -10.5900
+-33.7007 76.8371 21.2270
+0.2313 80.7710 35.4170
+35.7123 77.7259 21.9560
+55.7433 69.6568 -10.7550
+-70.2629 42.4743 -11.4200
+-64.4658 48.0353 16.9210
+-50.2438 53.1112 42.1920
+-27.4958 56.9311 60.3420
+0.3122 58.5120 66.4620
+29.5142 57.6019 59.5400
+51.8362 54.3048 40.8140
+67.9142 49.8297 16.3670
+73.0431 44.4217 -12.0000
+-84.0759 14.5673 -50.4290
+-80.7750 14.1203 -11.1350
+-77.2149 18.6433 24.4600
+-60.1819 22.7162 55.5440
+-34.0619 26.0111 79.9870
+0.3761 27.3900 88.6680
+34.7841 26.4379 78.8080
+62.2931 23.7228 55.6300
+79.5341 19.9357 24.4380
+81.8151 15.4167 -11.3300
+84.1131 14.3647 -50.5380
+-85.8941 -15.8287 -48.2830
+-84.1611 -16.0187 -9.3460
+-80.2801 -13.7597 29.1600
+-65.3581 -11.6317 64.3580
+-36.1580 -9.9839 89.7520
+0.4009 -9.1670 100.2440
+37.6720 -9.6241 88.4120
+67.1179 -10.9003 63.5800
+83.4559 -12.7763 29.2080
+85.0799 -15.0203 -9.4900
+85.5599 -16.3613 -48.2710
+-85.6192 -46.5147 -45.7070
+-84.8302 -46.0217 -7.0560
+-79.5922 -46.5507 30.9490
+-63.5562 -47.0088 65.6240
+-35.5131 -47.2919 91.3150
+0.3858 -47.3180 99.4320
+38.3838 -47.0731 90.6950
+66.6118 -46.6372 65.5800
+83.3218 -46.1013 31.2060
+85.5488 -45.5453 -7.1300
+86.1618 -47.0353 -45.8690
+-73.0093 -73.7657 -40.9980
+-72.4343 -73.4527 -2.4870
+-67.2723 -76.2907 28.3820
+-53.0073 -78.7878 55.9400
+-28.6203 -80.5249 75.4360
+0.3247 -81.1150 82.6150
+31.9197 -80.4871 76.7160
+55.6667 -78.5602 56.5610
+67.8877 -75.9043 28.0910
+73.0557 -73.0683 -2.5400
+73.8947 -74.3903 -41.2200
+-54.8404 -97.5279 2.7920
+-36.5114 -100.8529 37.1670
+0.2156 -102.1780 50.6080
+36.7816 -100.8491 36.3970
+55.6666 -97.6251 2.7300
+-29.4134 -112.4490 8.8390
+0.1076 -114.8920 14.6570
+29.8426 -112.1560 8.8000
+0.0045 -118.5650 -23.0780
+Labels
+LPA
+RPA
+Nz
+EEG001
+EEG002
+EEG003
+EEG004
+EEG005
+EEG006
+EEG007
+EEG008
+EEG009
+EEG010
+EEG011
+EEG012
+EEG013
+EEG014
+EEG015
+EEG016
+EEG017
+EEG018
+EEG019
+EEG020
+EEG021
+EEG022
+EEG023
+EEG024
+EEG025
+EEG026
+EEG027
+EEG028
+EEG029
+EEG030
+EEG031
+EEG032
+EEG033
+EEG034
+EEG035
+EEG036
+EEG037
+EEG038
+EEG039
+EEG040
+EEG041
+EEG042
+EEG043
+EEG044
+EEG045
+EEG046
+EEG047
+EEG048
+EEG049
+EEG050
+EEG051
+EEG052
+EEG053
+EEG054
+EEG055
+EEG056
+EEG057
+EEG058
+EEG059
+EEG060
+EEG061
+EEG062
+EEG063
+EEG064
+EEG065
+EEG066
+EEG067
+EEG068
+EEG069
+EEG070
\ No newline at end of file
diff --git a/mne/channels/data/neighbors/__init__.py b/mne/channels/data/neighbors/__init__.py
index c741b62..b49a56b 100644
--- a/mne/channels/data/neighbors/__init__.py
+++ b/mne/channels/data/neighbors/__init__.py
@@ -1,4 +1,4 @@
-# Neighbor definitions for clustering permutation analysis.
+"""Neighbor definitions for clustering permutation analysis."""
 # This is a selection of files from http://fieldtrip.fcdonders.nl/template
 # Additional definitions can be obtained through the FieldTrip software.
 # For additional information on how these definitions were computed, please
diff --git a/mne/channels/interpolation.py b/mne/channels/interpolation.py
index 988f6cd..9bfc77a 100644
--- a/mne/channels/interpolation.py
+++ b/mne/channels/interpolation.py
@@ -6,7 +6,7 @@ import numpy as np
 from numpy.polynomial.legendre import legval
 from scipy import linalg
 
-from ..utils import logger, warn
+from ..utils import logger, warn, verbose
 from ..io.pick import pick_types, pick_channels, pick_info
 from ..surface import _normalize_vectors
 from ..bem import _fit_sphere
@@ -37,7 +37,7 @@ def _calc_g(cosang, stiffness=4, num_lterms=50):
 
 
 def _make_interpolation_matrix(pos_from, pos_to, alpha=1e-5):
-    """Compute interpolation matrix based on spherical splines
+    """Compute interpolation matrix based on spherical splines.
 
     Implementation based on [1]
 
@@ -62,7 +62,6 @@ def _make_interpolation_matrix(pos_from, pos_to, alpha=1e-5):
         Spherical splines for scalp potential and current density mapping.
         Electroencephalography Clinical Neurophysiology, Feb; 72(2):184-7.
     """
-
     pos_from = pos_from.copy()
     pos_to = pos_to.copy()
 
@@ -90,26 +89,24 @@ def _make_interpolation_matrix(pos_from, pos_to, alpha=1e-5):
 
 
 def _do_interp_dots(inst, interpolation, goods_idx, bads_idx):
-    """Dot product of channel mapping matrix to channel data
-    """
-    from ..io.base import _BaseRaw
-    from ..epochs import _BaseEpochs
+    """Dot product of channel mapping matrix to channel data."""
+    from ..io.base import BaseRaw
+    from ..epochs import BaseEpochs
     from ..evoked import Evoked
 
-    if isinstance(inst, _BaseRaw):
+    if isinstance(inst, (BaseRaw, Evoked)):
         inst._data[bads_idx] = interpolation.dot(inst._data[goods_idx])
-    elif isinstance(inst, _BaseEpochs):
+    elif isinstance(inst, BaseEpochs):
         inst._data[:, bads_idx, :] = np.einsum('ij,xjy->xiy', interpolation,
                                                inst._data[:, goods_idx, :])
-    elif isinstance(inst, Evoked):
-        inst.data[bads_idx] = interpolation.dot(inst.data[goods_idx])
     else:
         raise ValueError('Inputs of type {0} are not supported'
                          .format(type(inst)))
 
 
-def _interpolate_bads_eeg(inst):
-    """Interpolate bad EEG channels
+ at verbose
+def _interpolate_bads_eeg(inst, verbose=None):
+    """Interpolate bad EEG channels.
 
     Operates in place.
 
@@ -157,6 +154,7 @@ def _interpolate_bads_eeg(inst):
     _do_interp_dots(inst, interpolation, goods_idx, bads_idx)
 
 
+ at verbose
 def _interpolate_bads_meg(inst, mode='accurate', verbose=None):
     """Interpolate bad channels from data in good channels.
 
@@ -169,7 +167,8 @@ def _interpolate_bads_meg(inst, mode='accurate', verbose=None):
         Legendre polynomial expansion used for interpolation. `'fast'` should
         be sufficient for most applications.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
     """
     picks_meg = pick_types(inst.info, meg=True, eeg=False, exclude=[])
     picks_good = pick_types(inst.info, meg=True, eeg=False, exclude='bads')
diff --git a/mne/channels/layout.py b/mne/channels/layout.py
index 4d19bf9..de115ae 100644
--- a/mne/channels/layout.py
+++ b/mne/channels/layout.py
@@ -15,17 +15,18 @@ import os.path as op
 
 import numpy as np
 
-from ..transforms import _polar_to_cartesian, _cartesian_to_sphere
+from ..transforms import _pol_to_cart, _cart_to_sph
 from ..bem import fit_sphere_to_headshape
 from ..io.pick import pick_types
 from ..io.constants import FIFF
 from ..io.meas_info import Info
 from ..utils import _clean_names, warn
 from ..externals.six.moves import map
+from .channels import _get_ch_info
 
 
 class Layout(object):
-    """Sensor layouts
+    """Sensor layouts.
 
     Layouts are typically loaded from a file using read_layout. Only use this
     class directly if you're constructing a new layout.
@@ -43,7 +44,8 @@ class Layout(object):
     kind : str
         The type of Layout (e.g. 'Vectorview-all').
     """
-    def __init__(self, box, pos, names, ids, kind):
+
+    def __init__(self, box, pos, names, ids, kind):  # noqa: D102
         self.box = box
         self.pos = pos
         self.names = names
@@ -51,7 +53,7 @@ class Layout(object):
         self.kind = kind
 
     def save(self, fname):
-        """Save Layout to disk
+        """Save Layout to disk.
 
         Parameters
         ----------
@@ -83,14 +85,18 @@ class Layout(object):
         f.close()
 
     def __repr__(self):
+        """Return the string representation."""
         return '<Layout | %s - Channels: %s ...>' % (self.kind,
                                                      ', '.join(self.names[:3]))
 
-    def plot(self, show=True):
+    def plot(self, picks=None, show=True):
         """Plot the sensor positions.
 
         Parameters
         ----------
+        picks : array-like
+            Indices of the channels to show. If None (default), all the
+            channels are shown.
         show : bool
             Show figure if True. Defaults to True.
 
@@ -104,11 +110,11 @@ class Layout(object):
         .. versionadded:: 0.12.0
         """
         from ..viz.topomap import plot_layout
-        return plot_layout(self, show=show)
+        return plot_layout(self, picks=picks, show=show)
 
 
 def _read_lout(fname):
-    """Aux function"""
+    """Aux function."""
     with open(fname) as f:
         box_line = f.readline()  # first line contains box dimension
         box = tuple(map(float, box_line.split()))
@@ -130,7 +136,7 @@ def _read_lout(fname):
 
 
 def _read_lay(fname):
-    """Aux function"""
+    """Aux function."""
     with open(fname) as f:
         box = None
         names, pos, ids = [], [], []
@@ -151,7 +157,7 @@ def _read_lay(fname):
 
 
 def read_layout(kind, path=None, scale=True):
-    """Read layout from a file
+    """Read layout from a file.
 
     Parameters
     ----------
@@ -209,7 +215,7 @@ def read_layout(kind, path=None, scale=True):
 
 
 def make_eeg_layout(info, radius=0.5, width=None, height=None, exclude='bads'):
-    """Create .lout file from EEG electrode digitization
+    """Create .lout file from EEG electrode digitization.
 
     Parameters
     ----------
@@ -284,7 +290,7 @@ def make_eeg_layout(info, radius=0.5, width=None, height=None, exclude='bads'):
 
 
 def make_grid_layout(info, picks=None, n_col=None):
-    """ Generate .lout file for custom data, i.e., ICA sources
+    """Generate .lout file for custom data, i.e., ICA sources.
 
     Parameters
     ----------
@@ -359,7 +365,7 @@ def make_grid_layout(info, picks=None, n_col=None):
 
 
 def find_layout(info, ch_type=None, exclude='bads'):
-    """Choose a layout based on the channels in the info 'chs' field
+    """Choose a layout based on the channels in the info 'chs' field.
 
     Parameters
     ----------
@@ -384,41 +390,12 @@ def find_layout(info, ch_type=None, exclude='bads'):
         raise ValueError('Invalid channel type (%s) requested '
                          '`ch_type` must be %s' % (ch_type, our_types))
 
-    chs = info['chs']
-    # Only take first 16 bits, as higher bits store CTF comp order
-    coil_types = set([ch['coil_type'] & 0xFFFF for ch in chs])
-    channel_types = set([ch['kind'] for ch in chs])
-
-    has_vv_mag = any(k in coil_types for k in
-                     [FIFF.FIFFV_COIL_VV_MAG_T1, FIFF.FIFFV_COIL_VV_MAG_T2,
-                      FIFF.FIFFV_COIL_VV_MAG_T3])
-    has_vv_grad = any(k in coil_types for k in [FIFF.FIFFV_COIL_VV_PLANAR_T1,
-                                                FIFF.FIFFV_COIL_VV_PLANAR_T2,
-                                                FIFF.FIFFV_COIL_VV_PLANAR_T3])
+    (has_vv_mag, has_vv_grad, is_old_vv, has_4D_mag, ctf_other_types,
+     has_CTF_grad, n_kit_grads, has_any_meg, has_eeg_coils,
+     has_eeg_coils_and_meg, has_eeg_coils_only) = _get_ch_info(info)
     has_vv_meg = has_vv_mag and has_vv_grad
     has_vv_only_mag = has_vv_mag and not has_vv_grad
     has_vv_only_grad = has_vv_grad and not has_vv_mag
-    is_old_vv = ' ' in chs[0]['ch_name']
-
-    has_4D_mag = FIFF.FIFFV_COIL_MAGNES_MAG in coil_types
-    ctf_other_types = (FIFF.FIFFV_COIL_CTF_REF_MAG,
-                       FIFF.FIFFV_COIL_CTF_REF_GRAD,
-                       FIFF.FIFFV_COIL_CTF_OFFDIAG_REF_GRAD)
-    has_CTF_grad = (FIFF.FIFFV_COIL_CTF_GRAD in coil_types or
-                    (FIFF.FIFFV_MEG_CH in channel_types and
-                     any(k in ctf_other_types for k in coil_types)))
-    # hack due to MNE-C bug in IO of CTF
-    # only take first 16 bits, as higher bits store CTF comp order
-    n_kit_grads = sum(ch['coil_type'] & 0xFFFF == FIFF.FIFFV_COIL_KIT_GRAD
-                      for ch in chs)
-
-    has_any_meg = any([has_vv_mag, has_vv_grad, has_4D_mag, has_CTF_grad,
-                       n_kit_grads])
-    has_eeg_coils = (FIFF.FIFFV_COIL_EEG in coil_types and
-                     FIFF.FIFFV_EEG_CH in channel_types)
-    has_eeg_coils_and_meg = has_eeg_coils and has_any_meg
-    has_eeg_coils_only = has_eeg_coils and not has_any_meg
-
     if ch_type == "meg" and not has_any_meg:
         raise RuntimeError('No MEG channels present. Cannot find MEG layout.')
 
@@ -448,7 +425,10 @@ def find_layout(info, ch_type=None, exclude='bads'):
     elif n_kit_grads > 0:
         layout_name = _find_kit_layout(info, n_kit_grads)
     else:
-        return None
+        xy = _auto_topomap_coords(info, picks=range(info['nchan']),
+                                  ignore_overlap=True, to_sphere=False)
+        return generate_2d_layout(xy, ch_names=info['ch_names'], name='custom',
+                                  normalize=False)
 
     layout = read_layout(layout_name)
     if not is_old_vv:
@@ -456,11 +436,19 @@ def find_layout(info, ch_type=None, exclude='bads'):
     if has_CTF_grad:
         layout.names = _clean_names(layout.names, before_dash=True)
 
+    # Apply mask for excluded channels.
+    if exclude == 'bads':
+        exclude = info['bads']
+    idx = [ii for ii, name in enumerate(layout.names) if name not in exclude]
+    layout.names = [layout.names[ii] for ii in idx]
+    layout.pos = layout.pos[idx]
+    layout.ids = [layout.ids[ii] for ii in idx]
+
     return layout
 
 
 def _find_kit_layout(info, n_grads):
-    """Determine the KIT layout
+    """Determine the KIT layout.
 
     Parameters
     ----------
@@ -510,7 +498,7 @@ def _find_kit_layout(info, n_grads):
 
 
 def _box_size(points, width=None, height=None, padding=0.0):
-    """ Given a series of points, calculate an appropriate box size.
+    """Given a series of points, calculate an appropriate box size.
 
     Parameters
     ----------
@@ -598,7 +586,7 @@ def _box_size(points, width=None, height=None, padding=0.0):
 
 
 def _find_topomap_coords(info, picks, layout=None):
-    """Try to guess the E/MEG layout and return appropriate topomap coordinates
+    """Guess the E/MEG layout and return appropriate topomap coordinates.
 
     Parameters
     ----------
@@ -629,8 +617,9 @@ def _find_topomap_coords(info, picks, layout=None):
     return pos
 
 
-def _auto_topomap_coords(info, picks, ignore_overlap=False):
+def _auto_topomap_coords(info, picks, ignore_overlap=False, to_sphere=True):
     """Make a 2 dimensional sensor map from sensor positions in an info dict.
+
     The default is to use the electrode locations. The fallback option is to
     attempt using digitization points of kind FIFFV_POINT_EEG. This only works
     with EEG and requires an equal number of digitization points and sensors.
@@ -641,6 +630,12 @@ def _auto_topomap_coords(info, picks, ignore_overlap=False):
         The measurement info.
     picks : list of int
         The channel indices to generate topomap coords for.
+    ignore_overlap : bool
+        Whether to ignore overlapping positions in the layout. If False and
+        positions overlap, an error is thrown.
+    to_sphere : bool
+        If True, the radial distance of spherical coordinates is ignored, in
+        effect fitting the xyz-coordinates to a sphere. Defaults to True.
 
     Returns
     -------
@@ -704,7 +699,7 @@ def _auto_topomap_coords(info, picks, ignore_overlap=False):
 
     # Duplicate points cause all kinds of trouble during visualization
     dist = pdist(locs3d)
-    if np.min(dist) < 1e-10 and not ignore_overlap:
+    if len(locs3d) > 1 and np.min(dist) < 1e-10 and not ignore_overlap:
         problematic_electrodes = [
             chs[elec_i]['ch_name']
             for elec_i in squareform(dist < 1e-10).any(axis=0).nonzero()[0]
@@ -714,14 +709,14 @@ def _auto_topomap_coords(info, picks, ignore_overlap=False):
                          '\n    ' + str(problematic_electrodes) + '\nThis '
                          'causes problems during visualization.')
 
-    x, y, z = locs3d.T
-    az, el, r = _cartesian_to_sphere(x, y, z)
-    locs2d = np.c_[_polar_to_cartesian(az, np.pi / 2 - el)]
-    return locs2d
+    if to_sphere:
+        # use spherical (theta, pol) as (r, theta) for polar->cartesian
+        return _pol_to_cart(_cart_to_sph(locs3d)[:, 1:][:, ::-1])
+    return _pol_to_cart(_cart_to_sph(locs3d))
 
 
 def _topo_to_sphere(pos, eegs):
-    """Helper function for transforming xy-coordinates to sphere.
+    """Transform xy-coordinates to sphere.
 
     Parameters
     ----------
@@ -755,7 +750,7 @@ def _topo_to_sphere(pos, eegs):
 
 def _pair_grad_sensors(info, layout=None, topomap_coords=True, exclude='bads',
                        raise_error=True):
-    """Find the picks for pairing grad channels
+    """Find the picks for pairing grad channels.
 
     Parameters
     ----------
@@ -816,7 +811,7 @@ def _pair_grad_sensors(info, layout=None, topomap_coords=True, exclude='bads',
 # this function is used to pair grad when info is not present
 # it is the case of Projection that don't have the info.
 def _pair_grad_sensors_from_ch_names(ch_names):
-    """Find the indexes for pairing grad channels
+    """Find the indexes for pairing grad channels.
 
     Parameters
     ----------
@@ -841,26 +836,34 @@ def _pair_grad_sensors_from_ch_names(ch_names):
     return grad_chs
 
 
-def _merge_grad_data(data):
-    """Merge data from channel pairs using the RMS
+def _merge_grad_data(data, method='rms'):
+    """Merge data from channel pairs using the RMS or mean.
 
     Parameters
     ----------
     data : array, shape = (n_channels, n_times)
         Data for channels, ordered in pairs.
+    method : str
+        Can be 'rms' or 'mean'.
 
     Returns
     -------
     data : array, shape = (n_channels / 2, n_times)
-        The root mean square for each pair.
+        The root mean square or mean for each pair.
     """
     data = data.reshape((len(data) // 2, 2, -1))
-    data = np.sqrt(np.sum(data ** 2, axis=1) / 2)
+    if method == 'mean':
+        data = np.mean(data, axis=1)
+    elif method == 'rms':
+        data = np.sqrt(np.sum(data ** 2, axis=1) / 2)
+    else:
+        raise ValueError('method must be "rms" or "mean, got %s.' % method)
     return data
 
 
 def generate_2d_layout(xy, w=.07, h=.05, pad=.02, ch_names=None,
-                       ch_indices=None, name='ecog', bg_image=None):
+                       ch_indices=None, name='ecog', bg_image=None,
+                       normalize=True):
     """Generate a custom 2D layout from xy points.
 
     Generates a 2-D layout for plotting with plot_topo methods and
@@ -892,6 +895,9 @@ def generate_2d_layout(xy, w=.07, h=.05, pad=.02, ch_names=None,
         image file, or an array that can be plotted with plt.imshow. If
         provided, xy points will be normalized by the width/height of this
         image. If not, xy points will be normalized by their own min/max.
+    normalize : bool
+        Whether to normalize the coordinates to run from 0 to 1. Defaults to
+        True.
 
     Returns
     -------
@@ -930,7 +936,7 @@ def generate_2d_layout(xy, w=.07, h=.05, pad=.02, ch_names=None,
             img = bg_image
         x /= img.shape[1]
         y /= img.shape[0]
-    else:
+    elif normalize:
         # Normalize x and y by their maxes
         for i_dim in [x, y]:
             i_dim -= i_dim.min(0)
diff --git a/mne/channels/montage.py b/mne/channels/montage.py
index 8b86d4a..fe6dc6b 100644
--- a/mne/channels/montage.py
+++ b/mne/channels/montage.py
@@ -14,27 +14,32 @@ import os
 import os.path as op
 
 import numpy as np
+import xml.etree.ElementTree as ElementTree
 
 from ..viz import plot_montage
 from .channels import _contains_ch_type
-from ..transforms import (_sphere_to_cartesian, apply_trans,
-                          get_ras_to_neuromag_trans, _topo_to_sphere,
-                          _str_to_frame, _frame_to_str)
-from ..io.meas_info import _make_dig_points, _read_dig_points, _read_dig_fif
+from ..transforms import (apply_trans, get_ras_to_neuromag_trans, _sph_to_cart,
+                          _topo_to_sph, _str_to_frame, _frame_to_str)
+from ..io.meas_info import (_make_dig_points, _read_dig_points, _read_dig_fif,
+                            write_dig)
 from ..io.pick import pick_types
 from ..io.open import fiff_open
 from ..io.constants import FIFF
-from ..utils import _check_fname, warn
+from ..utils import (_check_fname, warn, copy_function_doc_to_method_doc,
+                     _clean_names)
 
 from ..externals.six import string_types
 from ..externals.six.moves import map
 
+from .layout import _pol_to_cart, _cart_to_sph
+
 
 class Montage(object):
-    """Montage for EEG cap
+    """Montage for standard EEG electrode locations.
 
-    Montages are typically loaded from a file using read_montage. Only use this
-    class directly if you're constructing a new montage.
+    .. warning:: Montages should typically be loaded from a file using
+                 :func:`mne.channels.read_montage` instead of
+                 instantiating this class directly.
 
     Parameters
     ----------
@@ -47,99 +52,86 @@ class Montage(object):
     selection : array of int
         The indices of the selected channels in the montage file.
 
+    See Also
+    --------
+    DigMontage
+    read_montage
+    read_dig_montage
+
     Notes
     -----
     .. versionadded:: 0.9.0
     """
-    def __init__(self, pos, ch_names, kind, selection):
+
+    def __init__(self, pos, ch_names, kind, selection,
+                 nasion=None, lpa=None, rpa=None):  # noqa: D102
         self.pos = pos
         self.ch_names = ch_names
         self.kind = kind
         self.selection = selection
+        self.lpa = lpa
+        self.nasion = nasion
+        self.rpa = rpa
 
     def __repr__(self):
+        """Return string representation."""
         s = ('<Montage | %s - %d channels: %s ...>'
              % (self.kind, len(self.ch_names), ', '.join(self.ch_names[:3])))
         return s
 
-    def plot(self, scale_factor=1.5, show_names=False):
-        """Plot EEG sensor montage
+    def get_pos2d(self):
+        """Return positions converted to 2D."""
+        return _pol_to_cart(_cart_to_sph(self.pos)[:, 1:][:, ::-1])
 
-        Parameters
-        ----------
-        scale_factor : float
-            Determines the size of the points. Defaults to 1.5
-        show_names : bool
-            Whether to show the channel names. Defaults to False
-
-        Returns
-        -------
-        fig : Instance of matplotlib.figure.Figure
-            The figure object.
-        """
+    @copy_function_doc_to_method_doc(plot_montage)
+    def plot(self, scale_factor=20, show_names=True, kind='topomap',
+             show=True):
         return plot_montage(self, scale_factor=scale_factor,
-                            show_names=show_names)
+                            show_names=show_names, kind=kind, show=show)
+
+
+def get_builtin_montages():
+    """Get a list of all builtin montages.
+
+    Returns
+    -------
+    montages : list
+        Names of all builtin montages that can be loaded with
+        :func:`read_montage`.
+    """
+    path = op.join(op.dirname(__file__), 'data', 'montages')
+    supported = ('.elc', '.txt', '.csd', '.sfp', '.elp', '.hpts', '.loc',
+                 '.locs', '.eloc', '.bvef')
+    files = [op.splitext(f) for f in os.listdir(path)]
+    return sorted([f for f, ext in files if ext in supported])
 
 
 def read_montage(kind, ch_names=None, path=None, unit='m', transform=False):
-    """Read a generic (built-in) montage from a file
+    """Read a generic (built-in) montage.
 
-    This function can be used to read electrode positions from a user specified
-    file using the `kind` and `path` parameters. Alternatively, use only the
-    `kind` parameter to load one of the built-in montages:
+    Individualized (digitized) electrode positions should be read in using
+    :func:`read_dig_montage`.
 
-    ===================   =====================================================
-    Kind                  description
-    ===================   =====================================================
-    standard_1005         Electrodes are named and positioned according to the
-                          international 10-05 system.
-    standard_1020         Electrodes are named and positioned according to the
-                          international 10-20 system.
-    standard_alphabetic   Electrodes are named with LETTER-NUMBER combinations
-                          (A1, B2, F4, etc.)
-    standard_postfixed    Electrodes are named according to the international
-                          10-20 system using postfixes for intermediate
-                          positions.
-    standard_prefixed     Electrodes are named according to the international
-                          10-20 system using prefixes for intermediate
-                          positions.
-    standard_primed       Electrodes are named according to the international
-                          10-20 system using prime marks (' and '') for
-                          intermediate positions.
-
-    biosemi16             BioSemi cap with 16 electrodes
-    biosemi32             BioSemi cap with 32 electrodes
-    biosemi64             BioSemi cap with 64 electrodes
-    biosemi128            BioSemi cap with 128 electrodes
-    biosemi160            BioSemi cap with 160 electrodes
-    biosemi256            BioSemi cap with 256 electrodes
-
-    easycap-M10           Brainproducts EasyCap with electrodes named
-                          according to the 10-05 system
-    easycap-M1            Brainproduct EasyCap with numbered electrodes
-
-    EGI_256               Geodesic Sensor Net with 256 channels
-
-    GSN-HydroCel-32       HydroCel Geodesic Sensor Net with 32 electrodes
-    GSN-HydroCel-64_1.0   HydroCel Geodesic Sensor Net with 64 electrodes
-    GSN-HydroCel-65_1.0   HydroCel Geodesic Sensor Net with 64 electrodes + Cz
-    GSN-HydroCel-128      HydroCel Geodesic Sensor Net with 128 electrodes
-    GSN-HydroCel-129      HydroCel Geodesic Sensor Net with 128 electrodes + Cz
-    GSN-HydroCel-256      HydroCel Geodesic Sensor Net with 256 electrodes
-    GSN-HydroCel-257      HydroCel Geodesic Sensor Net with 256 electrodes + Cz
-    ===================   =====================================================
+    In most cases, you should only need to set the `kind` parameter to load one
+    of the built-in montages (see Notes).
 
     Parameters
     ----------
     kind : str
         The name of the montage file without the file extension (e.g.
         kind='easycap-M10' for 'easycap-M10.txt'). Files with extensions
-        '.elc', '.txt', '.csd', '.elp', '.hpts', '.sfp' or '.loc' ('.locs' and
-        '.eloc') are supported.
+        '.elc', '.txt', '.csd', '.elp', '.hpts', '.sfp', '.loc' ('.locs' and
+        '.eloc') or .bvef are supported.
     ch_names : list of str | None
         If not all electrodes defined in the montage are present in the EEG
-        data, use this parameter to select subset of electrode positions to
+        data, use this parameter to select a subset of electrode positions to
         load. If None (default), all defined electrode positions are returned.
+
+        .. note:: ``ch_names`` are compared to channel names in the montage
+                  file after converting them both to upper case. If a match is
+                  found, the letter case in the original ``ch_names`` is used
+                  in the returned montage.
+
     path : str | None
         The path of the folder containing the montage file. Defaults to the
         mne/channels/data/montages folder in your mne-python installation.
@@ -147,10 +139,9 @@ def read_montage(kind, ch_names=None, path=None, unit='m', transform=False):
         Unit of the input file. If not 'm' (default), coordinates will be
         rescaled to 'm'.
     transform : bool
-        If True, points will be transformed to Neuromag space.
-        The fidicuals, 'nasion', 'lpa', 'rpa' must be specified in
-        the montage file. Useful for points captured using Polhemus FastSCAN.
-        Default is False.
+        If True, points will be transformed to Neuromag space. The fidicuals,
+        'nasion', 'lpa', 'rpa' must be specified in the montage file. Useful
+        for points captured using Polhemus FastSCAN. Default is False.
 
     Returns
     -------
@@ -159,36 +150,87 @@ def read_montage(kind, ch_names=None, path=None, unit='m', transform=False):
 
     See Also
     --------
-    read_dig_montage : To read subject-specific digitization information.
+    DigMontage
+    Montage
+    read_dig_montage
 
     Notes
     -----
     Built-in montages are not scaled or transformed by default.
 
-    Montages can contain fiducial points in addition to electrode
-    locations, e.g. ``biosemi-64`` contains 67 total channels.
+    Montages can contain fiducial points in addition to electrode channels,
+    e.g. ``biosemi64`` contains 67 locations. In the following table, the
+    number of channels and fiducials is given in parentheses in the description
+    column (e.g. 64+3 means 64 channels and 3 fiducials).
+
+    Valid ``kind`` arguments are:
+
+    ===================   =====================================================
+    Kind                  Description
+    ===================   =====================================================
+    standard_1005         Electrodes are named and positioned according to the
+                          international 10-05 system (343+3 locations)
+    standard_1020         Electrodes are named and positioned according to the
+                          international 10-20 system (94+3 locations)
+    standard_alphabetic   Electrodes are named with LETTER-NUMBER combinations
+                          (A1, B2, F4, ...) (65+3 locations)
+    standard_postfixed    Electrodes are named according to the international
+                          10-20 system using postfixes for intermediate
+                          positions (100+3 locations)
+    standard_prefixed     Electrodes are named according to the international
+                          10-20 system using prefixes for intermediate
+                          positions (74+3 locations)
+    standard_primed       Electrodes are named according to the international
+                          10-20 system using prime marks (' and '') for
+                          intermediate positions (100+3 locations)
+
+    biosemi16             BioSemi cap with 16 electrodes (16+3 locations)
+    biosemi32             BioSemi cap with 32 electrodes (32+3 locations)
+    biosemi64             BioSemi cap with 64 electrodes (64+3 locations)
+    biosemi128            BioSemi cap with 128 electrodes (128+3 locations)
+    biosemi160            BioSemi cap with 160 electrodes (160+3 locations)
+    biosemi256            BioSemi cap with 256 electrodes (256+3 locations)
+
+    easycap-M1            EasyCap with 10-05 electrode names (74 locations)
+    easycap-M10           EasyCap with numbered electrodes (61 locations)
+
+    EGI_256               Geodesic Sensor Net (256 locations)
+
+    GSN-HydroCel-32       HydroCel Geodesic Sensor Net and Cz (33+3 locations)
+    GSN-HydroCel-64_1.0   HydroCel Geodesic Sensor Net (64+3 locations)
+    GSN-HydroCel-65_1.0   HydroCel Geodesic Sensor Net and Cz (65+3 locations)
+    GSN-HydroCel-128      HydroCel Geodesic Sensor Net (128+3 locations)
+    GSN-HydroCel-129      HydroCel Geodesic Sensor Net and Cz (129+3 locations)
+    GSN-HydroCel-256      HydroCel Geodesic Sensor Net (256+3 locations)
+    GSN-HydroCel-257      HydroCel Geodesic Sensor Net and Cz (257+3 locations)
+
+    mgh60                 The (older) 60-channel cap used at
+                          MGH (60+3 locations)
+    mgh70                 The (newer) 70-channel BrainVision cap used at
+                          MGH (70+3 locations)
+    ===================   =====================================================
 
     .. versionadded:: 0.9.0
     """
-
     if path is None:
         path = op.join(op.dirname(__file__), 'data', 'montages')
     if not op.isabs(kind):
         supported = ('.elc', '.txt', '.csd', '.sfp', '.elp', '.hpts', '.loc',
-                     '.locs', '.eloc')
+                     '.locs', '.eloc', '.bvef')
         montages = [op.splitext(f) for f in os.listdir(path)]
         montages = [m for m in montages if m[1] in supported and kind == m[0]]
         if len(montages) != 1:
             raise ValueError('Could not find the montage. Please provide the '
                              'full path.')
         kind, ext = montages[0]
-        fname = op.join(path, kind + ext)
     else:
         kind, ext = op.splitext(kind)
-        fname = op.join(path, kind + ext)
+    fname = op.join(path, kind + ext)
 
+    fid_names = ['lpa', 'nz', 'rpa']
     if ext == '.sfp':
         # EGI geodesic
+        fid_names = ['fidt9', 'fidnz', 'fidt10']
         with open(fname, 'r') as f:
             lines = f.read().replace('\t', ' ').splitlines()
 
@@ -234,26 +276,21 @@ def read_montage(kind, ch_names=None, path=None, unit='m', transform=False):
             data = np.genfromtxt(fname, dtype='str', skip_header=1)
         except TypeError:
             data = np.genfromtxt(fname, dtype='str', skiprows=1)
-        ch_names_ = list(data[:, 0])
-        theta, phi = data[:, 1].astype(float), data[:, 2].astype(float)
-        x = 85. * np.cos(np.deg2rad(phi)) * np.sin(np.deg2rad(theta))
-        y = 85. * np.sin(np.deg2rad(theta)) * np.sin(np.deg2rad(phi))
-        z = 85. * np.cos(np.deg2rad(theta))
-        pos = np.c_[x, y, z]
+        ch_names_ = data[:, 0].tolist()
+        az = np.deg2rad(data[:, 2].astype(float))
+        pol = np.deg2rad(data[:, 1].astype(float))
+        pos = _sph_to_cart(np.array([np.ones(len(az)) * 85., az, pol]).T)
     elif ext == '.csd':
         # CSD toolbox
-        dtype = [('label', 'S4'), ('theta', 'f8'), ('phi', 'f8'),
-                 ('radius', 'f8'), ('x', 'f8'), ('y', 'f8'), ('z', 'f8'),
-                 ('off_sph', 'f8')]
         try:  # newer version
-            table = np.loadtxt(fname, skip_header=2, dtype=dtype)
+            data = np.genfromtxt(fname, dtype='str', skip_header=2)
         except TypeError:
-            table = np.loadtxt(fname, skiprows=2, dtype=dtype)
-        ch_names_ = table['label']
-        theta = (2 * np.pi * table['theta']) / 360.
-        phi = (2 * np.pi * table['phi']) / 360.
-        pos = _sphere_to_cartesian(theta, phi, r=1.0)
-        pos = np.asarray(pos).T
+            data = np.genfromtxt(fname, dtype='str', skiprows=2)
+
+        ch_names_ = data[:, 0].tolist()
+        az = np.deg2rad(data[:, 1].astype(float))
+        pol = np.deg2rad(90. - data[:, 2].astype(float))
+        pos = _sph_to_cart(np.array([np.ones(len(az)), az, pol]).T)
     elif ext == '.elp':
         # standard BESA spherical
         dtype = np.dtype('S8, S8, f8, f8, f8')
@@ -262,46 +299,38 @@ def read_montage(kind, ch_names=None, path=None, unit='m', transform=False):
         except TypeError:
             data = np.loadtxt(fname, dtype=dtype, skiprows=1)
 
+        ch_names_ = data['f1'].astype(str).tolist()
         az = data['f2']
         horiz = data['f3']
-
         radius = np.abs(az / 180.)
-        angles = np.array([90. - h if a >= 0. else -90. - h
-                           for h, a in zip(horiz, az)])
-
-        sph_phi = (0.5 - radius) * 180.
-        sph_theta = angles
-
-        azimuth = sph_theta / 180.0 * np.pi
-        elevation = sph_phi / 180.0 * np.pi
-        r = 85.
-
-        y, x, z = _sphere_to_cartesian(azimuth, elevation, r)
-
-        pos = np.c_[x, y, z]
-        ch_names_ = data['f1'].astype(np.str)
+        az = np.deg2rad(np.array([h if a >= 0. else 180 + h
+                                  for h, a in zip(horiz, az)]))
+        pol = radius * np.pi
+        pos = _sph_to_cart(np.array([np.ones(len(az)) * 85., az, pol]).T)
     elif ext == '.hpts':
         # MNE-C specified format for generic digitizer data
+        fid_names = ['1', '2', '3']
         dtype = [('type', 'S8'), ('name', 'S8'),
                  ('x', 'f8'), ('y', 'f8'), ('z', 'f8')]
         data = np.loadtxt(fname, dtype=dtype)
+        ch_names_ = data['name'].astype(str).tolist()
         pos = np.vstack((data['x'], data['y'], data['z'])).T
-        ch_names_ = data['name'].astype(np.str)
     elif ext in ('.loc', '.locs', '.eloc'):
         ch_names_ = np.loadtxt(fname, dtype='S4',
-                               usecols=[3]).astype(np.str).tolist()
-        dtype = {'names': ('angle', 'radius'), 'formats': ('f4', 'f4')}
-        angle, radius = np.loadtxt(fname, dtype=dtype, usecols=[1, 2],
-                                   unpack=True)
-
-        sph_phi, sph_theta = _topo_to_sphere(angle, radius)
-
-        azimuth = sph_theta / 180.0 * np.pi
-        elevation = sph_phi / 180.0 * np.pi
-        r = np.ones((len(ch_names_), ))
-
-        x, y, z = _sphere_to_cartesian(azimuth, elevation, r)
-        pos = np.c_[-y, x, z]
+                               usecols=[3]).astype(str).tolist()
+        topo = np.loadtxt(fname, dtype=float, usecols=[1, 2])
+        sph = _topo_to_sph(topo)
+        pos = _sph_to_cart(sph)
+        pos[:, [0, 1]] = pos[:, [1, 0]] * [-1, 1]
+    elif ext == '.bvef':
+        # 'BrainVision Electrodes File' format
+        root = ElementTree.parse(fname).getroot()
+        ch_names_ = [s.text for s in root.findall("./Electrode/Name")]
+        theta = [float(s.text) for s in root.findall("./Electrode/Theta")]
+        pol = np.deg2rad(np.array(theta))
+        phi = [float(s.text) for s in root.findall("./Electrode/Phi")]
+        az = np.deg2rad(np.array(phi))
+        pos = _sph_to_cart(np.array([np.ones(len(az)) * 85., az, pol]).T)
     else:
         raise ValueError('Currently the "%s" template is not supported.' %
                          kind)
@@ -313,44 +342,44 @@ def read_montage(kind, ch_names=None, path=None, unit='m', transform=False):
         pos /= 1e2
     elif unit != 'm':
         raise ValueError("'unit' should be either 'm', 'cm', or 'mm'.")
+    names_lower = [name.lower() for name in list(ch_names_)]
+    fids = {key: pos[names_lower.index(fid_names[ii])]
+            if fid_names[ii] in names_lower else None
+            for ii, key in enumerate(['lpa', 'nasion', 'rpa'])}
     if transform:
-        names_lower = [name.lower() for name in list(ch_names_)]
-        if ext == '.hpts':
-            fids = ('2', '1', '3')  # Alternate cardinal point names
-        else:
-            fids = ('nz', 'lpa', 'rpa')
-
-        missing = [name for name in fids
-                   if name not in names_lower]
+        missing = [name for name, val in fids.items() if val is None]
         if missing:
             raise ValueError("The points %s are missing, but are needed "
                              "to transform the points to the MNE coordinate "
                              "system. Either add the points, or read the "
                              "montage with transform=False. " % missing)
-        nasion = pos[names_lower.index(fids[0])]
-        lpa = pos[names_lower.index(fids[1])]
-        rpa = pos[names_lower.index(fids[2])]
-
-        neuromag_trans = get_ras_to_neuromag_trans(nasion, lpa, rpa)
+        neuromag_trans = get_ras_to_neuromag_trans(
+            fids['nasion'], fids['lpa'], fids['rpa'])
         pos = apply_trans(neuromag_trans, pos)
+    fids = {key: pos[names_lower.index(fid_names[ii])]
+            if fid_names[ii] in names_lower else None
+            for ii, key in enumerate(['lpa', 'nasion', 'rpa'])}
 
     if ch_names is not None:
-        sel, ch_names_ = zip(*[(i, e) for i, e in enumerate(ch_names_)
-                             if e in ch_names])
+        # Ensure channels with differing case are found.
+        upper_names = [ch_name.upper() for ch_name in ch_names]
+        sel, ch_names_ = zip(*[(i, ch_names[upper_names.index(e)]) for i, e in
+                               enumerate([n.upper() for n in ch_names_])
+                               if e in upper_names])
         sel = list(sel)
         pos = pos[sel]
         selection = selection[sel]
-    else:
-        ch_names_ = list(ch_names_)
     kind = op.split(kind)[-1]
-    return Montage(pos=pos, ch_names=ch_names_, kind=kind, selection=selection)
+    return Montage(pos=pos, ch_names=ch_names_, kind=kind, selection=selection,
+                   lpa=fids['lpa'], nasion=fids['nasion'], rpa=fids['rpa'])
 
 
 class DigMontage(object):
-    """Montage for Digitized data
+    """Montage for digitized electrode and headshape position data.
 
-    Montages are typically loaded from a file using read_dig_montage. Only use
-    this class directly if you're constructing a new montage.
+    .. warning:: Montages are typically loaded from a file using
+                 :func:`read_dig_montage` instead of instantiating
+                 this class.
 
     Parameters
     ----------
@@ -378,51 +407,135 @@ class DigMontage(object):
 
         .. versionadded:: 0.12
 
+    coord_frame : str
+        The coordinate frame of the points. Usually this is "unknown"
+        for native digitizer space.
+
+    See Also
+    --------
+    Montage
+    read_dig_montage
+    read_montage
+
     Notes
     -----
     .. versionadded:: 0.9.0
     """
-    def __init__(self, hsp, hpi, elp, point_names,
+
+    def __init__(self, hsp=None, hpi=None, elp=None, point_names=None,
                  nasion=None, lpa=None, rpa=None, dev_head_t=None,
-                 dig_ch_pos=None):
+                 dig_ch_pos=None, coord_frame='unknown'):  # noqa: D102
         self.hsp = hsp
         self.hpi = hpi
+        if elp is not None:
+            if not isinstance(point_names, Iterable):
+                raise TypeError('If elp is specified, point_names must '
+                                'provide a list of str with one entry per ELP '
+                                'point')
+            point_names = list(point_names)
+            if len(point_names) != len(elp):
+                raise ValueError('elp contains %i points but %i '
+                                 'point_names were specified.' %
+                                 (len(elp), len(point_names)))
         self.elp = elp
         self.point_names = point_names
 
         self.nasion = nasion
         self.lpa = lpa
         self.rpa = rpa
-        if dev_head_t is None:
-            self.dev_head_t = np.identity(4)
-        else:
-            self.dev_head_t = dev_head_t
+        self.dev_head_t = dev_head_t
         self.dig_ch_pos = dig_ch_pos
+        if not isinstance(coord_frame, string_types) or \
+                coord_frame not in _str_to_frame:
+            raise ValueError('coord_frame must be one of %s, got %s'
+                             % (sorted(_str_to_frame.keys()), coord_frame))
+        self.coord_frame = coord_frame
 
     def __repr__(self):
-        s = '<DigMontage | %d Dig Points, %d HPI points: %s ...>'
-        s %= (len(self.hsp), len(self.point_names),
-              ', '.join(self.point_names[:3]))
+        """Return string representation."""
+        s = ('<DigMontage | %d extras (headshape), %d HPIs, %d fiducials, %d '
+             'channels>' %
+             (len(self.hsp) if self.hsp is not None else 0,
+              len(self.point_names) if self.point_names is not None else 0,
+              sum(x is not None for x in (self.lpa, self.rpa, self.nasion)),
+              len(self.dig_ch_pos) if self.dig_ch_pos is not None else 0,))
         return s
 
-    def plot(self, scale_factor=1.5, show_names=False):
-        """Plot EEG sensor montage
+    @copy_function_doc_to_method_doc(plot_montage)
+    def plot(self, scale_factor=20, show_names=False, kind='3d', show=True):
+        return plot_montage(self, scale_factor=scale_factor,
+                            show_names=show_names, kind=kind, show=show)
+
+    def transform_to_head(self):
+        """Transform digitizer points to Neuromag head coordinates."""
+        if self.coord_frame == 'head':  # nothing to do
+            return
+        nasion, rpa, lpa = self.nasion, self.rpa, self.lpa
+        if any(x is None for x in (nasion, rpa, lpa)):
+            if self.elp is None or self.point_names is None:
+                raise ValueError('ELP points and names must be specified for '
+                                 'transformation.')
+            names = [name.lower() for name in self.point_names]
+
+            # check that all needed points are present
+            kinds = ('nasion', 'lpa', 'rpa')
+            missing = [name for name in kinds if name not in names]
+            if len(missing) > 0:
+                raise ValueError('The points %s are missing, but are needed '
+                                 'to transform the points to the MNE '
+                                 'coordinate system. Either add the points, '
+                                 'or read the montage with transform=False.'
+                                 % str(missing))
+
+            nasion, lpa, rpa = [self.elp[names.index(kind)] for kind in kinds]
+
+            # remove fiducials from elp
+            mask = np.ones(len(names), dtype=bool)
+            for fid in ['nasion', 'lpa', 'rpa']:
+                mask[names.index(fid)] = False
+            self.elp = self.elp[mask]
+            self.point_names = [p for pi, p in enumerate(self.point_names)
+                                if mask[pi]]
+
+        native_head_t = get_ras_to_neuromag_trans(nasion, lpa, rpa)
+        self.nasion, self.lpa, self.rpa = apply_trans(
+            native_head_t, np.array([nasion, lpa, rpa]))
+        if self.elp is not None:
+            self.elp = apply_trans(native_head_t, self.elp)
+        if self.hsp is not None:
+            self.hsp = apply_trans(native_head_t, self.hsp)
+        if self.dig_ch_pos is not None:
+            for key, val in self.dig_ch_pos.items():
+                self.dig_ch_pos[key] = apply_trans(native_head_t, val)
+        self.coord_frame = 'head'
+
+    def compute_dev_head_t(self):
+        """Compute the Neuromag dev_head_t from matched points."""
+        from ..coreg import fit_matched_points
+        if self.elp is None or self.hpi is None:
+            raise RuntimeError('must have both elp and hpi to compute the '
+                               'device to head transform')
+        self.dev_head_t = fit_matched_points(tgt_pts=self.elp,
+                                             src_pts=self.hpi, out='trans')
+
+    def _get_dig(self):
+        """Get the digitization list."""
+        return _make_dig_points(
+            nasion=self.nasion, lpa=self.lpa, rpa=self.rpa, hpi=self.elp,
+            extra_points=self.hsp, dig_ch_pos=self.dig_ch_pos)
+
+    def save(self, fname):
+        """Save digitization points to FIF.
 
         Parameters
         ----------
-        scale_factor : float
-            Determines the size of the points. Defaults to 1.5
-        show_names : bool
-            Whether to show the channel names. Defaults to False
-
-        Returns
-        -------
-        fig : Instance of matplotlib.figure.Figure
-            The figure object.
+        fname : str
+            The filename to use. Should end in .fif or .fif.gz.
         """
-        from ..viz import plot_montage
-        return plot_montage(self, scale_factor=scale_factor,
-                            show_names=show_names)
+        if self.coord_frame != 'head':
+            raise RuntimeError('Can only write out digitization points in '
+                               'head coordinates.')
+        write_dig(fname, self._get_dig())
 
 
 _cardinal_ident_mapping = {
@@ -433,15 +546,16 @@ _cardinal_ident_mapping = {
 
 
 def _check_frame(d, frame_str):
-    """Helper to check coordinate frames"""
+    """Check coordinate frames."""
     if d['coord_frame'] != _str_to_frame[frame_str]:
         raise RuntimeError('dig point must be in %s coordinate frame, got %s'
                            % (frame_str, _frame_to_str[d['coord_frame']]))
 
 
 def read_dig_montage(hsp=None, hpi=None, elp=None, point_names=None,
-                     unit='auto', fif=None, transform=True, dev_head_t=False):
-    """Read subject-specific digitization montage from a file
+                     unit='auto', fif=None, egi=None, transform=True,
+                     dev_head_t=False):
+    r"""Read subject-specific digitization montage from a file.
 
     Parameters
     ----------
@@ -454,18 +568,21 @@ def read_dig_montage(hsp=None, hpi=None, elp=None, point_names=None,
     hpi : None | str | array, shape (n_hpi, 3)
         If str, this corresponds to the filename of Head Position Indicator
         (HPI) points. If numpy.array, this corresponds to an array
-        of HPI points. These points are in device space.
+        of HPI points. These points are in device space, and are only
+        necessary if computation of a ``dev_head_t`` by the
+        :class:`DigMontage` is required.
     elp : None | str | array, shape (n_fids + n_hpi, 3)
         If str, this corresponds to the filename of electrode position
         points. This is typically used with the Polhemus FastSCAN system.
-        Fiducials should be listed first: nasion, left periauricular point,
-        right periauricular point, then the points corresponding to the HPI.
         If numpy.array, this corresponds to an array of digitizer points in
         the same order. These points are assumed to be in the native digitizer
         space and will be rescaled according to the unit parameter.
     point_names : None | list
-        If list, this corresponds to a list of point names. This must be
-        specified if elp is defined.
+        A list of point names for elp (required if elp is defined).
+        Typically this would be like::
+
+            ('nasion', 'lpa', 'rpa', 'CHPI001', 'CHPI002', 'CHPI003')
+
     unit : 'auto' | 'm' | 'cm' | 'mm'
         Unit of the digitizer files (hsp and elp). If not 'm', coordinates will
         be rescaled to 'm'. Default is 'auto', which assumes 'm' for \*.hsp and
@@ -477,17 +594,25 @@ def read_dig_montage(hsp=None, hpi=None, elp=None, point_names=None,
 
         .. versionadded:: 0.12
 
+    egi : str | None
+        EGI MFF XML coordinates file from which to read digitization locations.
+        If str (filename), all other arguments are ignored.
+
+        .. versionadded:: 0.14
+
     transform : bool
-        If True, points will be transformed to Neuromag space.
-        The fidicuals, 'nasion', 'lpa', 'rpa' must be specified in
-        the montage file. Useful for points captured using Polhemus FastSCAN.
-        Default is True.
+        If True (default), points will be transformed to Neuromag space
+        using :meth:`DigMontage.transform_to_head`.
+        The fidicuals (nasion, lpa, and rpa) must be specified.
+        This is useful for points captured using a device that does
+        not automatically convert points to Neuromag head coordinates
+        (e.g., Polhemus FastSCAN).
     dev_head_t : bool
         If True, a Dev-to-Head transformation matrix will be added to the
-        montage. To get a proper `dev_head_t`, the hpi and the elp points
-        must be in the same order. If False, an identity matrix will be added
-        to the montage. Default is False.
-
+        montage using :meth:`DigMontage.compute_dev_head_t`.
+        To get a proper `dev_head_t`, the hpi and the elp points
+        must be in the same order. If False (default), no transformation
+        will be added to the montage.
 
     Returns
     -------
@@ -496,7 +621,9 @@ def read_dig_montage(hsp=None, hpi=None, elp=None, point_names=None,
 
     See Also
     --------
-    read_montage : Function to read generic EEG templates
+    DigMontage
+    Montage
+    read_montage
 
     Notes
     -----
@@ -510,10 +637,10 @@ def read_dig_montage(hsp=None, hpi=None, elp=None, point_names=None,
         if dev_head_t or not transform:
             raise ValueError('transform must be True and dev_head_t must be '
                              'False for FIF dig montage')
-        if not all(x is None for x in (hsp, hpi, elp, point_names)):
-            raise ValueError('hsp, hpi, elp, and point_names must all be None '
-                             'if fif is not None')
-        _check_fname(fif, overwrite=True, must_exist=True)
+        if not all(x is None for x in (hsp, hpi, elp, point_names, egi)):
+            raise ValueError('hsp, hpi, elp, point_names, egi must all be '
+                             'None if fif is not None')
+        _check_fname(fif, overwrite='read', must_exist=True)
         # Load the dig data
         f, tree = fiff_open(fif)[:2]
         with f as fid:
@@ -540,26 +667,75 @@ def read_dig_montage(hsp=None, hpi=None, elp=None, point_names=None,
             elif d['kind'] == FIFF.FIFFV_POINT_EEG:
                 _check_frame(d, 'head')
                 dig_ch_pos['EEG%03d' % d['ident']] = d['r']
-        fids = np.array([fids[key] for key in ('nasion', 'lpa', 'rpa')])
-        hsp = np.array(hsp)
-        elp = np.array(elp)
+        fids = [fids.get(key) for key in ('nasion', 'lpa', 'rpa')]
+        hsp = np.array(hsp) if len(hsp) else None
+        elp = np.array(elp) if len(elp) else None
+        coord_frame = 'head'
+    elif egi is not None:
+        if not all(x is None for x in (hsp, hpi, elp, point_names, fif)):
+            raise ValueError('hsp, hpi, elp, point_names, fif must all be '
+                             'None if egi is not None')
+        _check_fname(egi, overwrite='read', must_exist=True)
+
+        root = ElementTree.parse(egi).getroot()
+        ns = root.tag[root.tag.index('{'):root.tag.index('}') + 1]
+        sensors = root.find('%ssensorLayout/%ssensors' % (ns, ns))
+        fids = dict()
+        dig_ch_pos = dict()
+
+        fid_name_map = {'Nasion': 'nasion',
+                        'Right periauricular point': 'rpa',
+                        'Left periauricular point': 'lpa'}
+
+        scale = dict(mm=1e-3, cm=1e-2, auto=1e-2, m=1)
+        if unit not in scale:
+            raise ValueError("Unit needs to be one of %s, not %r" %
+                             (sorted(scale.keys()), unit))
+
+        for s in sensors:
+            name, number, kind = s[0].text, int(s[1].text), int(s[2].text)
+            coordinates = np.array([float(s[3].text), float(s[4].text),
+                                    float(s[5].text)])
+
+            coordinates *= scale[unit]
+
+            # EEG Channels
+            if kind == 0:
+                dig_ch_pos['EEG %03d' % number] = coordinates
+            # Reference
+            elif kind == 1:
+                dig_ch_pos['EEG %03d' %
+                           (len(dig_ch_pos.keys()) + 1)] = coordinates
+            # Fiducials
+            elif kind == 2:
+                fid_name = fid_name_map[name]
+                fids[fid_name] = coordinates
+            # Unknown
+            else:
+                warn('Unknown sensor type %s detected. Skipping sensor...'
+                     'Proceed with caution!' % kind)
+
+        fids = [fids[key] for key in ('nasion', 'lpa', 'rpa')]
+        coord_frame = 'unknown'
+
     else:
+        fids = [None] * 3
         dig_ch_pos = None
-        scale = {'mm': 1e-3, 'cm': 1e-2, 'auto': 1e-3, 'm': None}
+        scale = dict(mm=1e-3, cm=1e-2, auto=1e-3, m=1)
         if unit not in scale:
             raise ValueError("Unit needs to be one of %s, not %r" %
-                             (tuple(map(repr, scale)), unit))
+                             (sorted(scale.keys()), unit))
 
         # HSP
         if isinstance(hsp, string_types):
             hsp = _read_dig_points(hsp, unit=unit)
-        elif hsp is not None and scale[unit]:
+        elif hsp is not None:
             hsp *= scale[unit]
 
         # HPI
         if isinstance(hpi, string_types):
             ext = op.splitext(hpi)[-1]
-            if ext == '.txt':
+            if ext in ('.txt', '.mat'):
                 hpi = _read_dig_points(hpi, unit='m')
             elif ext in ('.sqd', '.mrk'):
                 from ..io.kit import read_mrk
@@ -574,63 +750,19 @@ def read_dig_montage(hsp=None, hpi=None, elp=None, point_names=None,
             elp = _read_dig_points(elp, unit=unit)
         elif elp is not None and scale[unit]:
             elp *= scale[unit]
+        coord_frame = 'unknown'
 
-        if elp is not None:
-            if not isinstance(point_names, Iterable):
-                raise TypeError("If elp is specified, point_names must "
-                                "provide a list of str with one entry per ELP "
-                                "point")
-            point_names = list(point_names)
-            if len(point_names) != len(elp):
-                raise ValueError("The elp file contains %i points, but %i "
-                                 "names were specified." %
-                                 (len(elp), len(point_names)))
-
-        # Transform digitizer coordinates to neuromag space
-        if transform:
-            if elp is None:
-                raise ValueError("ELP points are not specified. Points are "
-                                 "needed for transformation.")
-            names_lower = [name.lower() for name in point_names]
-
-            # check that all needed points are present
-            missing = [name for name in ('nasion', 'lpa', 'rpa')
-                       if name not in names_lower]
-            if missing:
-                raise ValueError("The points %s are missing, but are needed "
-                                 "to transform the points to the MNE "
-                                 "coordinate system. Either add the points, "
-                                 "or read the montage with transform=False."
-                                 % str(missing))
-
-            nasion = elp[names_lower.index('nasion')]
-            lpa = elp[names_lower.index('lpa')]
-            rpa = elp[names_lower.index('rpa')]
-
-            # remove fiducials from elp
-            mask = np.ones(len(names_lower), dtype=bool)
-            for fid in ['nasion', 'lpa', 'rpa']:
-                mask[names_lower.index(fid)] = False
-            elp = elp[mask]
-
-            neuromag_trans = get_ras_to_neuromag_trans(nasion, lpa, rpa)
-            fids = apply_trans(neuromag_trans, [nasion, lpa, rpa])
-            elp = apply_trans(neuromag_trans, elp)
-            hsp = apply_trans(neuromag_trans, hsp)
-        else:
-            fids = [None] * 3
-
+    # Transform digitizer coordinates to neuromag space
+    out = DigMontage(hsp, hpi, elp, point_names, fids[0], fids[1], fids[2],
+                     dig_ch_pos=dig_ch_pos, coord_frame=coord_frame)
+    if fif is None and transform:  # only need to do this for non-Neuromag
+        out.transform_to_head()
     if dev_head_t:
-        from ..coreg import fit_matched_points
-        trans = fit_matched_points(tgt_pts=elp, src_pts=hpi, out='trans')
-    else:
-        trans = np.identity(4)
+        out.compute_dev_head_t()
+    return out
 
-    return DigMontage(hsp, hpi, elp, point_names, fids[0], fids[1], fids[2],
-                      trans, dig_ch_pos)
 
-
-def _set_montage(info, montage, update_ch_names=False):
+def _set_montage(info, montage, update_ch_names=False, set_dig=True):
     """Apply montage to data.
 
     With a Montage, this function will replace the EEG channel names and
@@ -650,6 +782,7 @@ def _set_montage(info, montage, update_ch_names=False):
         The montage to apply.
     update_ch_names : bool
         If True, overwrite the info channel names with the ones from montage.
+        Defaults to False.
 
     Notes
     -----
@@ -671,14 +804,35 @@ def _set_montage(info, montage, update_ch_names=False):
             raise ValueError('No EEG channels found.')
 
         sensors_found = []
-        for pos, ch_name in zip(montage.pos, montage.ch_names):
-            if ch_name not in info['ch_names']:
+
+        # If there are no name collisions, match channel names in a case
+        # insensitive manner.
+        montage_lower = [ch_name.lower() for ch_name in montage.ch_names]
+        info_lower = [ch_name.lower() for ch_name in info['ch_names']]
+        if (len(set(montage_lower)) == len(montage_lower) and
+                len(set(info_lower)) == len(info_lower)):
+            montage_ch_names = montage_lower
+            info_ch_names = info_lower
+        else:
+            montage_ch_names = montage.ch_names
+            info_ch_names = info['ch_names']
+        info_ch_names = _clean_names(info_ch_names, remove_whitespace=True)
+        montage_ch_names = _clean_names(montage_ch_names,
+                                        remove_whitespace=True)
+
+        dig = dict()
+        for pos, ch_name in zip(montage.pos, montage_ch_names):
+            if ch_name not in info_ch_names:
                 continue
 
-            ch_idx = info['ch_names'].index(ch_name)
+            ch_idx = info_ch_names.index(ch_name)
             info['chs'][ch_idx]['loc'] = np.r_[pos, [0.] * 9]
             sensors_found.append(ch_idx)
-
+            dig[ch_name] = pos
+        if set_dig:
+            info['dig'] = _make_dig_points(
+                nasion=montage.nasion, lpa=montage.lpa, rpa=montage.rpa,
+                dig_ch_pos=dig)
         if len(sensors_found) == 0:
             raise ValueError('None of the sensors defined in the montage were '
                              'found in the info structure. Check the channel '
@@ -695,17 +849,18 @@ def _set_montage(info, montage, update_ch_names=False):
                  'left untouched.')
 
     elif isinstance(montage, DigMontage):
-        dig = _make_dig_points(nasion=montage.nasion, lpa=montage.lpa,
-                               rpa=montage.rpa, hpi=montage.hpi,
-                               dig_points=montage.hsp,
-                               dig_ch_pos=montage.dig_ch_pos)
-        info['dig'] = dig
-        info['dev_head_t']['trans'] = montage.dev_head_t
+        if set_dig:
+            info['dig'] = montage._get_dig()
+
+        if montage.dev_head_t is not None:
+            info['dev_head_t']['trans'] = montage.dev_head_t
+
         if montage.dig_ch_pos is not None:  # update channel positions, too
             eeg_ref_pos = montage.dig_ch_pos.get('EEG000', np.zeros(3))
             did_set = np.zeros(len(info['ch_names']), bool)
             is_eeg = np.zeros(len(info['ch_names']), bool)
             is_eeg[pick_types(info, meg=False, eeg=True, exclude=())] = True
+
             for ch_name, ch_pos in montage.dig_ch_pos.items():
                 if ch_name == 'EEG000':
                     continue
@@ -716,8 +871,10 @@ def _set_montage(info, montage, update_ch_names=False):
                 did_set[idx] = True
                 this_loc = np.concatenate((ch_pos, eeg_ref_pos))
                 info['chs'][idx]['loc'][:6] = this_loc
+
             did_not_set = [info['chs'][ii]['ch_name']
                            for ii in np.where(is_eeg & ~did_set)[0]]
+
             if len(did_not_set) > 0:
                 warn('Did not set %s channel positions:\n%s'
                      % (len(did_not_set), ', '.join(did_not_set)))
diff --git a/mne/channels/tests/test_channels.py b/mne/channels/tests/test_channels.py
index 0cda23d..4db0045 100644
--- a/mne/channels/tests/test_channels.py
+++ b/mne/channels/tests/test_channels.py
@@ -12,14 +12,17 @@ import warnings
 import numpy as np
 from scipy.io import savemat
 from numpy.testing import assert_array_equal
-from nose.tools import assert_raises, assert_true, assert_equal
+from nose.tools import assert_raises, assert_true, assert_equal, assert_false
 
-from mne.channels import rename_channels, read_ch_connectivity
-from mne.channels.channels import _ch_neighbor_connectivity
-from mne.io import read_info, read_raw_fif
+from mne.channels import (rename_channels, read_ch_connectivity,
+                          find_ch_connectivity)
+from mne.channels.channels import (_ch_neighbor_connectivity,
+                                   _compute_ch_connectivity)
+from mne.io import read_info, read_raw_fif, read_raw_ctf, read_raw_bti
 from mne.io.constants import FIFF
 from mne.utils import _TempDir, run_tests_if_main
 from mne import pick_types, pick_channels
+from mne.datasets import testing
 
 base_dir = op.join(op.dirname(__file__), '..', '..', 'io', 'tests', 'data')
 raw_fname = op.join(base_dir, 'test_raw.fif')
@@ -42,6 +45,11 @@ def test_rename_channels():
     assert_raises(ValueError, rename_channels, info, mapping)
     # Test bad input
     assert_raises(ValueError, rename_channels, info, 1.)
+    assert_raises(ValueError, rename_channels, info, 1.)
+    # Test name too long (channel names must be less than 15 characters)
+    A16 = 'A' * 16
+    mapping = {'MEG 2641': A16}
+    assert_raises(ValueError, rename_channels, info, mapping)
 
     # Test successful changes
     # Test ch_name and ch_names are changed
@@ -65,7 +73,7 @@ def test_rename_channels():
 
 def test_set_channel_types():
     """Test set_channel_types"""
-    raw = read_raw_fif(raw_fname, add_eeg_ref=False)
+    raw = read_raw_fif(raw_fname)
     # Error Tests
     # Test channel name exists in ch_names
     mapping = {'EEG 160': 'EEG060'}
@@ -79,7 +87,7 @@ def test_set_channel_types():
                'MEG 2442': 'hbo'}
     assert_raises(RuntimeError, raw.set_channel_types, mapping)
     # Test type change
-    raw2 = read_raw_fif(raw_fname, add_eeg_ref=False)
+    raw2 = read_raw_fif(raw_fname)
     raw2.info['bads'] = ['EEG 059', 'EEG 060', 'EOG 061']
     with warnings.catch_warnings(record=True):  # MEG channel change
         assert_raises(RuntimeError, raw2.set_channel_types, mapping)  # has prj
@@ -161,10 +169,47 @@ def test_read_ch_connectivity():
     assert_equal(len(ch_names), 102)
     assert_raises(ValueError, read_ch_connectivity, 'bananas!')
 
+    # In EGI 256, E31 sensor has no neighbour
+    a = partial(np.array)
+    nbh = np.array([[(['E31'], []),
+                     (['E1'], [[a(['E2'])],
+                               [a(['E3'])]]),
+                     (['E2'], [[a(['E1'])],
+                               [a(['E3'])]]),
+                     (['E3'], [[a(['E1'])],
+                               [a(['E2'])]])]],
+                   dtype=[('label', 'O'), ('neighblabel', 'O')])
+    mat = dict(neighbours=nbh)
+    mat_fname = op.join(tempdir, 'test_isolated_mat.mat')
+    savemat(mat_fname, mat, oned_as='row')
+    ch_connectivity, ch_names = read_ch_connectivity(mat_fname)
+    x = ch_connectivity.todense()
+    assert_equal(x.shape[0], len(ch_names))
+    assert_equal(x.shape, (4, 4))
+    assert_true(np.all(x.diagonal()))
+    assert_false(np.any(x[0, 1:]))
+    assert_false(np.any(x[1:, 0]))
+
+    # Check for neighbours consistency. If a sensor is marked as a neighbour,
+    # then it should also have its neighbours defined.
+    a = partial(np.array)
+    nbh = np.array([[(['E31'], []),
+                     (['E1'], [[a(['E8'])],
+                               [a(['E3'])]]),
+                     (['E2'], [[a(['E1'])],
+                               [a(['E3'])]]),
+                     (['E3'], [[a(['E1'])],
+                               [a(['E2'])]])]],
+                   dtype=[('label', 'O'), ('neighblabel', 'O')])
+    mat = dict(neighbours=nbh)
+    mat_fname = op.join(tempdir, 'test_error_mat.mat')
+    savemat(mat_fname, mat, oned_as='row')
+    assert_raises(ValueError, read_ch_connectivity, mat_fname)
+
 
 def test_get_set_sensor_positions():
     """Test get/set functions for sensor positions"""
-    raw1 = read_raw_fif(raw_fname, add_eeg_ref=False)
+    raw1 = read_raw_fif(raw_fname)
     picks = pick_types(raw1.info, meg=False, eeg=True)
     pos = np.array([ch['loc'][:3] for ch in raw1.info['chs']])[picks]
     raw_pos = raw1._get_channel_positions(picks=picks)
@@ -172,10 +217,48 @@ def test_get_set_sensor_positions():
 
     ch_name = raw1.info['ch_names'][13]
     assert_raises(ValueError, raw1._set_channel_positions, [1, 2], ['name'])
-    raw2 = read_raw_fif(raw_fname, add_eeg_ref=False)
+    raw2 = read_raw_fif(raw_fname)
     raw2.info['chs'][13]['loc'][:3] = np.array([1, 2, 3])
     raw1._set_channel_positions([[1, 2, 3]], [ch_name])
     assert_array_equal(raw1.info['chs'][13]['loc'],
                        raw2.info['chs'][13]['loc'])
 
+
+ at testing.requires_testing_data
+def test_find_ch_connectivity():
+    """Test computing the connectivity matrix."""
+    data_path = testing.data_path()
+
+    raw = read_raw_fif(raw_fname, preload=True)
+    sizes = {'mag': 828, 'grad': 1700, 'eeg': 386}
+    nchans = {'mag': 102, 'grad': 204, 'eeg': 60}
+    for ch_type in ['mag', 'grad', 'eeg']:
+        conn, ch_names = find_ch_connectivity(raw.info, ch_type)
+        # Silly test for checking the number of neighbors.
+        assert_equal(conn.getnnz(), sizes[ch_type])
+        assert_equal(len(ch_names), nchans[ch_type])
+    assert_raises(ValueError, find_ch_connectivity, raw.info, None)
+
+    # Test computing the conn matrix with gradiometers.
+    conn, ch_names = _compute_ch_connectivity(raw.info, 'grad')
+    assert_equal(conn.getnnz(), 2680)
+
+    # Test ch_type=None.
+    raw.pick_types(meg='mag')
+    find_ch_connectivity(raw.info, None)
+
+    bti_fname = op.join(data_path, 'BTi', 'erm_HFH', 'c,rfDC')
+    bti_config_name = op.join(data_path, 'BTi', 'erm_HFH', 'config')
+    raw = read_raw_bti(bti_fname, bti_config_name, None)
+    _, ch_names = find_ch_connectivity(raw.info, 'mag')
+    assert_true('A1' in ch_names)
+
+    ctf_fname = op.join(data_path, 'CTF', 'testdata_ctf_short.ds')
+    raw = read_raw_ctf(ctf_fname)
+    _, ch_names = find_ch_connectivity(raw.info, 'mag')
+    assert_true('MLC11' in ch_names)
+
+    assert_raises(ValueError, find_ch_connectivity, raw.info, 'eog')
+
+
 run_tests_if_main()
diff --git a/mne/channels/tests/test_interpolation.py b/mne/channels/tests/test_interpolation.py
index a326a80..820a221 100644
--- a/mne/channels/tests/test_interpolation.py
+++ b/mne/channels/tests/test_interpolation.py
@@ -3,11 +3,12 @@ import warnings
 
 import numpy as np
 from numpy.testing import (assert_allclose, assert_array_equal)
+import pytest
 from nose.tools import assert_raises, assert_equal, assert_true
 
 from mne import io, pick_types, pick_channels, read_events, Epochs
 from mne.channels.interpolation import _make_interpolation_matrix
-from mne.utils import run_tests_if_main, slow_test
+from mne.utils import run_tests_if_main
 
 base_dir = op.join(op.dirname(__file__), '..', '..', 'io', 'tests', 'data')
 raw_fname = op.join(base_dir, 'test_raw.fif')
@@ -21,7 +22,7 @@ def _load_data():
     """Helper function to load data."""
     # It is more memory efficient to load data in a separate
     # function so it's loaded on-demand
-    raw = io.read_raw_fif(raw_fname, add_eeg_ref=False)
+    raw = io.read_raw_fif(raw_fname)
     events = read_events(event_name)
     picks_eeg = pick_types(raw.info, meg=False, eeg=True, exclude=[])
     # select every second channel for faster speed but compensate by using
@@ -41,7 +42,7 @@ def _load_data():
     return raw, epochs, epochs_eeg, epochs_meg
 
 
- at slow_test
+ at pytest.mark.slowtest
 def test_interpolation():
     """Test interpolation"""
     raw, epochs, epochs_eeg, epochs_meg = _load_data()
@@ -92,6 +93,18 @@ def test_interpolation():
         inst.info['bads'] = [inst.ch_names[1]]
         assert_raises(ValueError, inst.interpolate_bads)
 
+    # check that interpolation works with few channels
+    raw_few = raw.copy().crop(0, 0.1).load_data()
+    raw_few.pick_channels(raw_few.ch_names[:1] + raw_few.ch_names[3:4])
+    assert_equal(len(raw_few.ch_names), 2)
+    raw_few.del_proj()
+    raw_few.info['bads'] = [raw_few.ch_names[-1]]
+    orig_data = raw_few[1][0]
+    raw_few.interpolate_bads(reset_bads=False)
+    new_data = raw_few[1][0]
+    assert_true((new_data == 0).mean() < 0.5)
+    assert_true(np.corrcoef(new_data, orig_data)[0, 1] > 0.1)
+
     # check that interpolation works when non M/EEG channels are present
     # before MEG channels
     with warnings.catch_warnings(record=True):  # change of units
@@ -110,8 +123,7 @@ def test_interpolation():
     raw_meg = io.RawArray(data=epochs_meg._data[0], info=epochs_meg.info)
     raw_meg.info['bads'] = ['MEG 0141']
     data1 = raw_meg[pick, :][0][0]
-    # reset_bads=False here because epochs_meg appears to share the same info
-    # dict with raw and we want to test the epochs functionality too
+
     raw_meg.info.normalize_proj()
     data2 = raw_meg.interpolate_bads(reset_bads=False)[pick, :][0][0]
     assert_true(np.corrcoef(data1, data2)[0, 1] > thresh)
@@ -124,7 +136,7 @@ def test_interpolation():
     epochs_meg.interpolate_bads()
     data2 = epochs_meg.get_data()[:, pick, :].ravel()
     assert_true(np.corrcoef(data1, data2)[0, 1] > thresh)
-    assert_true(len(raw_meg.info['bads']) == 0)
+    assert_true(len(epochs_meg.info['bads']) == 0)
 
     # MEG -- evoked
     data1 = evoked.data[pick]
@@ -132,4 +144,5 @@ def test_interpolation():
     data2 = evoked.interpolate_bads().data[pick]
     assert_true(np.corrcoef(data1, data2)[0, 1] > thresh)
 
+
 run_tests_if_main()
diff --git a/mne/channels/tests/test_layout.py b/mne/channels/tests/test_layout.py
index 7789b8f..004fc88 100644
--- a/mne/channels/tests/test_layout.py
+++ b/mne/channels/tests/test_layout.py
@@ -268,6 +268,7 @@ def test_find_layout():
 
     # Test plotting
     lout.plot()
+    lout.plot(picks=np.arange(10))
     plt.close('all')
 
 
diff --git a/mne/channels/tests/test_montage.py b/mne/channels/tests/test_montage.py
index 19ebd2d..006c22c 100644
--- a/mne/channels/tests/test_montage.py
+++ b/mne/channels/tests/test_montage.py
@@ -2,30 +2,42 @@
 #
 # License: BSD (3-clause)
 
+import os
 import os.path as op
 import warnings
 
 from nose.tools import assert_equal, assert_true, assert_raises
 
 import numpy as np
+from scipy.io import savemat
+
 from numpy.testing import (assert_array_equal, assert_almost_equal,
                            assert_allclose, assert_array_almost_equal,
                            assert_array_less)
 from mne.tests.common import assert_dig_allclose
-from mne.channels.montage import read_montage, _set_montage, read_dig_montage
+from mne.channels.montage import (read_montage, _set_montage, read_dig_montage,
+                                  get_builtin_montages)
 from mne.utils import _TempDir, run_tests_if_main
-from mne import create_info, EvokedArray, read_evokeds
+from mne import create_info, EvokedArray, read_evokeds, __file__ as _mne_file
+from mne.bem import _fit_sphere
 from mne.coreg import fit_matched_points
 from mne.transforms import apply_trans, get_ras_to_neuromag_trans
 from mne.io.constants import FIFF
 from mne.io.meas_info import _read_dig_points
+from mne.viz._3d import _fiducial_coords
+
 from mne.io.kit import read_mrk
-from mne.io import read_raw_brainvision
+from mne.io import (read_raw_brainvision, read_raw_egi, read_raw_fif,
+                    read_fiducials)
 
 from mne.datasets import testing
 
 data_path = testing.data_path(download=False)
 fif_dig_montage_fname = op.join(data_path, 'montage', 'eeganes07.fif')
+egi_dig_montage_fname = op.join(data_path, 'montage', 'coordinates.xml')
+egi_raw_fname = op.join(data_path, 'montage', 'egi_dig_test.raw')
+egi_fif_fname = op.join(data_path, 'montage', 'egi_dig_raw.fif')
+locs_montage_fname = op.join(data_path, 'EEGLAB', 'test_chans.locs')
 evoked_fname = op.join(data_path, 'montage', 'level2_raw-ave.fif')
 
 io_dir = op.join(op.dirname(__file__), '..', '..', 'io')
@@ -34,50 +46,159 @@ elp = op.join(kit_dir, 'test_elp.txt')
 hsp = op.join(kit_dir, 'test_hsp.txt')
 hpi = op.join(kit_dir, 'test_mrk.sqd')
 bv_fname = op.join(io_dir, 'brainvision', 'tests', 'data', 'test.vhdr')
+fif_fname = op.join(io_dir, 'tests', 'data', 'test_raw.fif')
+ctf_fif_fname = op.join(io_dir, 'tests', 'data', 'test_ctf_comp_raw.fif')
+
+
+def test_fiducials():
+    """Test handling of fiducials."""
+    # Eventually the code used here should be unified with montage.py, but for
+    # now it uses code in odd places
+    for fname in (fif_fname, ctf_fif_fname):
+        fids, coord_frame = read_fiducials(fname)
+        points = _fiducial_coords(fids, coord_frame)
+        assert points.shape == (3, 3)
+        # Fids
+        assert_allclose(points[:, 2], 0., atol=1e-6)
+        assert_allclose(points[::2, 1], 0., atol=1e-6)
+        assert points[2, 0] > 0  # RPA
+        assert points[0, 0] < 0  # LPA
+        # Nasion
+        assert_allclose(points[1, 0], 0., atol=1e-6)
+        assert points[1, 1] > 0
+
+
+def test_documented():
+    """Test that montages are documented."""
+    docs = read_montage.__doc__
+    lines = [line[4:] for line in docs.splitlines()]
+    start = stop = None
+    for li, line in enumerate(lines):
+        if line.startswith('====') and li < len(lines) - 2 and \
+                lines[li + 1].startswith('Kind') and\
+                lines[li + 2].startswith('===='):
+            start = li + 3
+        elif start is not None and li > start and line.startswith('===='):
+            stop = li
+            break
+    assert_true(start is not None)
+    assert_true(stop is not None)
+    kinds = [line.split(' ')[0] for line in lines[start:stop]]
+    kinds = [kind for kind in kinds if kind != '']
+    montages = os.listdir(op.join(op.dirname(_mne_file), 'channels', 'data',
+                                  'montages'))
+    montages = sorted(op.splitext(m)[0] for m in montages)
+    assert_equal(len(set(montages)), len(montages))
+    assert_equal(len(set(kinds)), len(kinds), msg=sorted(kinds))
+    assert_equal(set(montages), set(kinds))
 
 
 def test_montage():
-    """Test making montages"""
+    """Test making montages."""
     tempdir = _TempDir()
-    # no pep8
-    input_str = [
-        'FidNz 0.00000 10.56381 -2.05108\nFidT9 -7.82694 0.45386 -3.76056\n'
-        'very_very_very_long_name 7.82694 0.45386 -3.76056',
-        '// MatLab   Sphere coordinates [degrees]         Cartesian coordinates\n'  # noqa
-        '// Label       Theta       Phi    Radius         X         Y         Z       off sphere surface\n'  # noqa
-        'E1      37.700     -14.000       1.000    0.7677    0.5934   -0.2419  -0.00000000000000011\n'  # noqa
-        'E2      44.600      -0.880       1.000    0.7119    0.7021   -0.0154   0.00000000000000000\n'  # noqa
-        'E3      51.700      11.000       1.000    0.6084    0.7704    0.1908   0.00000000000000000',  # noqa
-        '# ASA electrode file\nReferenceLabel  avg\nUnitPosition    mm\n'
-        'NumberPositions=    68\nPositions\n-86.0761 -19.9897 -47.9860\n'
-        '85.7939 -20.0093 -48.0310\n0.0083 86.8110 -39.9830\n'
-        'Labels\nLPA\nRPA\nNz\n',
-        '# ASA electrode file\nReferenceLabel  avg\nUnitPosition    m\n'
-        'NumberPositions=    68\nPositions\n-.0860761 -.0199897 -.0479860\n'
-        '.0857939 -.0200093 -.0480310\n.0000083 .00868110 -.0399830\n'
-        'Labels\nLPA\nRPA\nNz\n',
-        'Site  Theta  Phi\nFp1  -92    -72\nFp2   92     72\n'
-        'very_very_very_long_name   -60    -51\n',
-        '346\n'
-        'EEG\t      F3\t -62.027\t -50.053\t      85\n'
-        'EEG\t      Fz\t  45.608\t      90\t      85\n'
-        'EEG\t      F4\t   62.01\t  50.103\t      85\n',
-        'eeg Fp1 -95.0 -31.0 -3.0\neeg AF7 -81 -59 -3\neeg AF3 -87 -41 28\n'
-    ]
-    kinds = ['test.sfp', 'test.csd', 'test_mm.elc', 'test_m.elc', 'test.txt',
-             'test.elp', 'test.hpts']
-    for kind, text in zip(kinds, input_str):
-        fname = op.join(tempdir, kind)
+    inputs = dict(
+        sfp='FidNz 0       9.071585155     -2.359754454\n'
+            'FidT9 -6.711765       0.040402876     -3.251600355\n'
+            'very_very_very_long_name -5.831241498 -4.494821698  4.955347697\n'
+            'Cz 0       0       8.899186843',
+        csd='// MatLab   Sphere coordinates [degrees]         Cartesian coordinates\n'  # noqa: E501
+            '// Label       Theta       Phi    Radius         X         Y         Z       off sphere surface\n'  # noqa: E501
+            'E1      37.700     -14.000       1.000    0.7677    0.5934   -0.2419  -0.00000000000000011\n'  # noqa: E501
+            'E3      51.700      11.000       1.000    0.6084    0.7704    0.1908   0.00000000000000000\n'  # noqa: E501
+            'E31      90.000     -11.000       1.000    0.0000    0.9816   -0.1908   0.00000000000000000\n'  # noqa: E501
+            'E61     158.000     -17.200       1.000   -0.8857    0.3579   -0.2957  -0.00000000000000022',  # noqa: E501
+        mm_elc='# ASA electrode file\nReferenceLabel  avg\nUnitPosition    mm\n'  # noqa:E501
+               'NumberPositions=    68\n'
+               'Positions\n'
+               '-86.0761 -19.9897 -47.9860\n'
+               '85.7939 -20.0093 -48.0310\n'
+               '0.0083 86.8110 -39.9830\n'
+               '-86.0761 -24.9897 -67.9860\n'
+               'Labels\nLPA\nRPA\nNz\nDummy\n',
+        m_elc='# ASA electrode file\nReferenceLabel  avg\nUnitPosition    m\n'
+              'NumberPositions=    68\nPositions\n-.0860761 -.0199897 -.0479860\n'  # noqa:E501
+              '.0857939 -.0200093 -.0480310\n.0000083 .00868110 -.0399830\n'
+              '.08 -.02 -.04\n'
+              'Labels\nLPA\nRPA\nNz\nDummy\n',
+        txt='Site  Theta  Phi\n'
+            'Fp1  -92    -72\n'
+            'Fp2   92     72\n'
+            'very_very_very_long_name       -92     72\n'
+            'O2        92    -90\n',
+        elp='346\n'
+            'EEG\t      F3\t -62.027\t -50.053\t      85\n'
+            'EEG\t      Fz\t  45.608\t      90\t      85\n'
+            'EEG\t      F4\t   62.01\t  50.103\t      85\n'
+            'EEG\t      FCz\t   68.01\t  58.103\t      85\n',
+        hpts='eeg Fp1 -95.0 -3. -3.\n'
+             'eeg AF7 -1 -1 -3\n'
+             'eeg A3 -2 -2 2\n'
+             'eeg A 0 0 0',
+        bvef='<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n'
+             '<!-- Generated by EasyCap Configurator 19.05.2014 -->\n'
+             '<Electrodes defaults="false">\n'
+             '  <Electrode>\n'
+             '    <Name>Fp1</Name>\n'
+             '    <Theta>-90</Theta>\n'
+             '    <Phi>-72</Phi>\n'
+             '    <Radius>1</Radius>\n'
+             '    <Number>1</Number>\n'
+             '  </Electrode>\n'
+             '  <Electrode>\n'
+             '    <Name>Fz</Name>\n'
+             '    <Theta>45</Theta>\n'
+             '    <Phi>90</Phi>\n'
+             '    <Radius>1</Radius>\n'
+             '    <Number>2</Number>\n'
+             '  </Electrode>\n'
+             '  <Electrode>\n'
+             '    <Name>F3</Name>\n'
+             '    <Theta>-60</Theta>\n'
+             '    <Phi>-51</Phi>\n'
+             '    <Radius>1</Radius>\n'
+             '    <Number>3</Number>\n'
+             '  </Electrode>\n'
+             '  <Electrode>\n'
+             '    <Name>F7</Name>\n'
+             '    <Theta>-90</Theta>\n'
+             '    <Phi>-36</Phi>\n'
+             '    <Radius>1</Radius>\n'
+             '    <Number>4</Number>\n'
+             '  </Electrode>\n'
+             '</Electrodes>',
+    )
+    # Get actual positions and save them for checking
+    # csd comes from the string above, all others come from commit 2fa35d4
+    poss = dict(
+        sfp=[[0.0, 9.07159, -2.35975], [-6.71176, 0.0404, -3.2516],
+             [-5.83124, -4.49482, 4.95535], [0.0, 0.0, 8.89919]],
+        mm_elc=[[-0.08608, -0.01999, -0.04799], [0.08579, -0.02001, -0.04803],
+                [1e-05, 0.08681, -0.03998], [-0.08608, -0.02499, -0.06799]],
+        m_elc=[[-0.08608, -0.01999, -0.04799], [0.08579, -0.02001, -0.04803],
+               [1e-05, 0.00868, -0.03998], [0.08, -0.02, -0.04]],
+        txt=[[-26.25044, 80.79056, -2.96646], [26.25044, 80.79056, -2.96646],
+             [-26.25044, -80.79056, -2.96646], [0.0, -84.94822, -2.96646]],
+        elp=[[-48.20043, 57.55106, 39.86971], [0.0, 60.73848, 59.4629],
+             [48.1426, 57.58403, 39.89198], [41.64599, 66.91489, 31.8278]],
+        hpts=[[-95, -3, -3], [-1, -1., -3.], [-2, -2, 2.], [0, 0, 0]],
+        bvef=[[-26.266444, 80.839803, 5.204748e-15],
+              [3.680313e-15, 60.104076, 60.104076],
+              [-46.325632, 57.207392, 42.500000],
+              [-68.766444, 49.961746, 5.204748e-15]],
+    )
+    for key, text in inputs.items():
+        kind = key.split('_')[-1]
+        fname = op.join(tempdir, 'test.' + kind)
         with open(fname, 'w') as fid:
             fid.write(text)
         montage = read_montage(fname)
-        if ".sfp" in kind or ".txt" in kind:
+        if kind in ('sfp', 'txt'):
             assert_true('very_very_very_long_name' in montage.ch_names)
-        assert_equal(len(montage.ch_names), 3)
+        assert_equal(len(montage.ch_names), 4)
         assert_equal(len(montage.ch_names), len(montage.pos))
-        assert_equal(montage.pos.shape, (3, 3))
-        assert_equal(montage.kind, op.splitext(kind)[0])
-        if kind.endswith('csd'):
+        assert_equal(montage.pos.shape, (4, 3))
+        assert_equal(montage.kind, 'test')
+        if kind == 'csd':
             dtype = [('label', 'S4'), ('theta', 'f8'), ('phi', 'f8'),
                      ('radius', 'f8'), ('x', 'f8'), ('y', 'f8'), ('z', 'f8'),
                      ('off_sph', 'f8')]
@@ -85,9 +206,8 @@ def test_montage():
                 table = np.loadtxt(fname, skip_header=2, dtype=dtype)
             except TypeError:
                 table = np.loadtxt(fname, skiprows=2, dtype=dtype)
-            pos2 = np.c_[table['x'], table['y'], table['z']]
-            assert_array_almost_equal(pos2, montage.pos, 4)
-        if kind.endswith('elc'):
+            poss['csd'] = np.c_[table['x'], table['y'], table['z']]
+        if kind == 'elc':
             # Make sure points are reasonable distance from geometric centroid
             centroid = np.sum(montage.pos, axis=0) / montage.pos.shape[0]
             distance_from_centroid = np.apply_along_axis(
@@ -95,65 +215,139 @@ def test_montage():
                 montage.pos - centroid)
             assert_array_less(distance_from_centroid, 0.2)
             assert_array_less(0.01, distance_from_centroid)
+        assert_array_almost_equal(poss[key], montage.pos, 4, err_msg=key)
+
+    # Test reading in different letter case.
+    ch_names = ["F3", "FZ", "F4", "FC3", "FCz", "FC4", "C3", "CZ", "C4", "CP3",
+                "CPZ", "CP4", "P3", "PZ", "P4", "O1", "OZ", "O2"]
+    montage = read_montage('standard_1020', ch_names=ch_names)
+    assert_array_equal(ch_names, montage.ch_names)
 
     # test transform
-    input_str = """
+    input_strs = ["""
     eeg Fp1 -95.0 -31.0 -3.0
     eeg AF7 -81 -59 -3
     eeg AF3 -87 -41 28
     cardinal 2 -91 0 -42
     cardinal 1 0 -91 -42
     cardinal 3 0 91 -42
-    """
-    kind = 'test_fid.hpts'
-    fname = op.join(tempdir, kind)
-    with open(fname, 'w') as fid:
-        fid.write(input_str)
-    montage = read_montage(op.join(tempdir, 'test_fid.hpts'), transform=True)
-    # check coordinate transformation
-    pos = np.array([-95.0, -31.0, -3.0])
-    nasion = np.array([-91, 0, -42])
-    lpa = np.array([0, -91, -42])
-    rpa = np.array([0, 91, -42])
-    fids = np.vstack((nasion, lpa, rpa))
-    trans = get_ras_to_neuromag_trans(fids[0], fids[1], fids[2])
-    pos = apply_trans(trans, pos)
-    assert_array_equal(montage.pos[0], pos)
-    idx = montage.ch_names.index('2')
-    assert_array_equal(montage.pos[idx, [0, 2]], [0, 0])
-    idx = montage.ch_names.index('1')
-    assert_array_equal(montage.pos[idx, [1, 2]], [0, 0])
-    idx = montage.ch_names.index('3')
-    assert_array_equal(montage.pos[idx, [1, 2]], [0, 0])
-    pos = np.array([-95.0, -31.0, -3.0])
-    montage_fname = op.join(tempdir, 'test_fid.hpts')
-    montage = read_montage(montage_fname, unit='mm')
-    assert_array_equal(montage.pos[0], pos * 1e-3)
-
-    # test with last
-    info = create_info(montage.ch_names, 1e3, ['eeg'] * len(montage.ch_names))
-    _set_montage(info, montage)
-    pos2 = np.array([c['loc'][:3] for c in info['chs']])
-    assert_array_equal(pos2, montage.pos)
-    assert_equal(montage.ch_names, info['ch_names'])
-
-    info = create_info(
-        montage.ch_names, 1e3, ['eeg'] * len(montage.ch_names))
-
-    evoked = EvokedArray(
-        data=np.zeros((len(montage.ch_names), 1)), info=info, tmin=0)
-    evoked.set_montage(montage)
-    pos3 = np.array([c['loc'][:3] for c in evoked.info['chs']])
-    assert_array_equal(pos3, montage.pos)
-    assert_equal(montage.ch_names, evoked.info['ch_names'])
-
-    # Warning should be raised when some EEG are not specified in the montage
+    """, """
+    Fp1 -95.0 -31.0 -3.0
+    AF7 -81 -59 -3
+    AF3 -87 -41 28
+    FidNz -91 0 -42
+    FidT9 0 -91 -42
+    FidT10 0 91 -42
+    """]
+    # sfp files seem to have Nz, T9, and T10 as fiducials:
+    # https://github.com/mne-tools/mne-python/pull/4482#issuecomment-321980611
+
+    kinds = ['test_fid.hpts',  'test_fid.sfp']
+
+    for kind, input_str in zip(kinds, input_strs):
+        fname = op.join(tempdir, kind)
+        with open(fname, 'w') as fid:
+            fid.write(input_str)
+        montage = read_montage(op.join(tempdir, kind), transform=True)
+
+        # check coordinate transformation
+        pos = np.array([-95.0, -31.0, -3.0])
+        nasion = np.array([-91, 0, -42])
+        lpa = np.array([0, -91, -42])
+        rpa = np.array([0, 91, -42])
+        fids = np.vstack((nasion, lpa, rpa))
+        trans = get_ras_to_neuromag_trans(fids[0], fids[1], fids[2])
+        pos = apply_trans(trans, pos)
+        assert_array_equal(montage.pos[0], pos)
+        assert_array_equal(montage.nasion[[0, 2]], [0, 0])
+        assert_array_equal(montage.lpa[[1, 2]], [0, 0])
+        assert_array_equal(montage.rpa[[1, 2]], [0, 0])
+        pos = np.array([-95.0, -31.0, -3.0])
+        montage_fname = op.join(tempdir, kind)
+        montage = read_montage(montage_fname, unit='mm')
+        assert_array_equal(montage.pos[0], pos * 1e-3)
+
+        # test with last
+        info = create_info(montage.ch_names, 1e3,
+                           ['eeg'] * len(montage.ch_names))
+        _set_montage(info, montage)
+        pos2 = np.array([c['loc'][:3] for c in info['chs']])
+        assert_array_equal(pos2, montage.pos)
+        assert_equal(montage.ch_names, info['ch_names'])
+
+        info = create_info(
+            montage.ch_names, 1e3, ['eeg'] * len(montage.ch_names))
+
+        evoked = EvokedArray(
+            data=np.zeros((len(montage.ch_names), 1)), info=info, tmin=0)
+
+        # test return type as well as set montage
+        assert_true(isinstance(evoked.set_montage(montage), type(evoked)))
+
+        pos3 = np.array([c['loc'][:3] for c in evoked.info['chs']])
+        assert_array_equal(pos3, montage.pos)
+        assert_equal(montage.ch_names, evoked.info['ch_names'])
+
+        # Warning should be raised when some EEG are not specified in montage
+        with warnings.catch_warnings(record=True) as w:
+            info = create_info(montage.ch_names + ['foo', 'bar'], 1e3,
+                               ['eeg'] * (len(montage.ch_names) + 2))
+            _set_montage(info, montage)
+            assert_true(len(w) == 1)
+
+    # Channel names can be treated case insensitive
+    with warnings.catch_warnings(record=True) as w:
+        info = create_info(['FP1', 'af7', 'AF3'], 1e3, ['eeg'] * 3)
+        _set_montage(info, montage)
+        assert_true(len(w) == 0)
+
+    # Unless there is a collision in names
     with warnings.catch_warnings(record=True) as w:
-        info = create_info(montage.ch_names + ['foo', 'bar'], 1e3,
-                           ['eeg'] * (len(montage.ch_names) + 2))
+        info = create_info(['FP1', 'Fp1', 'AF3'], 1e3, ['eeg'] * 3)
+        assert_true(info['dig'] is None)
         _set_montage(info, montage)
+        assert_equal(len(info['dig']), 5)  # 2 EEG w/pos, 3 fiducials
+        assert_true(len(w) == 1)
+    with warnings.catch_warnings(record=True) as w:
+        montage.ch_names = ['FP1', 'Fp1', 'AF3']
+        info = create_info(['fp1', 'AF3'], 1e3, ['eeg', 'eeg'])
+        assert_true(info['dig'] is None)
+        _set_montage(info, montage, set_dig=False)
+        assert_true(info['dig'] is None)
         assert_true(len(w) == 1)
 
+    # test get_pos2d method
+    montage = read_montage("standard_1020")
+    c3 = montage.get_pos2d()[montage.ch_names.index("C3")]
+    c4 = montage.get_pos2d()[montage.ch_names.index("C4")]
+    fz = montage.get_pos2d()[montage.ch_names.index("Fz")]
+    oz = montage.get_pos2d()[montage.ch_names.index("Oz")]
+    f1 = montage.get_pos2d()[montage.ch_names.index("F1")]
+    assert_true(c3[0] < 0)  # left hemisphere
+    assert_true(c4[0] > 0)  # right hemisphere
+    assert_true(fz[1] > 0)  # frontal
+    assert_true(oz[1] < 0)  # occipital
+    assert_allclose(fz[0], 0, atol=1e-2)  # midline
+    assert_allclose(oz[0], 0, atol=1e-2)  # midline
+    assert_true(f1[0] < 0 and f1[1] > 0)  # left frontal
+
+    # test get_builtin_montages function
+    montages = get_builtin_montages()
+    assert_true(len(montages) > 0)  # MNE should always ship with montages
+    assert_true("standard_1020" in montages)  # 10/20 montage
+    assert_true("standard_1005" in montages)  # 10/05 montage
+
+
+ at testing.requires_testing_data
+def test_read_locs():
+    """Test reading EEGLAB locs."""
+    pos = read_montage(locs_montage_fname).pos
+    expected = [[0., 9.99779165e-01, -2.10157875e-02],
+                [3.08738197e-01, 7.27341573e-01, -6.12907052e-01],
+                [-5.67059636e-01, 6.77066318e-01, 4.69067752e-01],
+                [0., 7.14575231e-01, 6.99558616e-01]]
+    assert_allclose(pos[:4], expected, atol=1e-7)
+
 
 def test_read_dig_montage():
     """Test read_dig_montage"""
@@ -166,7 +360,7 @@ def test_read_dig_montage():
     assert_array_equal(montage.elp, elp_points)
     assert_array_equal(montage.hsp, hsp_points)
     assert_array_equal(montage.hpi, hpi_points)
-    assert_array_equal(montage.dev_head_t, np.identity(4))
+    assert_true(montage.dev_head_t is None)
     montage = read_dig_montage(hsp, hpi, elp, names,
                                transform=True, dev_head_t=True)
     # check coordinate transformation
@@ -188,59 +382,88 @@ def test_read_dig_montage():
                           names)
     assert_allclose(m3.hsp, montage.hsp)
 
-    # test unit parameter
-    montage_cm = read_dig_montage(hsp, hpi, elp, names, unit='cm')
+    # test unit parameter and .mat support
+    tempdir = _TempDir()
+    mat_hsp = op.join(tempdir, 'test.mat')
+    savemat(mat_hsp, dict(Points=(1000 * hsp_points).T))
+    montage_cm = read_dig_montage(mat_hsp, hpi, elp, names, unit='cm')
     assert_allclose(montage_cm.hsp, montage.hsp * 10.)
     assert_allclose(montage_cm.elp, montage.elp * 10.)
     assert_array_equal(montage_cm.hpi, montage.hpi)
     assert_raises(ValueError, read_dig_montage, hsp, hpi, elp, names,
                   unit='km')
+    # extra columns
+    extra_hsp = op.join(tempdir, 'test.txt')
+    with open(hsp, 'rb') as fin:
+        with open(extra_hsp, 'wb') as fout:
+            for line in fin:
+                if line.startswith(b'%'):
+                    fout.write(line)
+                else:
+                    # extra column
+                    fout.write(line.rstrip() + b' 0.0 0.0 0.0\n')
+    with warnings.catch_warnings(record=True) as w:
+        montage_extra = read_dig_montage(extra_hsp, hpi, elp, names)
+    assert_true(len(w) == 1 and all('columns' in str(ww.message) for ww in w))
+    assert_allclose(montage_extra.hsp, montage.hsp)
+    assert_allclose(montage_extra.elp, montage.elp)
 
 
 def test_set_dig_montage():
-    """Test applying DigMontage to inst
-    """
+    """Test applying DigMontage to inst."""
     # Extensive testing of applying `dig` to info is done in test_meas_info
     # with `test_make_dig_points`.
     names = ['nasion', 'lpa', 'rpa', '1', '2', '3', '4', '5']
     hsp_points = _read_dig_points(hsp)
     elp_points = _read_dig_points(elp)
-    hpi_points = read_mrk(hpi)
-    p0, p1, p2 = elp_points[:3]
-    nm_trans = get_ras_to_neuromag_trans(p0, p1, p2)
+    nasion, lpa, rpa = elp_points[:3]
+    nm_trans = get_ras_to_neuromag_trans(nasion, lpa, rpa)
     elp_points = apply_trans(nm_trans, elp_points)
-    nasion_point, lpa_point, rpa_point = elp_points[:3]
+    nasion, lpa, rpa = elp_points[:3]
     hsp_points = apply_trans(nm_trans, hsp_points)
 
-    montage = read_dig_montage(hsp, hpi, elp, names, transform=True)
-    info = create_info(['Test Ch'], 1e3, ['eeg'])
-    _set_montage(info, montage)
-    hs = np.array([p['r'] for i, p in enumerate(info['dig'])
-                   if p['kind'] == FIFF.FIFFV_POINT_EXTRA])
-    nasion_dig = np.array([p['r'] for p in info['dig']
-                           if all([p['ident'] == FIFF.FIFFV_POINT_NASION,
-                                   p['kind'] == FIFF.FIFFV_POINT_CARDINAL])])
-    lpa_dig = np.array([p['r'] for p in info['dig']
-                        if all([p['ident'] == FIFF.FIFFV_POINT_LPA,
-                                p['kind'] == FIFF.FIFFV_POINT_CARDINAL])])
-    rpa_dig = np.array([p['r'] for p in info['dig']
-                        if all([p['ident'] == FIFF.FIFFV_POINT_RPA,
-                                p['kind'] == FIFF.FIFFV_POINT_CARDINAL])])
-    hpi_dig = np.array([p['r'] for p in info['dig']
-                        if p['kind'] == FIFF.FIFFV_POINT_HPI])
-    assert_array_equal(hs, hsp_points)
-    assert_array_equal(nasion_dig.ravel(), nasion_point)
-    assert_array_equal(lpa_dig.ravel(), lpa_point)
-    assert_array_equal(rpa_dig.ravel(), rpa_point)
-    assert_array_equal(hpi_dig, hpi_points)
-    assert_array_equal(montage.dev_head_t, info['dev_head_t']['trans'])
+    montage = read_dig_montage(hsp, hpi, elp, names, transform=True,
+                               dev_head_t=True)
+    temp_dir = _TempDir()
+    fname_temp = op.join(temp_dir, 'test.fif')
+    montage.save(fname_temp)
+    montage_read = read_dig_montage(fif=fname_temp)
+    for use_mon in (montage, montage_read):
+        info = create_info(['Test Ch'], 1e3, ['eeg'])
+        with warnings.catch_warnings(record=True) as w:  # test ch pos not set
+            _set_montage(info, use_mon)
+        assert_true(all('not set' in str(ww.message) for ww in w))
+        hs = np.array([p['r'] for i, p in enumerate(info['dig'])
+                       if p['kind'] == FIFF.FIFFV_POINT_EXTRA])
+        nasion_dig = np.array([p['r'] for p in info['dig']
+                               if all([p['ident'] == FIFF.FIFFV_POINT_NASION,
+                                       p['kind'] == FIFF.FIFFV_POINT_CARDINAL])
+                               ])
+        lpa_dig = np.array([p['r'] for p in info['dig']
+                            if all([p['ident'] == FIFF.FIFFV_POINT_LPA,
+                                    p['kind'] == FIFF.FIFFV_POINT_CARDINAL])])
+        rpa_dig = np.array([p['r'] for p in info['dig']
+                            if all([p['ident'] == FIFF.FIFFV_POINT_RPA,
+                                    p['kind'] == FIFF.FIFFV_POINT_CARDINAL])])
+        hpi_dig = np.array([p['r'] for p in info['dig']
+                            if p['kind'] == FIFF.FIFFV_POINT_HPI])
+        assert_allclose(hs, hsp_points, atol=1e-7)
+        assert_allclose(nasion_dig.ravel(), nasion, atol=1e-7)
+        assert_allclose(lpa_dig.ravel(), lpa, atol=1e-7)
+        assert_allclose(rpa_dig.ravel(), rpa, atol=1e-7)
+        assert_allclose(hpi_dig, elp_points[3:], atol=1e-7)
 
 
 @testing.requires_testing_data
 def test_fif_dig_montage():
-    """Test FIF dig montage support"""
+    """Test FIF dig montage support."""
     dig_montage = read_dig_montage(fif=fif_dig_montage_fname)
 
+    # test round-trip IO
+    temp_dir = _TempDir()
+    fname_temp = op.join(temp_dir, 'test.fif')
+    _check_roundtrip(dig_montage, fname_temp)
+
     # Make a BrainVision file like the one the user would have had
     with warnings.catch_warnings(record=True) as w:
         raw_bv = read_raw_brainvision(bv_fname, preload=True)
@@ -256,19 +479,92 @@ def test_fif_dig_montage():
     raw_bv.drop_channels(['STI 014'])
     raw_bv.add_channels([raw_bv_2])
 
-    # Set the montage
-    raw_bv.set_montage(dig_montage)
+    for ii in range(2):
+        if ii == 1:
+            dig_montage.transform_to_head()  # should have no meaningful effect
+
+        # Set the montage
+        raw_bv.set_montage(dig_montage)
+
+        # Check the result
+        evoked = read_evokeds(evoked_fname)[0]
+
+        assert_equal(len(raw_bv.ch_names), len(evoked.ch_names))
+        for ch_py, ch_c in zip(raw_bv.info['chs'], evoked.info['chs']):
+            assert_equal(ch_py['ch_name'],
+                         ch_c['ch_name'].replace('EEG ', 'EEG'))
+            # C actually says it's unknown, but it's not (?):
+            # assert_equal(ch_py['coord_frame'], ch_c['coord_frame'])
+            assert_equal(ch_py['coord_frame'], FIFF.FIFFV_COORD_HEAD)
+            assert_allclose(ch_py['loc'], ch_c['loc'], atol=1e-7)
+        assert_dig_allclose(raw_bv.info, evoked.info)
+
+    # Roundtrip of non-FIF start
+    names = ['nasion', 'lpa', 'rpa', '1', '2', '3', '4', '5']
+    montage = read_dig_montage(hsp, hpi, elp, names, transform=False)
+    assert_raises(RuntimeError, montage.save, fname_temp)  # must be head coord
+    montage = read_dig_montage(hsp, hpi, elp, names)
+    _check_roundtrip(montage, fname_temp)
+
+
+ at testing.requires_testing_data
+def test_egi_dig_montage():
+    """Test EGI MFF XML dig montage support."""
+    dig_montage = read_dig_montage(egi=egi_dig_montage_fname, unit='m')
+
+    # # test round-trip IO
+    temp_dir = _TempDir()
+    fname_temp = op.join(temp_dir, 'egi_test.fif')
+    _check_roundtrip(dig_montage, fname_temp)
+
+    # Test coordinate transform
+    dig_montage.transform_to_head()
+    # nasion
+    assert_almost_equal(dig_montage.nasion[0], 0)
+    assert_almost_equal(dig_montage.nasion[2], 0)
+    # lpa and rpa
+    assert_allclose(dig_montage.lpa[1:], 0, atol=1e-16)
+    assert_allclose(dig_montage.rpa[1:], 0, atol=1e-16)
+
+    # Test accuracy and embedding within raw object
+    raw_egi = read_raw_egi(egi_raw_fname, channel_naming='EEG %03d')
+    raw_egi.set_montage(dig_montage)
+    test_raw_egi = read_raw_fif(egi_fif_fname)
+
+    assert_equal(len(raw_egi.ch_names), len(test_raw_egi.ch_names))
+    for ch_raw, ch_test_raw in zip(raw_egi.info['chs'],
+                                   test_raw_egi.info['chs']):
+        assert_equal(ch_raw['ch_name'], ch_test_raw['ch_name'])
+        assert_equal(ch_raw['coord_frame'], FIFF.FIFFV_COORD_HEAD)
+        assert_allclose(ch_raw['loc'], ch_test_raw['loc'], atol=1e-7)
+    assert_dig_allclose(raw_egi.info, test_raw_egi.info)
+
+
+def test_set_montage():
+    """Test setting a montage."""
+    raw = read_raw_fif(fif_fname)
+    mon = read_montage('mgh60')
+    orig_pos = np.array([ch['loc'][:3] for ch in raw.info['chs']
+                         if ch['ch_name'].startswith('EEG')])
+    raw.set_montage(mon)
+    new_pos = np.array([ch['loc'][:3] for ch in raw.info['chs']
+                        if ch['ch_name'].startswith('EEG')])
+    assert_true((orig_pos != new_pos).all())
+    r0 = _fit_sphere(new_pos)[1]
+    assert_allclose(r0, [0., -0.016, 0.], atol=1e-3)
+
 
-    # Check the result
-    evoked = read_evokeds(evoked_fname)[0]
+def _check_roundtrip(montage, fname):
+    """Check roundtrip writing."""
+    assert_equal(montage.coord_frame, 'head')
+    montage.save(fname)
+    montage_read = read_dig_montage(fif=fname)
+    assert_equal(str(montage), str(montage_read))
+    for kind in ('elp', 'hsp', 'nasion', 'lpa', 'rpa'):
+        if getattr(montage, kind) is not None:
+            assert_allclose(getattr(montage, kind),
+                            getattr(montage_read, kind), err_msg=kind)
+    assert_equal(montage_read.coord_frame, 'head')
 
-    assert_equal(len(raw_bv.ch_names), len(evoked.ch_names))
-    for ch_py, ch_c in zip(raw_bv.info['chs'], evoked.info['chs']):
-        assert_equal(ch_py['ch_name'], ch_c['ch_name'].replace('EEG ', 'EEG'))
-        # C actually says it's unknown, but it's not (?):
-        # assert_equal(ch_py['coord_frame'], ch_c['coord_frame'])
-        assert_equal(ch_py['coord_frame'], FIFF.FIFFV_COORD_HEAD)
-        assert_allclose(ch_py['loc'], ch_c['loc'])
-    assert_dig_allclose(raw_bv.info, evoked.info)
 
 run_tests_if_main()
diff --git a/mne/chpi.py b/mne/chpi.py
index bc477e1..2b38b38 100644
--- a/mne/chpi.py
+++ b/mne/chpi.py
@@ -1,3 +1,34 @@
+# -*- coding: utf-8 -*-
+"""Functions for fitting head positions with (c)HPI coils."""
+
+# To fit head positions (continuously), the procedure using
+# ``_calculate_chpi_positions`` is:
+#
+#     1. Get HPI coil locations (as digitized in info['dig'] in head coords
+#        using ``_get_hpi_initial_fit``.
+#     2. Get HPI frequencies,  HPI status channel, HPI status bits,
+#        and digitization order using ``_setup_hpi_struct``.
+#     3. Map HPI coil locations into device coords and compute coil to coil
+#        distances.
+#     4. Window data using ``t_window`` (half before and half after ``t``) and
+#        ``t_step_min``.
+#        (Here Elekta high-passes the data, but we omit this step.)
+#     5. Use a linear model (DC + linear slope + sin + cos terms set up
+#        in ``_setup_hpi_struct``) to fit sinusoidal amplitudes to MEG
+#        channels. Use SVD to determine the phase/amplitude of the sinusoids.
+#        This step is accomplished using ``_fit_cHPI_amplitudes``
+#     6. If the amplitudes are 98% correlated with last position
+#        (and Δt < t_step_max), skip fitting.
+#     7. Fit magnetic dipoles using the amplitudes for each coil frequency
+#        (calling ``_fit_magnetic_dipole``).
+#     8. If ``use_distances is True`` choose good coils based on pairwise
+#        distances, taking into account the tolerance ``dist_limit``.
+#     9. Fit dev_head_t quaternion (using ``_fit_chpi_quat``).
+#     10. Accept or reject fit based on GOF threshold ``gof_limit``.
+#
+# The function ``filter_chpi`` uses the same linear model to filter cHPI
+# and (optionally) line frequencies from the data.
+
 # Authors: Eric Larson <larson.eric.d at gmail.com>
 #
 # License: BSD (3-clause)
@@ -5,17 +36,17 @@
 from functools import partial
 
 import numpy as np
-from scipy import linalg, fftpack
+from scipy import linalg
+import itertools
 
 from .io.pick import pick_types, pick_channels
 from .io.constants import FIFF
 from .forward import (_magnetic_dipole_field_vec, _create_meg_coils,
                       _concatenate_coils, _read_coil_defs)
-from .cov import make_ad_hoc_cov, _get_whitener_data
+from .cov import make_ad_hoc_cov, compute_whitener
 from .transforms import (apply_trans, invert_transform, _angle_between_quats,
                          quat_to_rot, rot_to_quat)
-from .utils import (verbose, logger, check_version, use_log_level,
-                    _check_fname, warn)
+from .utils import verbose, logger, use_log_level, _check_fname, warn
 
 # Eventually we should add:
 #   hpicons
@@ -27,7 +58,7 @@ from .utils import (verbose, logger, check_version, use_log_level,
 # Reading from text or FIF file
 
 def read_head_pos(fname):
-    """Read MaxFilter-formatted head position parameters
+    """Read MaxFilter-formatted head position parameters.
 
     Parameters
     ----------
@@ -49,7 +80,7 @@ def read_head_pos(fname):
     -----
     .. versionadded:: 0.12
     """
-    _check_fname(fname, must_exist=True, overwrite=True)
+    _check_fname(fname, must_exist=True, overwrite='read')
     data = np.loadtxt(fname, skiprows=1)  # first line is header, skip it
     data.shape = (-1, 10)  # ensure it's the right size even if empty
     if np.isnan(data).any():  # make sure we didn't do something dumb
@@ -59,7 +90,7 @@ def read_head_pos(fname):
 
 
 def write_head_pos(fname, pos):
-    """Write MaxFilter-formatted head position parameters
+    """Write MaxFilter-formatted head position parameters.
 
     Parameters
     ----------
@@ -91,7 +122,7 @@ def write_head_pos(fname, pos):
 
 
 def head_pos_to_trans_rot_t(quats):
-    """Convert Maxfilter-formatted head position quaternions
+    """Convert Maxfilter-formatted head position quaternions.
 
     Parameters
     ----------
@@ -109,8 +140,8 @@ def head_pos_to_trans_rot_t(quats):
 
     See Also
     --------
-    read_pos
-    write_pos
+    read_head_pos
+    write_head_pos
     """
     t = quats[..., 0].copy()
     rotation = quat_to_rot(quats[..., 1:4])
@@ -118,16 +149,49 @@ def head_pos_to_trans_rot_t(quats):
     return translation, rotation, t
 
 
-# ############################################################################
-# Estimate positions from data
-
 @verbose
-def _get_hpi_info(info, adjust=False, verbose=None):
-    """Helper to get HPI information from raw"""
+def _get_hpi_info(info, verbose=None):
+    """Get HPI information from raw."""
     if len(info['hpi_meas']) == 0 or \
             ('coil_freq' not in info['hpi_meas'][0]['hpi_coils'][0]):
         raise RuntimeError('Appropriate cHPI information not found in'
                            'raw.info["hpi_meas"], cannot process cHPI')
+    hpi_coils = sorted(info['hpi_meas'][-1]['hpi_coils'],
+                       key=lambda x: x['number'])  # ascending (info) order
+
+    # how cHPI active is indicated in the FIF file
+    hpi_sub = info['hpi_subsystem']
+    if 'event_channel' in hpi_sub:
+        hpi_pick = pick_channels(info['ch_names'],
+                                 [hpi_sub['event_channel']])
+        hpi_pick = hpi_pick[0] if len(hpi_pick) > 0 else None
+    else:
+        hpi_pick = None  # there is no pick!
+
+    # grab codes indicating a coil is active
+    hpi_on = [coil['event_bits'][0] for coil in hpi_sub['hpi_coils']]
+    # not all HPI coils will actually be used
+    hpi_on = np.array([hpi_on[hc['number'] - 1] for hc in hpi_coils])
+
+    # get frequencies
+    hpi_freqs = np.array([float(x['coil_freq']) for x in hpi_coils])
+    logger.info('Using %s HPI coils: %s Hz'
+                % (len(hpi_freqs), ' '.join(str(int(s)) for s in hpi_freqs)))
+
+    # mask for coils that may be active
+    hpi_mask = np.array([event_bit != 0 for event_bit in hpi_on])
+    hpi_on = hpi_on[hpi_mask]
+    hpi_freqs = hpi_freqs[hpi_mask]
+
+    return hpi_freqs, hpi_pick, hpi_on
+
+
+ at verbose
+def _get_hpi_initial_fit(info, adjust=False, verbose=None):
+    """Get HPI fit locations from raw."""
+    if info['hpi_results'] is None:
+        raise RuntimeError('no initial cHPI head localization performed')
+
     hpi_result = info['hpi_results'][-1]
     hpi_coils = sorted(info['hpi_meas'][-1]['hpi_coils'],
                        key=lambda x: x['number'])  # ascending (info) order
@@ -188,27 +252,11 @@ def _get_hpi_info(info, adjust=False, verbose=None):
     logger.debug('HP fitting limits: err = %.1f mm, gval = %.3f.'
                  % (1000 * hpi_result['dist_limit'], hpi_result['good_limit']))
 
-    # how cHPI active is indicated in the FIF file
-    hpi_sub = info['hpi_subsystem']
-    if 'event_channel' in hpi_sub:
-        hpi_pick = pick_channels(info['ch_names'],
-                                 [hpi_sub['event_channel']])[0]
-    else:
-        hpi_pick = None  # there is no pick!
-    hpi_on = [coil['event_bits'][0] for coil in hpi_sub['hpi_coils']]
-    # not all HPI coils will actually be used
-    hpi_on = np.array([hpi_on[hc['number'] - 1] for hc in hpi_coils])
-    assert len(hpi_coils) == len(hpi_on)
-
-    # get frequencies
-    hpi_freqs = np.array([float(x['coil_freq']) for x in hpi_coils])
-    logger.info('Using %s HPI coils: %s Hz'
-                % (len(hpi_freqs), ' '.join(str(int(s)) for s in hpi_freqs)))
-    return hpi_freqs, hpi_rrs, hpi_pick, hpi_on, pos_order
+    return hpi_rrs
 
 
 def _magnetic_dipole_objective(x, B, B2, coils, scale, method):
-    """Project data onto right eigenvectors of whitened forward"""
+    """Project data onto right eigenvectors of whitened forward."""
     if method == 'forward':
         fwd = _magnetic_dipole_field_vec(x[np.newaxis, :], coils)
     else:
@@ -224,51 +272,85 @@ def _magnetic_dipole_objective(x, B, B2, coils, scale, method):
 
 
 def _fit_magnetic_dipole(B_orig, x0, coils, scale, method):
-    """Fit a single bit of data (x0 = pos)"""
+    """Fit a single bit of data (x0 = pos)."""
     from scipy.optimize import fmin_cobyla
     B = np.dot(scale, B_orig)
     B2 = np.dot(B, B)
     objective = partial(_magnetic_dipole_objective, B=B, B2=B2,
                         coils=coils, scale=scale, method=method)
-    x = fmin_cobyla(objective, x0, (), rhobeg=1e-2, rhoend=1e-5, disp=False)
+    x = fmin_cobyla(objective, x0, (), rhobeg=1e-4, rhoend=1e-5, disp=False)
     return x, 1. - objective(x) / B2
 
 
 def _chpi_objective(x, coil_dev_rrs, coil_head_rrs):
-    """Helper objective function"""
+    """Compute objective function."""
     d = np.dot(coil_dev_rrs, quat_to_rot(x[:3]).T)
-    d += x[3:]
+    d += x[3:] / 10.  # in decimeters to get quats and head units close
     d -= coil_head_rrs
     d *= d
     return d.sum()
 
 
 def _unit_quat_constraint(x):
-    """Constrain our 3 quaternion rot params (ignoring w) to have norm <= 1"""
+    """Constrain our 3 quaternion rot params (ignoring w) to have norm <= 1."""
     return 1 - (x * x).sum()
 
 
-def _fit_chpi_pos(coil_dev_rrs, coil_head_rrs, x0):
-    """Fit rotation and translation parameters for cHPI coils"""
+def _fit_chpi_quat(coil_dev_rrs, coil_head_rrs, x0):
+    """Fit rotation and translation (quaternion) parameters for cHPI coils."""
     from scipy.optimize import fmin_cobyla
     denom = np.sum((coil_head_rrs - np.mean(coil_head_rrs, axis=0)) ** 2)
     objective = partial(_chpi_objective, coil_dev_rrs=coil_dev_rrs,
                         coil_head_rrs=coil_head_rrs)
+    x0 = x0.copy()
+    x0[3:] *= 10.  # decimeters to get quats and head units close
     x = fmin_cobyla(objective, x0, _unit_quat_constraint,
-                    rhobeg=1e-2, rhoend=1e-6, disp=False)
-    return x, 1. - objective(x) / denom
+                    rhobeg=1e-3, rhoend=1e-5, disp=False)
+    result = objective(x)
+    x[3:] /= 10.
+    return x, 1. - result / denom
+
+
+def _fit_coil_order_dev_head_trans(dev_pnts, head_pnts):
+    """Compute Device to Head transform allowing for permutiatons of points."""
+    id_quat = np.concatenate([rot_to_quat(np.eye(3)), [0.0, 0.0, 0.0]])
+    best_order = None
+    best_g = -999
+    best_quat = id_quat
+    for this_order in itertools.permutations(np.arange(len(head_pnts))):
+        head_pnts_tmp = head_pnts[np.array(this_order)]
+        this_quat, g = _fit_chpi_quat(dev_pnts, head_pnts_tmp, id_quat)
+        if g > best_g:
+            best_g = g
+            best_order = np.array(this_order)
+            best_quat = this_quat
+
+    # Convert Quaterion to transform
+    dev_head_t = np.concatenate(
+        (quat_to_rot(best_quat[:3]),
+         best_quat[3:][:, np.newaxis]), axis=1)
+    dev_head_t = np.concatenate((dev_head_t, [[0, 0, 0, 1.]]))
+    return dev_head_t, best_order
 
 
 @verbose
-def _setup_chpi_fits(info, t_window, t_step_min, method='forward',
-                     exclude='bads', add_hpi_stim_pick=True,
-                     remove_aliased=False, verbose=None):
-    """Helper to set up cHPI fits"""
-    from scipy.spatial.distance import cdist
+def _setup_hpi_struct(info, model_n_window,
+                      method='forward',
+                      exclude='bads',
+                      remove_aliased=False, verbose=None):
+    """Generate HPI structure for HPI localization.
+
+    Returns
+    -------
+    hpi : dict
+        Dictionary of parameters representing the cHPI system and needed to
+        perform head localization.
+    """
     from .preprocessing.maxwell import _prep_mf_coils
-    if not (check_version('numpy', '1.7') and check_version('scipy', '0.11')):
-        raise RuntimeError('numpy>=1.7 and scipy>=0.11 required')
-    hpi_freqs, coil_head_rrs, hpi_pick, hpi_ons = _get_hpi_info(info)[:4]
+
+    # grab basic info.
+    hpi_freqs, hpi_pick, hpi_ons = _get_hpi_info(info)
+    # worry about resampled/filtered data.
     # What to do e.g. if Raw has been resampled and some of our
     # HPI freqs would now be aliased
     highest = info.get('lowpass')
@@ -276,26 +358,21 @@ def _setup_chpi_fits(info, t_window, t_step_min, method='forward',
     keepers = np.array([h <= highest for h in hpi_freqs], bool)
     if remove_aliased:
         hpi_freqs = hpi_freqs[keepers]
-        coil_head_rrs = coil_head_rrs[keepers]
         hpi_ons = hpi_ons[keepers]
     elif not keepers.all():
         raise RuntimeError('Found HPI frequencies %s above the lowpass '
                            '(or Nyquist) frequency %0.1f'
                            % (hpi_freqs[~keepers].tolist(), highest))
-    line_freqs = np.arange(info['line_freq'], info['sfreq'] / 3.,
-                           info['line_freq'])
+    if info['line_freq'] is not None:
+        line_freqs = np.arange(info['line_freq'], info['sfreq'] / 3.,
+                               info['line_freq'])
+    else:
+        line_freqs = np.zeros([0])
     logger.info('Line interference frequencies: %s Hz'
                 % ' '.join(['%d' % l for l in line_freqs]))
-    # initial transforms
-    dev_head_t = info['dev_head_t']['trans']
-    head_dev_t = invert_transform(info['dev_head_t'])['trans']
-    # determine timing
-    n_window = int(round(t_window * info['sfreq']))
-    logger.debug('Coordinate transformation:')
-    for d in (dev_head_t[0, :3], dev_head_t[1, :3], dev_head_t[2, :3],
-              dev_head_t[:3, 3] * 1000.):
-        logger.debug('{0:8.4f} {1:8.4f} {2:8.4f}'.format(*d))
-    slope = np.arange(n_window).astype(np.float64)[:, np.newaxis]
+
+    # build model to extract sinusoidal amplitudes.
+    slope = np.arange(model_n_window).astype(np.float64)[:, np.newaxis]
     slope -= np.mean(slope)
     rads = slope / info['sfreq']
     rads *= 2 * np.pi
@@ -306,55 +383,177 @@ def _setup_chpi_fits(info, t_window, t_step_min, method='forward',
     model += [slope, np.ones(slope.shape)]
     model = np.concatenate(model, axis=1)
     inv_model = linalg.pinv(model)
-    # Set up highpass at half lowest cHPI freq
-    hp_n = 2 ** (int(np.ceil(np.log2(n_window))) + 1)
-    freqs = fftpack.rfftfreq(hp_n, 1. / info['sfreq'])
-    hp_ind = np.where(freqs >= hpi_freqs.min())[0][0] - 2
-    hp_window = np.concatenate(
-        [[0], np.repeat(np.hanning(hp_ind - 1)[:(hp_ind - 1) // 2],
-                        2)])[np.newaxis]
 
     # Set up magnetic dipole fits
-    picks_meg = pick_types(info, meg=True, eeg=False, exclude=exclude)
-    if add_hpi_stim_pick:
-        if hpi_pick is None:
-            raise RuntimeError('Could not find HPI status channel')
-        picks = np.concatenate([picks_meg, [hpi_pick]])
-    else:
-        picks = picks_meg
-    megchs = [ch for ci, ch in enumerate(info['chs']) if ci in picks_meg]
+    meg_picks = pick_types(info, meg=True, eeg=False, exclude=exclude)
+    if len(exclude) > 0:
+        if exclude == 'bads':
+            msg = info['bads']
+        else:
+            msg = exclude
+        logger.debug('Static bad channels (%d): %s'
+                     % (len(msg), u' '.join(msg)))
+
+    megchs = [ch for ci, ch in enumerate(info['chs']) if ci in meg_picks]
     templates = _read_coil_defs(elekta_defs=True, verbose=False)
     coils = _create_meg_coils(megchs, 'accurate', coilset=templates)
     if method == 'forward':
         coils = _concatenate_coils(coils)
     else:  # == 'multipole'
         coils = _prep_mf_coils(info)
-    scale = make_ad_hoc_cov(info, verbose=False)
-    scale = _get_whitener_data(info, scale, picks_meg, verbose=False)
-    orig_dev_head_quat = np.concatenate([rot_to_quat(dev_head_t[:3, :3]),
-                                         dev_head_t[:3, 3]])
-    dists = cdist(coil_head_rrs, coil_head_rrs)
-    hpi = dict(dists=dists, scale=scale, picks=picks, model=model,
-               inv_model=inv_model, coil_head_rrs=coil_head_rrs,
-               coils=coils, on=hpi_ons, n_window=n_window, method=method,
-               freqs=hpi_freqs, line_freqs=line_freqs,
-               hp_ind=hp_ind, hp_n=hp_n, hp_window=hp_window)
-    last = dict(quat=orig_dev_head_quat, coil_head_rrs=coil_head_rrs,
-                coil_dev_rrs=apply_trans(head_dev_t, coil_head_rrs),
-                sin_fit=None, fit_time=-t_step_min)
-    return hpi, last
+    diag_cov = make_ad_hoc_cov(info, verbose=False)
+
+    diag_whitener, _ = compute_whitener(diag_cov, info, picks=meg_picks,
+                                        verbose=False)
+
+    hpi = dict(meg_picks=meg_picks, hpi_pick=hpi_pick,
+               model=model, inv_model=inv_model,
+               on=hpi_ons, n_window=model_n_window, method=method,
+               freqs=hpi_freqs, line_freqs=line_freqs, n_freqs=len(hpi_freqs),
+               scale=diag_whitener, coils=coils
+               )
+
+    return hpi
 
 
 def _time_prefix(fit_time):
-    """Helper to format log messages"""
+    """Format log messages."""
     return ('    t=%0.3f:' % fit_time).ljust(17)
 
 
 @verbose
+def _fit_cHPI_amplitudes(raw, time_sl, hpi, fit_time, verbose=None):
+    """Fit amplitudes for each channel from each of the N cHPI sinusoids.
+
+    Returns
+    -------
+    sin_fit : ndarray, shape (n_freqs, n_channels)) or None :
+        The sin amplitudes matching each cHPI frequency
+            or None if this time window should be skipped
+    """
+    with use_log_level(False):
+        # loads good channels
+        meg_chpi_data = raw[hpi['meg_picks'], time_sl][0]
+
+    mchpi_data = np.tile(np.mean(meg_chpi_data, axis=1, keepdims=True),
+                         (1, meg_chpi_data.shape[1]))
+    mchpi_data = mchpi_data.reshape(meg_chpi_data.shape)
+    this_data = meg_chpi_data - mchpi_data
+
+    # which HPI coils to use
+    # other then erroring I don't see this getting used elsewhere?
+    if hpi['hpi_pick'] is not None:
+        with use_log_level(False):
+            # loads hpi_stim channel
+            chpi_data = raw[hpi['hpi_pick'], time_sl][0]
+
+        ons = (np.round(chpi_data).astype(np.int) &
+               hpi['on'][:, np.newaxis]).astype(bool)
+        n_on = np.sum(ons, axis=0)
+        if not (n_on >= 3).all():
+            logger.info(_time_prefix(fit_time) + '%s < 3 HPI coils turned on, '
+                        'skipping fit' % (n_on.min(),))
+            return None
+        # #TODO REMOVE # ons = ons.all(axis=1)  # which HPI coils to use
+
+    n_freqs = hpi['n_freqs']
+    this_len = time_sl.stop - time_sl.start
+    if this_len == hpi['n_window']:
+        model, inv_model = hpi['model'], hpi['inv_model']
+    else:  # first or last window
+        model = hpi['model'][:this_len]
+        inv_model = linalg.pinv(model)
+    X = np.dot(inv_model, this_data.T)
+    X_sin, X_cos = X[:n_freqs], X[n_freqs:2 * n_freqs]
+
+    # use SVD across all sensors to estimate the sinusoid phase
+    sin_fit = np.zeros((n_freqs, X_sin.shape[1]))
+    for fi in range(n_freqs):
+        u, s, vt = np.linalg.svd(np.vstack((X_sin[fi, :], X_cos[fi, :])),
+                                 full_matrices=False)
+        # the first component holds the predominant phase direction
+        # (so ignore the second, effectively doing s[1] = 0):
+        X[[fi, fi + n_freqs], :] = np.outer(u[:, 0] * s[0], vt[0])
+        sin_fit[fi, :] = vt[0]
+
+    data_diff = np.dot(model, X).T - this_data
+
+    # compute amplitude correlation (For logging)
+    g_sin = 1 - np.sqrt((data_diff**2).sum() / (this_data**2).sum())
+    g_chan = 1 - np.sqrt((data_diff**2).sum(axis=1) /
+                         (this_data**2).sum(axis=1))
+    logger.debug('    HPI amplitude correlation %0.3f: %0.3f '
+                 '(%s chnls > 0.90)' % (fit_time, g_sin,
+                                        (g_chan > 0.90).sum()))
+
+    return sin_fit
+
+
+ at verbose
+def _fit_device_hpi_positions(raw, t_win=None, initial_dev_rrs=None,
+                              verbose=None):
+    """Calculate location of HPI coils in device coords for 1 time window.
+
+    Parameters
+    ----------
+    raw : instance of Raw
+        Raw data with cHPI information.
+    t_win : list, shape (2)
+        Time window to fit. If None entire data run is used.
+    initial_dev_rrs : ndarry, shape (n_CHPI, 3) || None
+        Initial guess on HPI locations. If None (0,0,0) is used for each hpi.
+    verbose : bool, str, int, or None
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
+
+    Returns
+    -------
+    coil_dev_rrs : ndarray, shape (n_CHPI, 3)
+        Fit locations of each cHPI coil in device coordinates
+    """
+    # 0. determine samples to fit.
+    if t_win is None:  # use the whole window
+        i_win = [0, len(raw.times)]
+    else:
+        i_win = raw.time_as_index(t_win, use_rounding=True)
+
+    # clamp index windows
+    i_win = [max(i_win[0], 0), min(i_win[1], len(raw.times))]
+
+    time_sl = slice(i_win[0], i_win[1])
+
+    hpi = _setup_hpi_struct(raw.info, i_win[1] - i_win[0])
+
+    if initial_dev_rrs is None:
+        initial_dev_rrs = []
+        for i in range(hpi['n_freqs']):
+            initial_dev_rrs.append([0.0, 0.0, 0.0])
+
+    # 1. Fit amplitudes for each channel from each of the N cHPI sinusoids
+    sin_fit = _fit_cHPI_amplitudes(raw, time_sl, hpi, 0)
+
+    # skip this window if it bad.
+    # logging has already been done! Maybe turn this into an Exception
+    if sin_fit is None:
+        return None
+
+    # 2. fit each HPI coil if its turned on
+    outs = [_fit_magnetic_dipole(f, pos, hpi['coils'], hpi['scale'],
+                                 hpi['method'])
+            for f, pos, on in zip(sin_fit, initial_dev_rrs, hpi['on'])
+            if on > 0]
+
+    coil_dev_rrs = np.array([o[0] for o in outs])
+    coil_g = np.array([o[0] for o in outs])
+
+    return coil_dev_rrs, coil_g
+
+
+ at verbose
 def _calculate_chpi_positions(raw, t_step_min=0.1, t_step_max=10.,
                               t_window=0.2, dist_limit=0.005, gof_limit=0.98,
-                              verbose=None):
-    """Calculate head positions using cHPI coils
+                              use_distances=True, verbose=None):
+    """Calculate head positions using cHPI coils.
 
     Parameters
     ----------
@@ -367,14 +566,15 @@ def _calculate_chpi_positions(raw, t_step_min=0.1, t_step_max=10.,
         Maximum time step to use.
     t_window : float
         Time window to use to estimate the head positions.
-    max_step : float
-        Maximum time step to go between estimations.
     dist_limit : float
         Minimum distance (m) to accept for coil position fitting.
     gof_limit : float
         Minimum goodness of fit to accept.
+    use_distances : bool
+        use dist_limit to choose 'good' coils based on pairwise distancs.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -392,129 +592,140 @@ def _calculate_chpi_positions(raw, t_step_min=0.1, t_step_max=10.,
     write_head_pos
     """
     from scipy.spatial.distance import cdist
-    hpi, last = _setup_chpi_fits(raw.info, t_window, t_step_min)
-    fit_idxs = raw.time_as_index(np.arange(0., raw.times[-1], t_step_min),
+    # extract initial geometry from info['hpi_results']
+    hpi_dig_head_rrs = _get_hpi_initial_fit(raw.info)
+
+    # extract hpi system information
+    hpi = _setup_hpi_struct(raw.info, int(round(t_window * raw.info['sfreq'])))
+
+    # move to device coords
+    dev_head_t = raw.info['dev_head_t']['trans']
+    head_dev_t = invert_transform(raw.info['dev_head_t'])['trans']
+    hpi_dig_dev_rrs = apply_trans(head_dev_t, hpi_dig_head_rrs)
+
+    # compute initial coil to coil distances
+    hpi_coil_dists = cdist(hpi_dig_head_rrs, hpi_dig_head_rrs)
+
+    # setup last iteration structure
+    last = dict(sin_fit=None, fit_time=t_step_min,
+                coil_dev_rrs=hpi_dig_dev_rrs,
+                quat=np.concatenate([rot_to_quat(dev_head_t[:3, :3]),
+                                     dev_head_t[:3, 3]]))
+
+    t_begin = raw.times[0]
+    t_end = raw.times[-1]
+    fit_idxs = raw.time_as_index(np.arange(t_begin + t_window / 2., t_end,
+                                           t_step_min),
                                  use_rounding=True)
     quats = []
     logger.info('Fitting up to %s time points (%0.1f sec duration)'
-                % (len(fit_idxs), raw.times[-1]))
+                % (len(fit_idxs), t_end - t_begin))
     pos_0 = None
-    n_freqs = len(hpi['freqs'])
+
+    hpi['n_freqs'] = len(hpi['freqs'])
     for midpt in fit_idxs:
         #
-        # 1. Fit amplitudes for each channel from each of the N cHPI sinusoids
+        # 0. determine samples to fit.
         #
-        fit_time = midpt / raw.info['sfreq']
+        fit_time = (midpt + raw.first_samp - hpi['n_window'] / 2.) /\
+            raw.info['sfreq']
+
         time_sl = midpt - hpi['n_window'] // 2
         time_sl = slice(max(time_sl, 0),
                         min(time_sl + hpi['n_window'], len(raw.times)))
-        with use_log_level(False):
-            meg_chpi_data = raw[hpi['picks'], time_sl][0]
-        this_data = meg_chpi_data[:-1]
-        chpi_data = meg_chpi_data[-1]
-        ons = (np.round(chpi_data).astype(np.int) &
-               hpi['on'][:, np.newaxis]).astype(bool)
-        n_on = np.sum(ons, axis=0)
-        if not (n_on >= 3).all():
-            logger.info(_time_prefix(fit_time) + '%s < 3 HPI coils turned on, '
-                        'skipping fit' % (n_on.min(),))
+
+        #
+        # 1. Fit amplitudes for each channel from each of the N cHPI sinusoids
+        #
+        sin_fit = _fit_cHPI_amplitudes(raw, time_sl, hpi, fit_time)
+
+        # skip this window if bad
+        # logging has already been done! Maybe turn this into an Exception
+        if sin_fit is None:
             continue
-        # ons = ons.all(axis=1)  # which HPI coils to use
-        this_len = time_sl.stop - time_sl.start
-        if this_len == hpi['n_window']:
-            model, inv_model = hpi['model'], hpi['inv_model']
-        else:  # first or last window
-            model = hpi['model'][:this_len]
-            inv_model = linalg.pinv(model)
-        X = np.dot(inv_model, this_data.T)
-        data_diff = np.dot(model, X).T - this_data
-        del model, inv_model
-        data_diff *= data_diff
-        this_data *= this_data
-        g_chan = (1 - np.sqrt(data_diff.sum(axis=1) / this_data.sum(axis=1)))
-        g_sin = (1 - np.sqrt(data_diff.sum() / this_data.sum()))
-        del data_diff, this_data
-        X_sin, X_cos = X[:n_freqs], X[n_freqs:2 * n_freqs]
-        signs = np.sign(np.arctan2(X_sin, X_cos))
-        X_sin *= X_sin
-        X_cos *= X_cos
-        X_sin += X_cos
-        sin_fit = np.sqrt(X_sin)
+
+        # check if data has sufficiently changed
         if last['sin_fit'] is not None:  # first iteration
             corr = np.corrcoef(sin_fit.ravel(), last['sin_fit'].ravel())[0, 1]
             # check to see if we need to continue
             if fit_time - last['fit_time'] <= t_step_max - 1e-7 and \
                     corr * corr > 0.98:
-                continue  # don't need to re-fit data
-        last['sin_fit'] = sin_fit.copy()  # save *before* inplace sign mult
-        sin_fit *= signs
-        del signs, X_sin, X_cos, X
+                # don't need to refit data
+                continue
+
+        # update 'last' sin_fit *before* inplace sign mult
+        last['sin_fit'] = sin_fit.copy()
 
         #
         # 2. Fit magnetic dipole for each coil to obtain coil positions
         #    in device coordinates
         #
-        logger.debug('    HPI amplitude correlation %0.3f: %0.3f '
-                     '(%s chnls > 0.950)' % (fit_time, np.sqrt(g_sin),
-                                             (np.sqrt(g_chan) > 0.95).sum()))
         outs = [_fit_magnetic_dipole(f, pos, hpi['coils'], hpi['scale'],
                                      hpi['method'])
                 for f, pos in zip(sin_fit, last['coil_dev_rrs'])]
         this_coil_dev_rrs = np.array([o[0] for o in outs])
         g_coils = [o[1] for o in outs]
-        these_dists = cdist(this_coil_dev_rrs, this_coil_dev_rrs)
-        these_dists = np.abs(hpi['dists'] - these_dists)
-        # there is probably a better algorithm for finding the bad ones...
-        good = False
-        use_mask = np.ones(n_freqs, bool)
-        while not good:
-            d = these_dists[use_mask][:, use_mask]
-            d_bad = (d > dist_limit)
-            good = not d_bad.any()
+
+        # filter coil fits based on the correspodnace to digitization geometry
+        use_mask = np.ones(hpi['n_freqs'], bool)
+        if use_distances:
+            these_dists = cdist(this_coil_dev_rrs, this_coil_dev_rrs)
+            these_dists = np.abs(hpi_coil_dists - these_dists)
+            # there is probably a better algorithm for finding the bad ones...
+            good = False
+            while not good:
+                d = these_dists[use_mask][:, use_mask]
+                d_bad = (d > dist_limit)
+                good = not d_bad.any()
+                if not good:
+                    if use_mask.sum() == 2:
+                        use_mask[:] = False
+                        break  # failure
+                    # exclude next worst point
+                    badness = (d * d_bad).sum(axis=0)
+                    exclude_coils = np.where(use_mask)[0][np.argmax(badness)]
+                    use_mask[exclude_coils] = False
+            good = use_mask.sum() >= 3
             if not good:
-                if use_mask.sum() == 2:
-                    use_mask[:] = False
-                    break  # failure
-                # exclude next worst point
-                badness = (d * d_bad).sum(axis=0)
-                exclude = np.where(use_mask)[0][np.argmax(badness)]
-                use_mask[exclude] = False
-        good = use_mask.sum() >= 3
-        if not good:
-            warn(_time_prefix(fit_time) + '%s/%s good HPI fits, '
-                 'cannot determine the transformation!'
-                 % (use_mask.sum(), n_freqs))
-            continue
+                warn(_time_prefix(fit_time) + '%s/%s good HPI fits, '
+                     'cannot determine the transformation!'
+                     % (use_mask.sum(), hpi['n_freqs']))
+                continue
 
         #
         # 3. Fit the head translation and rotation params (minimize error
         #    between coil positions and the head coil digitization positions)
         #
-        this_quat, g = _fit_chpi_pos(this_coil_dev_rrs[use_mask],
-                                     hpi['coil_head_rrs'][use_mask],
-                                     last['quat'])
+        this_quat, g = _fit_chpi_quat(this_coil_dev_rrs[use_mask],
+                                      hpi_dig_head_rrs[use_mask],
+                                      last['quat'])
         if g < gof_limit:
             logger.info(_time_prefix(fit_time) +
                         'Bad coil fit! (g=%7.3f)' % (g,))
             continue
+
+        # Convert quaterion to transform
         this_dev_head_t = np.concatenate(
             (quat_to_rot(this_quat[:3]),
              this_quat[3:][:, np.newaxis]), axis=1)
         this_dev_head_t = np.concatenate((this_dev_head_t, [[0, 0, 0, 1.]]))
+
         # velocities, in device coords, of HPI coils
-        dt = fit_time - last['fit_time']
+        # dt = fit_time - last['fit_time'] #
+        dt = t_window
         vs = tuple(1000. * np.sqrt(np.sum((last['coil_dev_rrs'] -
                                            this_coil_dev_rrs) ** 2,
                                           axis=1)) / dt)
         logger.info(_time_prefix(fit_time) +
                     ('%s/%s good HPI fits, movements [mm/s] = ' +
-                     ' / '.join(['% 6.1f'] * n_freqs))
-                    % ((use_mask.sum(), n_freqs) + vs))
+                     ' / '.join(['% 6.1f'] * hpi['n_freqs']))
+                    % ((use_mask.sum(), hpi['n_freqs']) + vs))
+
         # resulting errors in head coil positions
         est_coil_head_rrs = apply_trans(this_dev_head_t, this_coil_dev_rrs)
-        errs = 1000. * np.sqrt(((hpi['coil_head_rrs'] -
+        errs = 1000. * np.sqrt(((hpi_dig_head_rrs -
                                  est_coil_head_rrs) ** 2).sum(axis=-1))
-        e = errs.mean() / 1000.  # mm -> m
+        e = errs[use_mask].mean() / 1000.  # mm -> m
         d = 100 * np.sqrt(np.sum(last['quat'][3:] - this_quat[3:]) ** 2)  # cm
         r = _angle_between_quats(last['quat'][:3], this_quat[:3]) / dt
         v = d / dt  # cm/sec
@@ -522,7 +733,7 @@ def _calculate_chpi_positions(raw, t_step_min=0.1, t_step_max=10.,
             pos_0 = this_quat[3:].copy()
         d = 100 * np.sqrt(np.sum((this_quat[3:] - pos_0) ** 2))  # dis from 1st
         # MaxFilter averages over a 200 ms window for display, but we don't
-        for ii in range(n_freqs):
+        for ii in range(hpi['n_freqs']):
             if use_mask[ii]:
                 start, end = ' ', '/'
             else:
@@ -536,9 +747,9 @@ def _calculate_chpi_positions(raw, t_step_min=0.1, t_step_max=10.,
                 log_str += '{8:6.3f} {9:6.3f} {10:6.3f}'
             elif ii == 3:
                 log_str += '{8:6.1f} {9:6.1f} {10:6.1f}'
-            vals = np.concatenate((1000 * hpi['coil_head_rrs'][ii],
+            vals = np.concatenate((1000 * hpi_dig_head_rrs[ii],
                                    1000 * est_coil_head_rrs[ii],
-                                   [g_coils[ii], errs[ii]]))
+                                   [g_coils[ii], errs[ii]]))  # errs in mm
             if ii <= 2:
                 vals = np.concatenate((vals, this_dev_head_t[ii, :3]))
             elif ii == 3:
@@ -547,7 +758,11 @@ def _calculate_chpi_positions(raw, t_step_min=0.1, t_step_max=10.,
         logger.debug('    #t = %0.3f, #e = %0.2f cm, #g = %0.3f, '
                      '#v = %0.2f cm/s, #r = %0.2f rad/s, #d = %0.2f cm'
                      % (fit_time, 100 * e, g, v, r, d))
-        quats.append(np.concatenate(([fit_time], this_quat, [g], [e], [v])))
+        logger.debug('    #t = %0.3f, #q = %s '
+                     % (fit_time, ' '.join(map('{:8.5f}'.format, this_quat))))
+
+        quats.append(np.concatenate(([fit_time], this_quat, [g],
+                                     [e * 100], [v])))  # e in centimeters
         last['fit_time'] = fit_time
         last['quat'] = this_quat
         last['coil_dev_rrs'] = this_coil_dev_rrs
@@ -558,8 +773,136 @@ def _calculate_chpi_positions(raw, t_step_min=0.1, t_step_max=10.,
 
 
 @verbose
-def filter_chpi(raw, include_line=True, verbose=None):
-    """Remove cHPI and line noise from data
+def _calculate_chpi_coil_locs(raw, t_step_min=0.1, t_step_max=10.,
+                              t_window=0.2, dist_limit=0.005, gof_limit=0.98,
+                              verbose=None):
+    """Calculate locations of each cHPI coils over time.
+
+    Parameters
+    ----------
+    raw : instance of Raw
+        Raw data with cHPI information.
+    t_step_min : float
+        Minimum time step to use. If correlations are sufficiently high,
+        t_step_max will be used.
+    t_step_max : float
+        Maximum time step to use.
+    t_window : float
+        Time window to use to estimate the head positions.
+    dist_limit : float
+        Minimum distance (m) to accept for coil position fitting.
+    gof_limit : float
+        Minimum goodness of fit to accept.
+    verbose : bool, str, int, or None
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
+
+    Returns
+    -------
+    time : ndarray, shape (N, 1)
+        The start time of each fitting interval
+    chpi_digs :ndarray, shape (N, 1)
+        Array of dig structures containing the cHPI locations. Includes
+        goodness of fit for each cHPI.
+
+    Notes
+    -----
+    The number of time points ``N`` will depend on the velocity of head
+    movements as well as ``t_step_max`` and ``t_step_min``.
+
+    See Also
+    --------
+    read_head_pos
+    write_head_pos
+    """
+    # extract initial geometry from info['hpi_results']
+    hpi_dig_head_rrs = _get_hpi_initial_fit(raw.info)
+
+    # extract hpi system information
+    hpi = _setup_hpi_struct(raw.info, int(round(t_window * raw.info['sfreq'])))
+
+    # move to device coords
+    head_dev_t = invert_transform(raw.info['dev_head_t'])['trans']
+    hpi_dig_dev_rrs = apply_trans(head_dev_t, hpi_dig_head_rrs)
+
+    # setup last iteration structure
+    last = dict(sin_fit=None, fit_time=t_step_min,
+                coil_dev_rrs=hpi_dig_dev_rrs)
+
+    t_begin = raw.times[0]
+    t_end = raw.times[-1]
+    fit_idxs = raw.time_as_index(np.arange(t_begin + t_window / 2., t_end,
+                                           t_step_min),
+                                 use_rounding=True)
+    times = []
+    chpi_digs = []
+    logger.info('Fitting up to %s time points (%0.1f sec duration)'
+                % (len(fit_idxs), t_end - t_begin))
+
+    hpi['n_freqs'] = len(hpi['freqs'])
+    for midpt in fit_idxs:
+        #
+        # 0. determine samples to fit.
+        #
+        fit_time = (midpt + raw.first_samp - hpi['n_window'] / 2.) /\
+            raw.info['sfreq']
+
+        time_sl = midpt - hpi['n_window'] // 2
+        time_sl = slice(max(time_sl, 0),
+                        min(time_sl + hpi['n_window'], len(raw.times)))
+
+        #
+        # 1. Fit amplitudes for each channel from each of the N cHPI sinusoids
+        #
+        sin_fit = _fit_cHPI_amplitudes(raw, time_sl, hpi, fit_time)
+
+        # skip this window if bad
+        # logging has already been done! Maybe turn this into an Exception
+        if sin_fit is None:
+            continue
+
+        # check if data has sufficiently changed
+        if last['sin_fit'] is not None:  # first iteration
+            corr = np.corrcoef(sin_fit.ravel(), last['sin_fit'].ravel())[0, 1]
+            # check to see if we need to continue
+            if fit_time - last['fit_time'] <= t_step_max - 1e-7 and \
+                    corr * corr > 0.98:
+                # don't need to refit data
+                continue
+
+        # update 'last' sin_fit *before* inplace sign mult
+        last['sin_fit'] = sin_fit.copy()
+
+        #
+        # 2. Fit magnetic dipole for each coil to obtain coil positions
+        #    in device coordinates
+        #
+        outs = [_fit_magnetic_dipole(f, pos, hpi['coils'], hpi['scale'],
+                                     hpi['method'])
+                for f, pos in zip(sin_fit, last['coil_dev_rrs'])]
+
+        dig = []
+        for idx, o in enumerate(outs):
+            dig.append({'r': o[0], 'ident': idx + 1,
+                        'kind': FIFF.FIFFV_POINT_HPI,
+                        'coord_frame': FIFF.FIFFV_COORD_DEVICE,
+                        'gof': o[1]})
+
+        this_coil_dev_rrs = np.array([o[0] for o in outs])
+
+        times.append(fit_time)
+        chpi_digs.append(dig)
+
+        last['fit_time'] = fit_time
+        last['coil_dev_rrs'] = this_coil_dev_rrs
+    logger.info('[done]')
+    return times, chpi_digs
+
+
+ at verbose
+def filter_chpi(raw, include_line=True, t_step=0.01, t_window=0.2,
+                verbose=None):
+    """Remove cHPI and line noise from data.
 
     .. note:: This function will only work properly if cHPI was on
               during the recording.
@@ -570,8 +913,14 @@ def filter_chpi(raw, include_line=True, verbose=None):
         Raw data with cHPI information. Must be preloaded. Operates in-place.
     include_line : bool
         If True, also filter line noise.
+    t_step : float
+        Time step to use for estimation, default is 0.01 (10 ms).
+    t_window : float
+        Time window to use to estimate the amplitudes, default is
+        0.2 (200 ms).
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -589,12 +938,16 @@ def filter_chpi(raw, include_line=True, verbose=None):
     """
     if not raw.preload:
         raise RuntimeError('raw data must be preloaded')
-    t_window = 0.2
-    t_step = 0.01
+    t_step = float(t_step)
+    t_window = float(t_window)
+    if not (t_step > 0 and t_window > 0):
+        raise ValueError('t_step (%s) and t_window (%s) must both be > 0.'
+                         % (t_step, t_window))
     n_step = int(np.ceil(t_step * raw.info['sfreq']))
-    hpi = _setup_chpi_fits(raw.info, t_window, t_window, exclude='bads',
-                           add_hpi_stim_pick=False, remove_aliased=True,
-                           verbose=False)[0]
+    hpi = _setup_hpi_struct(raw.info, int(round(t_window * raw.info['sfreq'])),
+                            exclude='bads', remove_aliased=True,
+                            verbose=False)
+
     fit_idxs = np.arange(0, len(raw.times) + hpi['n_window'] // 2, n_step)
     n_freqs = len(hpi['freqs'])
     n_remove = 2 * n_freqs
diff --git a/mne/commands/__init__.py b/mne/commands/__init__.py
index eb018c3..62eef81 100644
--- a/mne/commands/__init__.py
+++ b/mne/commands/__init__.py
@@ -1 +1,3 @@
+"""Command-line utilities."""
+
 from . import utils
diff --git a/mne/commands/mne_browse_raw.py b/mne/commands/mne_browse_raw.py
index 9ff3b2b..b44e6b5 100755
--- a/mne/commands/mne_browse_raw.py
+++ b/mne/commands/mne_browse_raw.py
@@ -1,5 +1,5 @@
 #!/usr/bin/env python
-"""Browse raw data
+r"""Browse raw data.
 
 You can do for example:
 
@@ -15,6 +15,7 @@ import mne
 
 
 def run():
+    """Run command."""
     import matplotlib.pyplot as plt
 
     from mne.commands.utils import get_optparser
diff --git a/mne/commands/mne_bti2fiff.py b/mne/commands/mne_bti2fiff.py
index ec57e55..7dd9f13 100755
--- a/mne/commands/mne_bti2fiff.py
+++ b/mne/commands/mne_bti2fiff.py
@@ -1,5 +1,5 @@
 #!/usr/bin/env python
-"""Import BTi / 4D MagnesWH3600 data to fif file.
+r"""Import BTi / 4D MagnesWH3600 data to fif file.
 
 example usage: mne bti2fiff --pdf C,rfDC -o my_raw.fif
 
@@ -29,6 +29,7 @@ from mne.io import read_raw_bti
 
 
 def run():
+    """Run command."""
     from mne.commands.utils import get_optparser
 
     parser = get_optparser(__file__)
diff --git a/mne/commands/mne_clean_eog_ecg.py b/mne/commands/mne_clean_eog_ecg.py
index 3606660..09b3ffe 100755
--- a/mne/commands/mne_clean_eog_ecg.py
+++ b/mne/commands/mne_clean_eog_ecg.py
@@ -1,5 +1,9 @@
 #!/usr/bin/env python
-"""Clean a raw file from EOG and ECG artifacts with PCA (ie SSP)
+"""Clean a raw file from EOG and ECG artifacts with PCA (ie SSP).
+
+You can do for example:
+
+$ mne clean_eog_ecg -i in_raw.fif -o clean_raw.fif -e -c
 """
 from __future__ import print_function
 
@@ -17,7 +21,7 @@ def clean_ecg_eog(in_fif_fname, out_fif_fname=None, eog=True, ecg=True,
                   ecg_proj_fname=None, eog_proj_fname=None,
                   ecg_event_fname=None, eog_event_fname=None, in_path='.',
                   quiet=False):
-    """Clean ECG from raw fif file
+    """Clean ECG from raw fif file.
 
     Parameters
     ----------
@@ -101,6 +105,7 @@ def clean_ecg_eog(in_fif_fname, out_fif_fname=None, eog=True, ecg=True,
 
 
 def run():
+    """Run command."""
     from mne.commands.utils import get_optparser
 
     parser = get_optparser(__file__)
diff --git a/mne/commands/mne_compare_fiff.py b/mne/commands/mne_compare_fiff.py
index bc8a223..a1b29f0 100755
--- a/mne/commands/mne_compare_fiff.py
+++ b/mne/commands/mne_compare_fiff.py
@@ -1,5 +1,5 @@
 #!/usr/bin/env python
-"""Compare FIFF files
+"""Compare FIFF files.
 
 You can do for example:
 
@@ -13,6 +13,7 @@ import mne
 
 
 def run():
+    """Run command."""
     parser = mne.commands.utils.get_optparser(
         __file__, usage='mne compare_fiff <file_a> <file_b>')
     options, args = parser.parse_args()
diff --git a/mne/commands/mne_compute_proj_ecg.py b/mne/commands/mne_compute_proj_ecg.py
index 238f212..3be76f8 100755
--- a/mne/commands/mne_compute_proj_ecg.py
+++ b/mne/commands/mne_compute_proj_ecg.py
@@ -1,5 +1,5 @@
 #!/usr/bin/env python
-"""Compute SSP/PCA projections for ECG artifacts
+r"""Compute SSP/PCA projections for ECG artifacts.
 
 You can do for example:
 
@@ -19,6 +19,7 @@ import mne
 
 
 def run():
+    """Run command."""
     from mne.commands.utils import get_optparser
 
     parser = get_optparser(__file__)
diff --git a/mne/commands/mne_compute_proj_eog.py b/mne/commands/mne_compute_proj_eog.py
index 0513ab4..090b970 100755
--- a/mne/commands/mne_compute_proj_eog.py
+++ b/mne/commands/mne_compute_proj_eog.py
@@ -1,5 +1,5 @@
 #!/usr/bin/env python
-"""Compute SSP/PCA projections for EOG artifacts
+r"""Compute SSP/PCA projections for EOG artifacts.
 
 You can do for example:
 
@@ -28,6 +28,7 @@ import mne
 
 
 def run():
+    """Run command."""
     from mne.commands.utils import get_optparser
 
     parser = get_optparser(__file__)
diff --git a/mne/commands/mne_coreg.py b/mne/commands/mne_coreg.py
index fb37d16..aebe6e7 100644
--- a/mne/commands/mne_coreg.py
+++ b/mne/commands/mne_coreg.py
@@ -4,7 +4,6 @@
 """Open the coregistration GUI.
 
 example usage:  $ mne coreg
-
 """
 
 import sys
@@ -14,6 +13,7 @@ from mne.utils import ETSContext
 
 
 def run():
+    """Run command."""
     from mne.commands.utils import get_optparser
 
     parser = get_optparser(__file__)
@@ -28,13 +28,49 @@ def run():
                       default=False, help="Option for small screens: Combine "
                       "the data source panel and the coregistration panel "
                       "into a single panel with tabs.")
+    parser.add_option("--no-guess-mri", dest="guess_mri_subject",
+                      action='store_false', default=None,
+                      help="Prevent the GUI from automatically guessing and "
+                      "changing the MRI subject when a new head shape source "
+                      "file is selected.")
+    parser.add_option("--head-opacity", type=float, default=None,
+                      dest="head_opacity",
+                      help="The opacity of the head surface, in the range "
+                      "[0, 1].")
+    parser.add_option("--high-res-head",
+                      action='store_true', default=False, dest="high_res_head",
+                      help="Use a high-resolution head surface.")
+    parser.add_option("--low-res-head",
+                      action='store_true', default=False, dest="low_res_head",
+                      help="Use a low-resolution head surface.")
+    parser.add_option('--trans', dest='trans', default=None,
+                      help='Head<->MRI transform FIF file ("-trans.fif")')
+    parser.add_option('--verbose', action='store_true', dest='verbose',
+                      help='Turn on verbose mode.')
 
     options, args = parser.parse_args()
 
+    if options.low_res_head:
+        if options.high_res_head:
+            raise ValueError("Can't specify --high-res-head and "
+                             "--low-res-head at the same time.")
+        head_high_res = False
+    elif options.high_res_head:
+        head_high_res = True
+    else:
+        head_high_res = None
+
     with ETSContext():
-        mne.gui.coregistration(options.tabbed, inst=options.inst,
+        mne.gui.coregistration(options.tabbed,
+                               inst=options.inst,
                                subject=options.subject,
-                               subjects_dir=options.subjects_dir)
+                               subjects_dir=options.subjects_dir,
+                               guess_mri_subject=options.guess_mri_subject,
+                               head_opacity=options.head_opacity,
+                               head_high_res=head_high_res,
+                               trans=options.trans,
+                               scrollable=True,
+                               verbose=options.verbose)
     if is_main:
         sys.exit(0)
 
diff --git a/mne/commands/mne_flash_bem.py b/mne/commands/mne_flash_bem.py
index cc52010..c590a7e 100644
--- a/mne/commands/mne_flash_bem.py
+++ b/mne/commands/mne_flash_bem.py
@@ -1,5 +1,5 @@
 #!/usr/bin/env python
-"""Create 3-Layers BEM model from Flash MRI images
+"""Create 3-layer BEM model from Flash MRI images.
 
 This program assumes that FreeSurfer and MNE are installed and
 sourced properly.
@@ -34,7 +34,6 @@ Before running this script do the following:
 Example usage:
 
 $ mne flash_bem --subject sample
-
 """
 from __future__ import print_function
 
@@ -44,6 +43,7 @@ from mne.bem import convert_flash_mris, make_flash_bem
 
 
 def run():
+    """Run command."""
     from mne.commands.utils import get_optparser
 
     parser = get_optparser(__file__)
diff --git a/mne/commands/mne_freeview_bem_surfaces.py b/mne/commands/mne_freeview_bem_surfaces.py
index 78b085b..b47da94 100644
--- a/mne/commands/mne_freeview_bem_surfaces.py
+++ b/mne/commands/mne_freeview_bem_surfaces.py
@@ -1,6 +1,9 @@
 #!/usr/bin/env python
-"""View the 3-Layers BEM model using Freeview
+"""View the 3-Layers BEM model using Freeview.
 
+You can do for example:
+
+$ mne freeview_bem_surfaces -s sample
 """
 from __future__ import print_function
 
@@ -14,7 +17,7 @@ from mne.utils import run_subprocess, get_subjects_dir
 
 
 def freeview_bem_surfaces(subject, subjects_dir, method):
-    """View 3-Layers BEM model with Freeview
+    """View 3-Layers BEM model with Freeview.
 
     Parameters
     ----------
@@ -63,6 +66,7 @@ def freeview_bem_surfaces(subject, subjects_dir, method):
 
 
 def run():
+    """Run command."""
     from mne.commands.utils import get_optparser
 
     parser = get_optparser(__file__)
diff --git a/mne/commands/mne_kit2fiff.py b/mne/commands/mne_kit2fiff.py
index bc337d5..ca91354 100755
--- a/mne/commands/mne_kit2fiff.py
+++ b/mne/commands/mne_kit2fiff.py
@@ -16,6 +16,7 @@ from mne.utils import ETSContext
 
 
 def run():
+    """Run command."""
     from mne.commands.utils import get_optparser
 
     parser = get_optparser(__file__)
@@ -39,9 +40,15 @@ def run():
     parser.add_option('--output', dest='out_fname',
                       help='Name of the resulting fiff file',
                       metavar='filename')
+    parser.add_option('--debug', dest='debug', action='store_true',
+                      default=False,
+                      help='Set logging level for terminal output to debug')
 
     options, args = parser.parse_args()
 
+    if options.debug:
+        mne.set_log_level('debug')
+
     input_fname = options.input_fname
     if input_fname is None:
         with ETSContext():
diff --git a/mne/commands/mne_make_scalp_surfaces.py b/mne/commands/mne_make_scalp_surfaces.py
index 08c60b1..69924e7 100755
--- a/mne/commands/mne_make_scalp_surfaces.py
+++ b/mne/commands/mne_make_scalp_surfaces.py
@@ -17,17 +17,19 @@ import copy
 import os.path as op
 import sys
 import mne
-from mne.utils import run_subprocess, _TempDir, verbose, logger, ETSContext
+from mne.utils import (run_subprocess, verbose, logger, ETSContext,
+                       get_subjects_dir)
 
 
 def _check_file(fname, overwrite):
-    """Helper to prevent overwrites"""
+    """Prevent overwrites."""
     if op.isfile(fname) and not overwrite:
         raise IOError('File %s exists, use --overwrite to overwrite it'
                       % fname)
 
 
 def run():
+    """Run command."""
     from mne.commands.utils import get_optparser
 
     parser = get_optparser(__file__)
@@ -62,41 +64,28 @@ def run():
 @verbose
 def _run(subjects_dir, subject, force, overwrite, no_decimate, verbose=None):
     this_env = copy.copy(os.environ)
+    subjects_dir = get_subjects_dir(subjects_dir, raise_error=True)
     this_env['SUBJECTS_DIR'] = subjects_dir
     this_env['SUBJECT'] = subject
-
-    if 'SUBJECTS_DIR' not in this_env:
-        raise RuntimeError('The environment variable SUBJECTS_DIR should '
-                           'be set')
-
-    if not op.isdir(subjects_dir):
-        raise RuntimeError('subjects directory %s not found, specify using '
-                           'the environment variable SUBJECTS_DIR or '
-                           'the command line option --subjects-dir')
-
-    if 'MNE_ROOT' not in this_env:
-        raise RuntimeError('MNE_ROOT environment variable is not set')
-
     if 'FREESURFER_HOME' not in this_env:
         raise RuntimeError('The FreeSurfer environment needs to be set up '
                            'for this script')
     force = '--force' if force else '--check'
     subj_path = op.join(subjects_dir, subject)
     if not op.exists(subj_path):
-        raise RuntimeError('%s does not exits. Please check your subject '
+        raise RuntimeError('%s does not exist. Please check your subject '
                            'directory path.' % subj_path)
 
-    if op.exists(op.join(subj_path, 'mri', 'T1.mgz')):
-        mri = 'T1.mgz'
-    else:
-        mri = 'T1'
+    mri = 'T1.mgz' if op.exists(op.join(subj_path, 'mri', 'T1.mgz')) else 'T1'
 
     logger.info('1. Creating a dense scalp tessellation with mkheadsurf...')
 
     def check_seghead(surf_path=op.join(subj_path, 'surf')):
-        for k in ['/lh.seghead', '/lh.smseghead']:
-            surf = surf_path + k if op.exists(surf_path + k) else None
-            if surf is not None:
+        surf = None
+        for k in ['lh.seghead', 'lh.smseghead']:
+            this_surf = op.join(surf_path, k)
+            if op.exists(this_surf):
+                surf = this_surf
                 break
         return surf
 
@@ -114,33 +103,27 @@ def _run(subjects_dir, subject, force, overwrite, no_decimate, verbose=None):
                                                           subject)
     logger.info('2. Creating %s ...' % dense_fname)
     _check_file(dense_fname, overwrite)
-    run_subprocess(['mne_surf2bem', '--surf', surf, '--id', '4', force,
-                    '--fif', dense_fname], env=this_env)
+    surf = mne.bem._surfaces_to_bem(
+        [surf], [mne.io.constants.FIFF.FIFFV_BEM_SURF_ID_HEAD], [1])[0]
+    mne.write_bem_surfaces(dense_fname, surf)
     levels = 'medium', 'sparse'
-    my_surf = mne.read_bem_surfaces(dense_fname)[0]
     tris = [] if no_decimate else [30000, 2500]
     if os.getenv('_MNE_TESTING_SCALP', 'false') == 'true':
-        tris = [len(my_surf['tris'])]  # don't actually decimate
+        tris = [len(surf['tris'])]  # don't actually decimate
     for ii, (n_tri, level) in enumerate(zip(tris, levels), 3):
         logger.info('%i. Creating %s tessellation...' % (ii, level))
         logger.info('%i.1 Decimating the dense tessellation...' % ii)
         with ETSContext():
-            points, tris = mne.decimate_surface(points=my_surf['rr'],
-                                                triangles=my_surf['tris'],
+            points, tris = mne.decimate_surface(points=surf['rr'],
+                                                triangles=surf['tris'],
                                                 n_triangles=n_tri)
-        other_fname = dense_fname.replace('dense', level)
-        logger.info('%i.2 Creating %s' % (ii, other_fname))
-        _check_file(other_fname, overwrite)
-        tempdir = _TempDir()
-        surf_fname = tempdir + '/tmp-surf.surf'
-        # convert points to meters, make mne_analyze happy
-        mne.write_surface(surf_fname, points * 1e3, tris)
-        # XXX for some reason --check does not work here.
-        try:
-            run_subprocess(['mne_surf2bem', '--surf', surf_fname, '--id', '4',
-                            '--force', '--fif', other_fname], env=this_env)
-        finally:
-            del tempdir
+        dec_fname = dense_fname.replace('dense', level)
+        logger.info('%i.2 Creating %s' % (ii, dec_fname))
+        _check_file(dec_fname, overwrite)
+        dec_surf = mne.bem._surfaces_to_bem(
+            [dict(rr=points, tris=tris)],
+            [mne.io.constants.FIFF.FIFFV_BEM_SURF_ID_HEAD], [1], rescale=False)
+        mne.write_bem_surfaces(dec_fname, dec_surf)
 
 is_main = (__name__ == '__main__')
 if is_main:
diff --git a/mne/commands/mne_maxfilter.py b/mne/commands/mne_maxfilter.py
index 4c64b9e..31dbfe2 100755
--- a/mne/commands/mne_maxfilter.py
+++ b/mne/commands/mne_maxfilter.py
@@ -1,5 +1,5 @@
 #!/usr/bin/env python
-"""Apply MaxFilter
+"""Apply MaxFilter.
 
 Example usage:
 
@@ -18,6 +18,7 @@ import mne
 
 
 def run():
+    """Run command."""
     from mne.commands.utils import get_optparser
 
     parser = get_optparser(__file__)
diff --git a/mne/commands/mne_report.py b/mne/commands/mne_report.py
index 417730e..8d45f62 100644
--- a/mne/commands/mne_report.py
+++ b/mne/commands/mne_report.py
@@ -1,5 +1,5 @@
 #!/usr/bin/env python
-"""Create mne report for a folder
+r"""Create mne report for a folder.
 
 Example usage
 
@@ -18,10 +18,12 @@ from mne.utils import verbose, logger
 
 @verbose
 def log_elapsed(t, verbose=None):
+    """Log elapsed time."""
     logger.info('Report complete in %s seconds' % round(t, 1))
 
 
 def run():
+    """Run command."""
     from mne.commands.utils import get_optparser
 
     parser = get_optparser(__file__)
@@ -55,6 +57,9 @@ def run():
     parser.add_option("-m", "--mri-decim", type="int", dest="mri_decim",
                       default=2, help="Integer factor used to decimate "
                       "BEM plots")
+    parser.add_option("--image-format", type="str", dest="image_format",
+                      default='png', help="Image format to use "
+                      "(can be 'png' or 'svg')")
 
     options, args = parser.parse_args()
     path = options.path
@@ -65,6 +70,7 @@ def run():
     cov_fname = options.cov_fname
     subjects_dir = options.subjects_dir
     subject = options.subject
+    image_format = options.image_format
     mri_decim = int(options.mri_decim)
     verbose = True if options.verbose is not None else False
     open_browser = False if options.no_browser is not None else True
@@ -82,7 +88,8 @@ def run():
     t0 = time.time()
     report = Report(info_fname, subjects_dir=subjects_dir,
                     subject=subject, baseline=baseline,
-                    cov_fname=cov_fname, verbose=verbose)
+                    cov_fname=cov_fname, verbose=verbose,
+                    image_format=image_format)
     report.parse_folder(path, verbose=verbose, n_jobs=n_jobs,
                         mri_decim=mri_decim)
     log_elapsed(time.time() - t0, verbose=verbose)
diff --git a/mne/commands/mne_show_fiff.py b/mne/commands/mne_show_fiff.py
index 3076fe7..471188d 100644
--- a/mne/commands/mne_show_fiff.py
+++ b/mne/commands/mne_show_fiff.py
@@ -1,9 +1,13 @@
 #!/usr/bin/env python
-"""Show the contents of a FIFF file
+"""Show the contents of a FIFF file.
 
 You can do for example:
 
 $ mne show_fiff test_raw.fif
+
+To see only tag 102:
+
+$ mne show_fiff test_raw.fif --tag=102
 """
 
 # Authors : Eric Larson, PhD
@@ -14,18 +18,23 @@ import mne
 
 
 def run():
+    """Run command."""
     parser = mne.commands.utils.get_optparser(
         __file__, usage='mne show_fiff <file>')
+    parser.add_option("-t", "--tag", dest="tag",
+                      help="provide information about this tag", metavar="TAG")
     options, args = parser.parse_args()
+
     if len(args) != 1:
         parser.print_help()
         sys.exit(1)
-    # This works around an annoying bug on Windows for show_fiff, see:
-    # https://pythonhosted.org/kitchen/unicode-frustrations.html
-    if int(sys.version[0]) < 3:
+    if sys.platform == "win32" and int(sys.version[0]) < 3:
+        # This works around an annoying bug on Windows for show_fiff, see:
+        # https://pythonhosted.org/kitchen/unicode-frustrations.html
         UTF8Writer = codecs.getwriter('utf8')
         sys.stdout = UTF8Writer(sys.stdout)
-    print(mne.io.show_fiff(args[0]))
+    msg = mne.io.show_fiff(args[0], tag=options.tag).strip()
+    print(msg)
 
 
 is_main = (__name__ == '__main__')
diff --git a/mne/commands/mne_show_info.py b/mne/commands/mne_show_info.py
index aead8a9..622eaf7 100644
--- a/mne/commands/mne_show_info.py
+++ b/mne/commands/mne_show_info.py
@@ -13,6 +13,7 @@ import mne
 
 
 def run():
+    """Run command."""
     parser = mne.commands.utils.get_optparser(
         __file__, usage='mne show_info <file>')
     options, args = parser.parse_args()
diff --git a/mne/commands/mne_surf2bem.py b/mne/commands/mne_surf2bem.py
index 9b40b8b..033a9d3 100755
--- a/mne/commands/mne_surf2bem.py
+++ b/mne/commands/mne_surf2bem.py
@@ -1,11 +1,10 @@
 #!/usr/bin/env python
-"""Convert surface to BEM FIF file
+r"""Convert surface to BEM FIF file.
 
 Example usage
 
 mne surf2bem --surf ${SUBJECTS_DIR}/${SUBJECT}/surf/lh.seghead --fif \
 ${SUBJECTS_DIR}/${SUBJECT}/bem/${SUBJECT}-head.fif --id=4
-
 """
 from __future__ import print_function
 # Authors: Alexandre Gramfort <alexandre.gramfort at telecom-paristech.fr>
@@ -18,6 +17,7 @@ import mne
 
 
 def run():
+    """Run command."""
     from mne.commands.utils import get_optparser
 
     parser = get_optparser(__file__)
@@ -36,10 +36,8 @@ def run():
         sys.exit(1)
 
     print("Converting %s to BEM FIF file." % options.surf)
-    points, tris = mne.read_surface(options.surf)
-    points *= 1e-3
-    surf = dict(coord_frame=5, id=int(options.id), nn=None, np=len(points),
-                ntri=len(tris), rr=points, sigma=1, tris=tris)
+    surf = mne.bem._surfaces_to_bem([options.surf], [int(options.id)],
+                                    sigmas=[1])
     mne.write_bem_surfaces(options.fif, surf)
 
 
diff --git a/mne/commands/mne_watershed_bem.py b/mne/commands/mne_watershed_bem.py
index 8efe423..af8d8ae 100644
--- a/mne/commands/mne_watershed_bem.py
+++ b/mne/commands/mne_watershed_bem.py
@@ -1,10 +1,10 @@
 #!/usr/bin/env python
 # Authors: Lorenzo De Santis
-"""
+"""Create BEM surfaces using the watershed algorithm included with FreeSurfer.
 
-    Create BEM surfaces using the watershed algorithm included with
-        FreeSurfer
+You can do for example:
 
+$ mne watershed_bem -s sample
 """
 
 from __future__ import print_function
@@ -14,6 +14,7 @@ from mne.bem import make_watershed_bem
 
 
 def run():
+    """Run command."""
     from mne.commands.utils import get_optparser
 
     parser = get_optparser(__file__)
diff --git a/mne/commands/tests/test_commands.py b/mne/commands/tests/test_commands.py
index 51520c1..7ed6874 100644
--- a/mne/commands/tests/test_commands.py
+++ b/mne/commands/tests/test_commands.py
@@ -4,9 +4,11 @@ from os import path as op
 import shutil
 import glob
 import warnings
+import pytest
 from nose.tools import assert_true, assert_raises
+from numpy.testing import assert_equal, assert_allclose
 
-from mne import concatenate_raws
+from mne import concatenate_raws, read_bem_surfaces
 from mne.commands import (mne_browse_raw, mne_bti2fiff, mne_clean_eog_ecg,
                           mne_compute_proj_ecg, mne_compute_proj_eog,
                           mne_coreg, mne_kit2fiff,
@@ -18,7 +20,7 @@ from mne.datasets import testing, sample
 from mne.io import read_raw_fif
 from mne.utils import (run_tests_if_main, _TempDir, requires_mne, requires_PIL,
                        requires_mayavi, requires_tvtk, requires_freesurfer,
-                       ArgvSetter, slow_test, ultra_slow_test)
+                       ArgvSetter)
 
 
 base_dir = op.join(op.dirname(__file__), '..', '..', 'io', 'tests', 'data')
@@ -40,7 +42,7 @@ def check_usage(module, force_help=False):
         assert_true('Usage: ' in out.stdout.getvalue())
 
 
- at slow_test
+ at pytest.mark.slowtest
 def test_browse_raw():
     """Test mne browse_raw."""
     check_usage(mne_browse_raw)
@@ -61,6 +63,8 @@ def test_show_fiff():
     check_usage(mne_show_fiff)
     with ArgvSetter((raw_fname,)):
         mne_show_fiff.run()
+    with ArgvSetter((raw_fname, '--tag=102')):
+        mne_show_fiff.run()
 
 
 @requires_mne
@@ -68,7 +72,7 @@ def test_clean_eog_ecg():
     """Test mne clean_eog_ecg."""
     check_usage(mne_clean_eog_ecg)
     tempdir = _TempDir()
-    raw = concatenate_raws([read_raw_fif(f, add_eeg_ref=False)
+    raw = concatenate_raws([read_raw_fif(f)
                             for f in [raw_fname, raw_fname, raw_fname]])
     raw.info['bads'] = ['MEG 2443']
     use_fname = op.join(tempdir, op.basename(raw_fname))
@@ -81,7 +85,7 @@ def test_clean_eog_ecg():
     assert_true(len(fnames) == 3)  # raw plus two projs
 
 
- at slow_test
+ at pytest.mark.slowtest
 def test_compute_proj_ecg_eog():
     """Test mne compute_proj_ecg/eog."""
     for fun in (mne_compute_proj_ecg, mne_compute_proj_eog):
@@ -94,7 +98,8 @@ def test_compute_proj_ecg_eog():
         shutil.copyfile(raw_fname, use_fname)
         with ArgvSetter(('-i', use_fname, '--bad=' + bad_fname,
                          '--rej-eeg', '150')):
-            fun.run()
+            with warnings.catch_warnings(record=True):  # too few samples
+                fun.run()
         fnames = glob.glob(op.join(tempdir, '*proj.fif'))
         assert_true(len(fnames) == 1)
         fnames = glob.glob(op.join(tempdir, '*-eve.fif'))
@@ -113,7 +118,6 @@ def test_kit2fiff():
 
 
 @requires_tvtk
- at requires_mne
 @testing.requires_testing_data
 def test_make_scalp_surfaces():
     """Test mne make_scalp_surfaces."""
@@ -129,27 +133,33 @@ def test_make_scalp_surfaces():
     shutil.copy(op.join(surf_path, 'lh.seghead'), surf_path_new)
 
     orig_fs = os.getenv('FREESURFER_HOME', None)
-    orig_mne = os.getenv('MNE_ROOT')
     if orig_fs is not None:
         del os.environ['FREESURFER_HOME']
     cmd = ('-s', 'sample', '--subjects-dir', tempdir)
     os.environ['_MNE_TESTING_SCALP'] = 'true'
+    dense_fname = op.join(subj_dir, 'sample-head-dense.fif')
+    medium_fname = op.join(subj_dir, 'sample-head-medium.fif')
     try:
         with ArgvSetter(cmd, disable_stdout=False, disable_stderr=False):
             assert_raises(RuntimeError, mne_make_scalp_surfaces.run)
-            os.environ['FREESURFER_HOME'] = tempdir  # don't need it
-            del os.environ['MNE_ROOT']
-            assert_raises(RuntimeError, mne_make_scalp_surfaces.run)
-            os.environ['MNE_ROOT'] = orig_mne
+            os.environ['FREESURFER_HOME'] = tempdir  # don't actually use it
             mne_make_scalp_surfaces.run()
-            assert_true(op.isfile(op.join(subj_dir, 'sample-head-dense.fif')))
-            assert_true(op.isfile(op.join(subj_dir, 'sample-head-medium.fif')))
+            assert_true(op.isfile(dense_fname))
+            assert_true(op.isfile(medium_fname))
             assert_raises(IOError, mne_make_scalp_surfaces.run)  # no overwrite
     finally:
         if orig_fs is not None:
             os.environ['FREESURFER_HOME'] = orig_fs
-        os.environ['MNE_ROOT'] = orig_mne
+        else:
+            del os.environ['FREESURFER_HOME']
         del os.environ['_MNE_TESTING_SCALP']
+    # actually check the outputs
+    head_py = read_bem_surfaces(dense_fname)
+    assert_equal(len(head_py), 1)
+    head_py = head_py[0]
+    head_c = read_bem_surfaces(op.join(subjects_dir, 'sample', 'bem',
+                                       'sample-head-dense.fif'))[0]
+    assert_allclose(head_py['rr'], head_c['rr'])
 
 
 def test_maxfilter():
@@ -169,7 +179,7 @@ def test_maxfilter():
             assert_true(check in out.stdout.getvalue(), check)
 
 
- at slow_test
+ at pytest.mark.slowtest
 @requires_mayavi
 @requires_PIL
 @testing.requires_testing_data
@@ -191,7 +201,8 @@ def test_surf2bem():
     check_usage(mne_surf2bem)
 
 
- at ultra_slow_test
+ at pytest.mark.slowtest
+ at pytest.mark.ultraslowtest
 @requires_freesurfer
 @testing.requires_testing_data
 def test_watershed_bem():
@@ -215,7 +226,8 @@ def test_watershed_bem():
         mne_watershed_bem.run()
 
 
- at ultra_slow_test
+ at pytest.mark.slowtest
+ at pytest.mark.ultraslowtest
 @requires_freesurfer
 @sample.requires_sample_data
 def test_flash_bem():
diff --git a/mne/commands/utils.py b/mne/commands/utils.py
index 2957300..00df392 100644
--- a/mne/commands/utils.py
+++ b/mne/commands/utils.py
@@ -1,11 +1,10 @@
-"""Some utility functions for commands (e.g. for cmdline handling)
-"""
+"""Some utility functions for commands (e.g. for cmdline handling)."""
 
 # Authors: Yaroslav Halchenko <debian at onerussian.com>
 #
 # License: BSD (3-clause)
 
-import imp
+import sys
 import os
 import re
 from optparse import OptionParser
@@ -13,9 +12,44 @@ from optparse import OptionParser
 import mne
 
 
-def get_optparser(cmdpath, usage=None):
-    """Create OptionParser with cmd source specific settings (e.g. prog value)
+def load_module(name, path):
+    """Load module from .py/.pyc file.
+
+    Parameters
+    ----------
+    name : str
+        Name of the module.
+    path : str
+        Path to .py/.pyc file.
+
+    Returns
+    -------
+    mod : module
+        Imported module.
     """
+    if sys.version_info < (3, 3):
+        import imp
+        if path.endswith('.pyc'):
+            return imp.load_compiled(name, path)
+        else:
+            return imp.load_source(name, path)
+    elif sys.version_info < (3, 5):
+        if path.endswith('.pyc'):
+            from importlib.machinery import SourcelessFileLoader
+            return SourcelessFileLoader(name, path).load_module()
+        else:
+            from importlib.machinery import SourceFileLoader
+            return SourceFileLoader(name, path).load_module()
+    else:  # Python 3.5 or greater
+        from importlib.util import spec_from_file_location, module_from_spec
+        spec = spec_from_file_location(name, path)
+        mod = module_from_spec(spec)
+        spec.loader.exec_module(mod)
+        return mod
+
+
+def get_optparser(cmdpath, usage=None):
+    """Create OptionParser with cmd specific settings (e.g. prog value)."""
     command = os.path.basename(cmdpath)
     if re.match('mne_(.*).py', command):
         command = command[4:-3]
@@ -23,10 +57,7 @@ def get_optparser(cmdpath, usage=None):
         command = command[4:-4]
 
     # Fetch description
-    if cmdpath.endswith('.pyc'):
-        mod = imp.load_compiled('__temp', cmdpath)
-    else:
-        mod = imp.load_source('__temp', cmdpath)
+    mod = load_module('__temp', cmdpath)
     if mod.__doc__:
         doc, description, epilog = mod.__doc__, None, None
 
diff --git a/mne/connectivity/__init__.py b/mne/connectivity/__init__.py
index 1495fb9..69f51d3 100644
--- a/mne/connectivity/__init__.py
+++ b/mne/connectivity/__init__.py
@@ -1,5 +1,4 @@
-""" Connectivity Analysis Tools
-"""
+"""Spectral and effective connectivity measures."""
 
 from .utils import seed_target_indices
 from .spectral import spectral_connectivity
diff --git a/mne/connectivity/effective.py b/mne/connectivity/effective.py
index 636661b..8861155 100644
--- a/mne/connectivity/effective.py
+++ b/mne/connectivity/effective.py
@@ -6,7 +6,7 @@ import copy
 
 import numpy as np
 
-from ..utils import logger, verbose
+from ..utils import logger, verbose, _freqs_dep
 from .spectral import spectral_connectivity
 
 
@@ -15,10 +15,9 @@ def phase_slope_index(data, indices=None, sfreq=2 * np.pi,
                       mode='multitaper', fmin=None, fmax=np.inf,
                       tmin=None, tmax=None, mt_bandwidth=None,
                       mt_adaptive=False, mt_low_bias=True,
-                      cwt_frequencies=None, cwt_n_cycles=7, block_size=1000,
-                      n_jobs=1, verbose=None):
-    """
-    Compute the Phase Slope Index (PSI) connectivity measure
+                      cwt_freqs=None, cwt_n_cycles=7, block_size=1000,
+                      n_jobs=1, cwt_frequencies=None, verbose=None):
+    """Compute the Phase Slope Index (PSI) connectivity measure.
 
     The PSI is an effective connectivity measure, i.e., a measure which can
     give an indication of the direction of the information flow (causality).
@@ -79,7 +78,7 @@ def phase_slope_index(data, indices=None, sfreq=2 * np.pi,
     mt_low_bias : bool
         Only use tapers with more than 90% spectral concentration within
         bandwidth. Only used in 'multitaper' mode.
-    cwt_frequencies : array
+    cwt_freqs : array
         Array of frequencies of interest. Only used in 'cwt_morlet' mode.
     cwt_n_cycles: float | array of float
         Number of cycles. Fixed number or one per frequency. Only used in
@@ -90,7 +89,8 @@ def phase_slope_index(data, indices=None, sfreq=2 * np.pi,
     n_jobs : int
         How many epochs to process in parallel.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -112,13 +112,14 @@ def phase_slope_index(data, indices=None, sfreq=2 * np.pi,
         The number of DPSS tapers used. Only defined in 'multitaper' mode.
         Otherwise None is returned.
     """
+    cwt_freqs = _freqs_dep(cwt_freqs, cwt_frequencies, 'cwt_')
     logger.info('Estimating phase slope index (PSI)')
     # estimate the coherency
     cohy, freqs_, times, n_epochs, n_tapers = spectral_connectivity(
         data, method='cohy', indices=indices, sfreq=sfreq, mode=mode,
         fmin=fmin, fmax=fmax, fskip=0, faverage=False, tmin=tmin, tmax=tmax,
         mt_bandwidth=mt_bandwidth, mt_adaptive=mt_adaptive,
-        mt_low_bias=mt_low_bias, cwt_frequencies=cwt_frequencies,
+        mt_low_bias=mt_low_bias, cwt_freqs=cwt_freqs,
         cwt_n_cycles=cwt_n_cycles, block_size=block_size, n_jobs=n_jobs,
         verbose=verbose)
 
diff --git a/mne/connectivity/spectral.py b/mne/connectivity/spectral.py
index 5f83225..163f6aa 100644
--- a/mne/connectivity/spectral.py
+++ b/mne/connectivity/spectral.py
@@ -1,4 +1,5 @@
 # Authors: Martin Luessi <mluessi at nmr.mgh.harvard.edu>
+#          Denis A. Engemann <denis.engemann at gmail.com>
 #
 # License: BSD (3-clause)
 
@@ -6,18 +7,17 @@ from functools import partial
 from inspect import getmembers
 
 import numpy as np
-from scipy.fftpack import fftfreq
 
 from .utils import check_indices
 from ..fixes import _get_args
 from ..parallel import parallel_func
 from ..source_estimate import _BaseSourceEstimate
-from ..epochs import _BaseEpochs
+from ..epochs import BaseEpochs
 from ..time_frequency.multitaper import (dpss_windows, _mt_spectra,
                                          _psd_from_mt, _csd_from_mt,
                                          _psd_from_mt_adaptive)
 from ..time_frequency.tfr import morlet, cwt
-from ..utils import logger, verbose, _time_mask, warn
+from ..utils import logger, verbose, _time_mask, warn, _freqs_dep
 from ..externals.six import string_types
 
 ########################################################################
@@ -25,24 +25,24 @@ from ..externals.six import string_types
 
 
 class _AbstractConEstBase(object):
-    """Abstract base class for all connectivity estimators, specifies
-       the interface but doesn't do anything"""
+    """ABC for connectivity estimators."""
 
     def start_epoch(self):
-        raise RuntimeError('start_epoch method not implemented')
+        raise NotImplementedError('start_epoch method not implemented')
 
     def accumulate(self, con_idx, csd_xy):
-        raise RuntimeError('accumulate method not implemented')
+        raise NotImplementedError('accumulate method not implemented')
 
     def combine(self, other):
-        raise RuntimeError('combine method not implemented')
+        raise NotImplementedError('combine method not implemented')
 
     def compute_con(self, con_idx, n_epochs):
-        raise RuntimeError('compute_con method not implemented')
+        raise NotImplementedError('compute_con method not implemented')
 
 
 class _EpochMeanConEstBase(_AbstractConEstBase):
-    """Base class for methods that estimate connectivity as mean over epochs"""
+    """Base class for methods that estimate connectivity as mean epoch-wise."""
+
     def __init__(self, n_cons, n_freqs, n_times):
         self.n_cons = n_cons
         self.n_freqs = n_freqs
@@ -55,17 +55,18 @@ class _EpochMeanConEstBase(_AbstractConEstBase):
 
         self.con_scores = None
 
-    def start_epoch(self):
-        """This method is called at the start of each epoch"""
+    def start_epoch(self):  # noqa: D401
+        """Called at the start of each epoch."""
         pass  # for this type of con. method we don't do anything
 
     def combine(self, other):
-        """Include con. accumated for some epochs in this estimate"""
+        """Include con. accumated for some epochs in this estimate."""
         self._acc += other._acc
 
 
 class _CohEstBase(_EpochMeanConEstBase):
-    """Base Estimator for Coherence, Coherency, Imag. Coherence"""
+    """Base Estimator for Coherence, Coherency, Imag. Coherence."""
+
     def __init__(self, n_cons, n_freqs, n_times):
         super(_CohEstBase, self).__init__(n_cons, n_freqs, n_times)
 
@@ -73,16 +74,17 @@ class _CohEstBase(_EpochMeanConEstBase):
         self._acc = np.zeros(self.csd_shape, dtype=np.complex128)
 
     def accumulate(self, con_idx, csd_xy):
-        """Accumulate CSD for some connections"""
+        """Accumulate CSD for some connections."""
         self._acc[con_idx] += csd_xy
 
 
 class _CohEst(_CohEstBase):
-    """Coherence Estimator"""
+    """Coherence Estimator."""
+
     name = 'Coherence'
 
     def compute_con(self, con_idx, n_epochs, psd_xx, psd_yy):
-        """Compute final con. score for some connections"""
+        """Compute final con. score for some connections."""
         if self.con_scores is None:
             self.con_scores = np.zeros(self.csd_shape)
         csd_mean = self._acc[con_idx] / n_epochs
@@ -90,11 +92,12 @@ class _CohEst(_CohEstBase):
 
 
 class _CohyEst(_CohEstBase):
-    """Coherency Estimator"""
+    """Coherency Estimator."""
+
     name = 'Coherency'
 
     def compute_con(self, con_idx, n_epochs, psd_xx, psd_yy):
-        """Compute final con. score for some connections"""
+        """Compute final con. score for some connections."""
         if self.con_scores is None:
             self.con_scores = np.zeros(self.csd_shape,
                                        dtype=np.complex128)
@@ -103,11 +106,12 @@ class _CohyEst(_CohEstBase):
 
 
 class _ImCohEst(_CohEstBase):
-    """Imaginary Coherence Estimator"""
+    """Imaginary Coherence Estimator."""
+
     name = 'Imaginary Coherence'
 
     def compute_con(self, con_idx, n_epochs, psd_xx, psd_yy):
-        """Compute final con. score for some connections"""
+        """Compute final con. score for some connections."""
         if self.con_scores is None:
             self.con_scores = np.zeros(self.csd_shape)
         csd_mean = self._acc[con_idx] / n_epochs
@@ -115,7 +119,8 @@ class _ImCohEst(_CohEstBase):
 
 
 class _PLVEst(_EpochMeanConEstBase):
-    """PLV Estimator"""
+    """PLV Estimator."""
+
     name = 'PLV'
 
     def __init__(self, n_cons, n_freqs, n_times):
@@ -125,11 +130,11 @@ class _PLVEst(_EpochMeanConEstBase):
         self._acc = np.zeros(self.csd_shape, dtype=np.complex128)
 
     def accumulate(self, con_idx, csd_xy):
-        """Accumulate some connections"""
+        """Accumulate some connections."""
         self._acc[con_idx] += csd_xy / np.abs(csd_xy)
 
     def compute_con(self, con_idx, n_epochs):
-        """Compute final con. score for some connections"""
+        """Compute final con. score for some connections."""
         if self.con_scores is None:
             self.con_scores = np.zeros(self.csd_shape)
         plv = np.abs(self._acc / n_epochs)
@@ -137,7 +142,8 @@ class _PLVEst(_EpochMeanConEstBase):
 
 
 class _PLIEst(_EpochMeanConEstBase):
-    """PLI Estimator"""
+    """PLI Estimator."""
+
     name = 'PLI'
 
     def __init__(self, n_cons, n_freqs, n_times):
@@ -147,11 +153,11 @@ class _PLIEst(_EpochMeanConEstBase):
         self._acc = np.zeros(self.csd_shape)
 
     def accumulate(self, con_idx, csd_xy):
-        """Accumulate some connections"""
+        """Accumulate some connections."""
         self._acc[con_idx] += np.sign(np.imag(csd_xy))
 
     def compute_con(self, con_idx, n_epochs):
-        """Compute final con. score for some connections"""
+        """Compute final con. score for some connections."""
         if self.con_scores is None:
             self.con_scores = np.zeros(self.csd_shape)
         pli_mean = self._acc[con_idx] / n_epochs
@@ -159,11 +165,12 @@ class _PLIEst(_EpochMeanConEstBase):
 
 
 class _PLIUnbiasedEst(_PLIEst):
-    """Unbiased PLI Square Estimator"""
+    """Unbiased PLI Square Estimator."""
+
     name = 'Unbiased PLI Square'
 
     def compute_con(self, con_idx, n_epochs):
-        """Compute final con. score for some connections"""
+        """Compute final con. score for some connections."""
         if self.con_scores is None:
             self.con_scores = np.zeros(self.csd_shape)
         pli_mean = self._acc[con_idx] / n_epochs
@@ -175,7 +182,8 @@ class _PLIUnbiasedEst(_PLIEst):
 
 
 class _WPLIEst(_EpochMeanConEstBase):
-    """WPLI Estimator"""
+    """WPLI Estimator."""
+
     name = 'WPLI'
 
     def __init__(self, n_cons, n_freqs, n_times):
@@ -186,13 +194,13 @@ class _WPLIEst(_EpochMeanConEstBase):
         self._acc = np.zeros(acc_shape)
 
     def accumulate(self, con_idx, csd_xy):
-        """Accumulate some connections"""
+        """Accumulate some connections."""
         im_csd = np.imag(csd_xy)
         self._acc[0, con_idx] += im_csd
         self._acc[1, con_idx] += np.abs(im_csd)
 
     def compute_con(self, con_idx, n_epochs):
-        """Compute final con. score for some connections"""
+        """Compute final con. score for some connections."""
         if self.con_scores is None:
             self.con_scores = np.zeros(self.csd_shape)
 
@@ -212,7 +220,8 @@ class _WPLIEst(_EpochMeanConEstBase):
 
 
 class _WPLIDebiasedEst(_EpochMeanConEstBase):
-    """Debiased WPLI Square Estimator"""
+    """Debiased WPLI Square Estimator."""
+
     name = 'Debiased WPLI Square'
 
     def __init__(self, n_cons, n_freqs, n_times):
@@ -222,14 +231,14 @@ class _WPLIDebiasedEst(_EpochMeanConEstBase):
         self._acc = np.zeros(acc_shape)
 
     def accumulate(self, con_idx, csd_xy):
-        """Accumulate some connections"""
+        """Accumulate some connections."""
         im_csd = np.imag(csd_xy)
         self._acc[0, con_idx] += im_csd
         self._acc[1, con_idx] += np.abs(im_csd)
         self._acc[2, con_idx] += im_csd ** 2
 
     def compute_con(self, con_idx, n_epochs):
-        """Compute final con. score for some connections"""
+        """Compute final con. score for some connections."""
         if self.con_scores is None:
             self.con_scores = np.zeros(self.csd_shape)
 
@@ -254,7 +263,8 @@ class _WPLIDebiasedEst(_EpochMeanConEstBase):
 
 
 class _PPCEst(_EpochMeanConEstBase):
-    """Pairwise Phase Consistency (PPC) Estimator"""
+    """Pairwise Phase Consistency (PPC) Estimator."""
+
     name = 'PPC'
 
     def __init__(self, n_cons, n_freqs, n_times):
@@ -264,7 +274,7 @@ class _PPCEst(_EpochMeanConEstBase):
         self._acc = np.zeros(self.csd_shape, dtype=np.complex128)
 
     def accumulate(self, con_idx, csd_xy):
-        """Accumulate some connections"""
+        """Accumulate some connections."""
         denom = np.abs(csd_xy)
         z_denom = np.where(denom == 0.)
         denom[z_denom] = 1.
@@ -274,7 +284,7 @@ class _PPCEst(_EpochMeanConEstBase):
         self._acc[con_idx] += this_acc
 
     def compute_con(self, con_idx, n_epochs):
-        """Compute final con. score for some connections"""
+        """Compute final con. score for some connections."""
         if self.con_scores is None:
             self.con_scores = np.zeros(self.csd_shape)
 
@@ -293,8 +303,7 @@ def _epoch_spectral_connectivity(data, sig_idx, tmin_idx, tmax_idx, sfreq,
                                  psd, accumulate_psd, con_method_types,
                                  con_methods, n_signals, n_times,
                                  accumulate_inplace=True):
-    """Connectivity estimation for one epoch see spectral_connectivity"""
-
+    """Estimate connectivity for one epoch (see spectral_connectivity)."""
     n_cons = len(idx_map[0])
 
     if wavelets is not None:
@@ -314,7 +323,7 @@ def _epoch_spectral_connectivity(data, sig_idx, tmin_idx, tmax_idx, sfreq,
         sig_idx = slice(None, None)
 
     # compute tapered spectra
-    if mode in ['multitaper', 'fourier']:
+    if mode in ('multitaper', 'fourier'):
         x_mt = list()
         this_psd = list()
         sig_pos_start = 0
@@ -433,34 +442,38 @@ def _epoch_spectral_connectivity(data, sig_idx, tmin_idx, tmax_idx, sfreq,
 
             for method in con_methods:
                 method.accumulate(con_idx, csd)
-    else:
-        # cwt_morlet mode
-        for i in range(0, n_cons, block_size):
+    elif mode in ('cwt_morlet',):  # reminder to add alternative TFR methods
+        for i_block, i in enumerate(range(0, n_cons, block_size)):
             con_idx = slice(i, i + block_size)
+            # this codes can be very slow
+            csd = (x_cwt[idx_map[0][con_idx]] *
+                   x_cwt[idx_map[1][con_idx]].conjugate())
 
-            csd = x_cwt[idx_map[0][con_idx]] * \
-                np.conjugate(x_cwt[idx_map[1][con_idx]])
             for method in con_methods:
                 method.accumulate(con_idx, csd)
+                # future estimator types need to be explicitly handled here
+    else:
+        raise RuntimeError('This should never happen')
 
     return con_methods, psd
 
 
 def _get_n_epochs(epochs, n):
-    """Generator that returns lists with at most n epochs"""
-    epochs_out = []
-    for e in epochs:
-        if not isinstance(e, (list, tuple)):
-            e = (e,)
-        epochs_out.append(e)
+    """Generate lists with at most n epochs."""
+    epochs_out = list()
+    for epoch in epochs:
+        if not isinstance(epoch, (list, tuple)):
+            epoch = (epoch,)
+        epochs_out.append(epoch)
         if len(epochs_out) >= n:
             yield epochs_out
-            epochs_out = []
-    yield epochs_out
+            epochs_out = list()
+    if 0 < len(epochs_out) < n:
+        yield epochs_out
 
 
 def _check_method(method):
-    """Test if a method implements the required interface"""
+    """Test if a method implements the required interface."""
     interface_members = [m[0] for m in getmembers(_AbstractConEstBase)
                          if not m[0].startswith('_')]
     method_members = [m[0] for m in getmembers(method)
@@ -473,7 +486,7 @@ def _check_method(method):
 
 
 def _get_and_verify_data_sizes(data, n_signals=None, n_times=None, times=None):
-    """Helper function to get and/or verify the data sizes and time scales"""
+    """Get and/or verify the data sizes and time scales."""
     if not isinstance(data, (list, tuple)):
         raise ValueError('data has to be a list or tuple')
     n_signals_tot = 0
@@ -511,15 +524,46 @@ _CON_METHOD_MAP = {'coh': _CohEst, 'cohy': _CohyEst, 'imcoh': _ImCohEst,
                    'wpli2_debiased': _WPLIDebiasedEst}
 
 
+def _check_estimators(method, mode):
+    """Check constrution of connectivity estimators."""
+    n_methods = len(method)
+    con_method_types = list()
+    for this_method in method:
+        if this_method in _CON_METHOD_MAP:
+            con_method_types.append(_CON_METHOD_MAP[this_method])
+        elif isinstance(this_method, string_types):
+            raise ValueError('%s is not a valid connectivity method' %
+                             this_method)
+        else:
+            # support for custom class
+            method_valid, msg = _check_method(this_method)
+            if not method_valid:
+                raise ValueError('The supplied connectivity method does '
+                                 'not have the method %s' % msg)
+            con_method_types.append(this_method)
+
+    # determine how many arguments the compute_con_function needs
+    n_comp_args = [len(_get_args(mtype.compute_con))
+                   for mtype in con_method_types]
+
+    # we currently only support 3 arguments
+    if any(n not in (3, 5) for n in n_comp_args):
+        raise ValueError('The .compute_con method needs to have either '
+                         '3 or 5 arguments')
+    # if none of the comp_con functions needs the PSD, we don't estimate it
+    accumulate_psd = any(n == 5 for n in n_comp_args)
+    return con_method_types, n_methods, accumulate_psd, n_comp_args
+
+
 @verbose
 def spectral_connectivity(data, method='coh', indices=None, sfreq=2 * np.pi,
                           mode='multitaper', fmin=None, fmax=np.inf,
                           fskip=0, faverage=False, tmin=None, tmax=None,
                           mt_bandwidth=None, mt_adaptive=False,
-                          mt_low_bias=True, cwt_frequencies=None,
+                          mt_low_bias=True, cwt_freqs=None,
                           cwt_n_cycles=7, block_size=1000, n_jobs=1,
-                          verbose=None):
-    """Compute frequency-domain and time-frequency domain connectivity measures
+                          cwt_frequencies=None, verbose=None):
+    """Compute frequency- and time-frequency-domain connectivity measures.
 
     The connectivity method(s) are specified using the "method" parameter.
     All methods are based on estimates of the cross- and power spectral
@@ -591,33 +635,9 @@ def spectral_connectivity(data, method='coh', indices=None, sfreq=2 * np.pi,
             WPLI = ------------------
                       E[|Im(Sxy)|]
 
-        'wpli2_debiased' : Debiased estimator of squared WPLI [5].
+        'wpli2_debiased' : Debiased estimator of squared WPLI [5]_.
 
 
-    References
-    ----------
-
-    .. [1] Nolte et al. "Identifying true brain interaction from EEG data using
-           the imaginary part of coherency" Clinical neurophysiology, vol. 115,
-           no. 10, pp. 2292-2307, Oct. 2004.
-
-    .. [2] Lachaux et al. "Measuring phase synchrony in brain signals" Human
-           brain mapping, vol. 8, no. 4, pp. 194-208, Jan. 1999.
-
-    .. [3] Vinck et al. "The pairwise phase consistency: a bias-free measure of
-           rhythmic neuronal synchronization" NeuroImage, vol. 51, no. 1,
-           pp. 112-122, May 2010.
-
-    .. [4] Stam et al. "Phase lag index: assessment of functional connectivity
-           from multi channel EEG and MEG with diminished bias from common
-           sources" Human brain mapping, vol. 28, no. 11, pp. 1178-1193,
-           Nov. 2007.
-
-    .. [5] Vinck et al. "An improved index of phase-synchronization for
-           electro-physiological data in the presence of volume-conduction,
-           noise and sample-size bias" NeuroImage, vol. 55, no. 4,
-           pp. 1548-1565, Apr. 2011.
-
     Parameters
     ----------
     data : array-like, shape=(n_epochs, n_signals, n_times) | Epochs
@@ -672,7 +692,7 @@ def spectral_connectivity(data, method='coh', indices=None, sfreq=2 * np.pi,
     mt_low_bias : bool
         Only use tapers with more than 90% spectral concentration within
         bandwidth. Only used in 'multitaper' mode.
-    cwt_frequencies : array
+    cwt_freqs : array
         Array of frequencies of interest. Only used in 'cwt_morlet' mode.
     cwt_n_cycles: float | array of float
         Number of cycles. Fixed number or one per frequency. Only used in
@@ -683,17 +703,18 @@ def spectral_connectivity(data, method='coh', indices=None, sfreq=2 * np.pi,
     n_jobs : int
         How many epochs to process in parallel.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
     con : array | list of arrays
         Computed connectivity measure(s). The shape of each array is either
-        (n_signals, n_signals, n_frequencies) mode: 'multitaper' or 'fourier'
-        (n_signals, n_signals, n_frequencies, n_times) mode: 'cwt_morlet'
+        (n_signals, n_signals, n_freqs) mode: 'multitaper' or 'fourier'
+        (n_signals, n_signals, n_freqs, n_times) mode: 'cwt_morlet'
         when "indices" is None, or
-        (n_con, n_frequencies) mode: 'multitaper' or 'fourier'
-        (n_con, n_frequencies, n_times) mode: 'cwt_morlet'
+        (n_con, n_freqs) mode: 'multitaper' or 'fourier'
+        (n_con, n_freqs, n_times) mode: 'cwt_morlet'
         when "indices" is specified and "n_con = len(indices[0])".
     freqs : array
         Frequency points at which the connectivity was computed.
@@ -704,7 +725,27 @@ def spectral_connectivity(data, method='coh', indices=None, sfreq=2 * np.pi,
     n_tapers : int
         The number of DPSS tapers used. Only defined in 'multitaper' mode.
         Otherwise None is returned.
+
+    References
+    ----------
+    .. [1] Nolte et al. "Identifying true brain interaction from EEG data using
+           the imaginary part of coherency" Clinical neurophysiology, vol. 115,
+           no. 10, pp. 2292-2307, Oct. 2004.
+    .. [2] Lachaux et al. "Measuring phase synchrony in brain signals" Human
+           brain mapping, vol. 8, no. 4, pp. 194-208, Jan. 1999.
+    .. [3] Vinck et al. "The pairwise phase consistency: a bias-free measure of
+           rhythmic neuronal synchronization" NeuroImage, vol. 51, no. 1,
+           pp. 112-122, May 2010.
+    .. [4] Stam et al. "Phase lag index: assessment of functional connectivity
+           from multi channel EEG and MEG with diminished bias from common
+           sources" Human brain mapping, vol. 28, no. 11, pp. 1178-1193,
+           Nov. 2007.
+    .. [5] Vinck et al. "An improved index of phase-synchronization for
+           electro-physiological data in the presence of volume-conduction,
+           noise and sample-size bias" NeuroImage, vol. 55, no. 4,
+           pp. 1548-1565, Apr. 2011.
     """
+    cwt_freqs = _freqs_dep(cwt_freqs, cwt_frequencies, 'cwt_')
     if n_jobs != 1:
         parallel, my_epoch_spectral_connectivity, _ = \
             parallel_func(_epoch_spectral_connectivity, n_jobs,
@@ -727,35 +768,11 @@ def spectral_connectivity(data, method='coh', indices=None, sfreq=2 * np.pi,
     if not isinstance(method, (list, tuple)):
         method = [method]  # make it a list so we can iterate over it
 
-    n_methods = len(method)
-    con_method_types = []
-    for m in method:
-        if m in _CON_METHOD_MAP:
-            method = _CON_METHOD_MAP[m]
-            con_method_types.append(method)
-        elif isinstance(m, string_types):
-            raise ValueError('%s is not a valid connectivity method' % m)
-        else:
-            # add custom method
-            method_valid, msg = _check_method(m)
-            if not method_valid:
-                raise ValueError('The supplied connectivity method does '
-                                 'not have the method %s' % msg)
-            con_method_types.append(m)
+    # handle connectivity estimators
+    (con_method_types, n_methods, accumulate_psd,
+     n_comp_args) = _check_estimators(method=method, mode=mode)
 
-    # determine how many arguments the compute_con_function needs
-    n_comp_args = [len(_get_args(mtype.compute_con))
-                   for mtype in con_method_types]
-
-    # we only support 3 or 5 arguments
-    if any(n not in (3, 5) for n in n_comp_args):
-        raise ValueError('The compute_con function needs to have either '
-                         '3 or 5 arguments')
-
-    # if none of the comp_con functions needs the PSD, we don't estimate it
-    accumulate_psd = any(n == 5 for n in n_comp_args)
-
-    if isinstance(data, _BaseEpochs):
+    if isinstance(data, BaseEpochs):
         times_in = data.times  # input times for Epochs input type
         sfreq = data.info['sfreq']
 
@@ -764,175 +781,23 @@ def spectral_connectivity(data, method='coh', indices=None, sfreq=2 * np.pi,
     epoch_idx = 0
     logger.info('Connectivity computation...')
     for epoch_block in _get_n_epochs(data, n_jobs):
-
         if epoch_idx == 0:
-            # initialize everything
-            first_epoch = epoch_block[0]
-
-            # get the data size and time scale
-            n_signals, n_times_in, times_in = \
-                _get_and_verify_data_sizes(first_epoch)
-
-            if times_in is None:
-                # we are not using Epochs or SourceEstimate(s) as input
-                times_in = np.linspace(0.0, n_times_in / sfreq, n_times_in,
-                                       endpoint=False)
-
-            n_times_in = len(times_in)
-            mask = _time_mask(times_in, tmin, tmax, sfreq=sfreq)
-            tmin_idx, tmax_idx = np.where(mask)[0][[0, -1]]
-            tmax_idx += 1
-            tmin_true = times_in[tmin_idx]
-            tmax_true = times_in[tmax_idx - 1]  # time of last point used
-
-            times = times_in[tmin_idx:tmax_idx]
-            n_times = len(times)
-
-            if indices is None:
-                # only compute r for lower-triangular region
-                indices_use = np.tril_indices(n_signals, -1)
-            else:
-                indices_use = check_indices(indices)
-
-            # number of connectivities to compute
-            n_cons = len(indices_use[0])
-
-            logger.info('    computing connectivity for %d connections'
-                        % n_cons)
-
-            logger.info('    using t=%0.3fs..%0.3fs for estimation (%d points)'
-                        % (tmin_true, tmax_true, n_times))
-
-            # get frequencies of interest for the different modes
-            if mode in ['multitaper', 'fourier']:
-                # fmin fmax etc is only supported for these modes
-                # decide which frequencies to keep
-                freqs_all = fftfreq(n_times, 1. / sfreq)
-                freqs_all = freqs_all[freqs_all >= 0]
-            elif mode == 'cwt_morlet':
-                # cwt_morlet mode
-                if cwt_frequencies is None:
-                    raise ValueError('define frequencies of interest using '
-                                     'cwt_frequencies')
-                else:
-                    cwt_frequencies = cwt_frequencies.astype(np.float)
-                if any(cwt_frequencies > (sfreq / 2.)):
-                    raise ValueError('entries in cwt_frequencies cannot be '
-                                     'larger than Nyquist (sfreq / 2)')
-                freqs_all = cwt_frequencies
-            else:
-                raise ValueError('mode has an invalid value')
-
-            # check that fmin corresponds to at least 5 cycles
-            five_cycle_freq = 5. * sfreq / float(n_times)
-
-            if len(fmin) == 1 and fmin[0] == -np.inf:
-                # we use the 5 cycle freq. as default
-                fmin = [five_cycle_freq]
-            else:
-                if any(fmin < five_cycle_freq):
-                    warn('fmin corresponds to less than 5 cycles, '
-                         'spectrum estimate will be unreliable')
-
-            # create a frequency mask for all bands
-            freq_mask = np.zeros(len(freqs_all), dtype=np.bool)
-            for f_lower, f_upper in zip(fmin, fmax):
-                freq_mask |= ((freqs_all >= f_lower) & (freqs_all <= f_upper))
-
-            # possibly skip frequency points
-            for pos in range(fskip):
-                freq_mask[pos + 1::fskip + 1] = False
-
-            # the frequency points where we compute connectivity
-            freqs = freqs_all[freq_mask]
-            n_freqs = len(freqs)
-
-            # get the freq. indices and points for each band
-            freq_idx_bands = [np.where((freqs >= fl) & (freqs <= fu))[0]
-                              for fl, fu in zip(fmin, fmax)]
-            freqs_bands = [freqs[freq_idx] for freq_idx in freq_idx_bands]
-
-            # make sure we don't have empty bands
-            for i, n_f_band in enumerate([len(f) for f in freqs_bands]):
-                if n_f_band == 0:
-                    raise ValueError('There are no frequency points between '
-                                     '%0.1fHz and %0.1fHz. Change the band '
-                                     'specification (fmin, fmax) or the '
-                                     'frequency resolution.'
-                                     % (fmin[i], fmax[i]))
-
-            if n_bands == 1:
-                logger.info('    frequencies: %0.1fHz..%0.1fHz (%d points)'
-                            % (freqs_bands[0][0], freqs_bands[0][-1],
-                               n_freqs))
-            else:
-                logger.info('    computing connectivity for the bands:')
-                for i, bfreqs in enumerate(freqs_bands):
-                    logger.info('     band %d: %0.1fHz..%0.1fHz '
-                                '(%d points)' % (i + 1, bfreqs[0],
-                                                 bfreqs[-1], len(bfreqs)))
-
-            if faverage:
-                logger.info('    connectivity scores will be averaged for '
-                            'each band')
+            # initialize everything times and frequencies
+            (n_cons, times, n_times, times_in, n_times_in, tmin_idx,
+             tmax_idx, n_freqs, freq_mask, freqs, freqs_bands, freq_idx_bands,
+             n_signals, indices_use) = _prepare_connectivity(
+                epoch_block=epoch_block, tmin=tmin, tmax=tmax, fmin=fmin,
+                fmax=fmax, sfreq=sfreq, indices=indices, mode=mode,
+                fskip=fskip, n_bands=n_bands,
+                cwt_freqs=cwt_freqs, faverage=faverage)
 
             # get the window function, wavelets, etc for different modes
-            if mode == 'multitaper':
-                # compute standardized half-bandwidth
-                if mt_bandwidth is not None:
-                    half_nbw = float(mt_bandwidth) * n_times / (2 * sfreq)
-                else:
-                    half_nbw = 4
-
-                # compute dpss windows
-                n_tapers_max = int(2 * half_nbw)
-                window_fun, eigvals = dpss_windows(n_times, half_nbw,
-                                                   n_tapers_max,
-                                                   low_bias=mt_low_bias)
-                n_tapers = len(eigvals)
-                logger.info('    using multitaper spectrum estimation with '
-                            '%d DPSS windows' % n_tapers)
-
-                if mt_adaptive and len(eigvals) < 3:
-                    warn('Not adaptively combining the spectral estimators '
-                         'due to a low number of tapers.')
-                    mt_adaptive = False
-
-                n_times_spectrum = 0  # this method only uses the freq. domain
-                wavelets = None
-            elif mode == 'fourier':
-                logger.info('    using FFT with a Hanning window to estimate '
-                            'spectra')
-
-                window_fun = np.hanning(n_times)
-                mt_adaptive = False
-                eigvals = 1.
-                n_tapers = None
-                n_times_spectrum = 0  # this method only uses the freq. domain
-                wavelets = None
-            elif mode == 'cwt_morlet':
-                logger.info('    using CWT with Morlet wavelets to estimate '
-                            'spectra')
-
-                # reformat cwt_n_cycles if we have removed some frequencies
-                # using fmin, fmax, fskip
-                cwt_n_cycles = np.asarray((cwt_n_cycles,)).ravel()
-                if len(cwt_n_cycles) > 1:
-                    if len(cwt_n_cycles) != len(cwt_frequencies):
-                        raise ValueError('cwt_n_cycles must be float or an '
-                                         'array with the same size as '
-                                         'cwt_frequencies')
-                    cwt_n_cycles = cwt_n_cycles[freq_mask]
-
-                # get the Morlet wavelets
-                wavelets = morlet(sfreq, freqs, n_cycles=cwt_n_cycles,
-                                  zero_mean=True)
-                eigvals = None
-                n_tapers = None
-                window_fun = None
-                n_times_spectrum = n_times
-            else:
-                raise ValueError('mode has an invalid value')
+            (spectral_params, mt_adaptive, n_times_spectrum,
+             n_tapers) = _assemble_spectral_params(
+                mode=mode, n_times=n_times, mt_adaptive=mt_adaptive,
+                mt_bandwidth=mt_bandwidth, sfreq=sfreq,
+                mt_low_bias=mt_low_bias, cwt_n_cycles=cwt_n_cycles,
+                cwt_freqs=cwt_freqs, freqs=freqs, freq_mask=freq_mask)
 
             # unique signals for which we actually need to compute PSD etc.
             sig_idx = np.unique(np.r_[indices_use[0], indices_use[1]])
@@ -964,19 +829,25 @@ def spectral_connectivity(data, method='coh', indices=None, sfreq=2 * np.pi,
             _get_and_verify_data_sizes(this_epoch, n_signals, n_times_in,
                                        times_in)
 
+        call_params = dict(
+            sig_idx=sig_idx, tmin_idx=tmin_idx,
+            tmax_idx=tmax_idx, sfreq=sfreq, mode=mode,
+            freq_mask=freq_mask, idx_map=idx_map, block_size=block_size,
+            psd=psd, accumulate_psd=accumulate_psd,
+            mt_adaptive=mt_adaptive,
+            con_method_types=con_method_types,
+            con_methods=con_methods if n_jobs == 1 else None,
+            n_signals=n_signals, n_times=n_times,
+            accumulate_inplace=True if n_jobs == 1 else False)
+        call_params.update(**spectral_params)
+
         if n_jobs == 1:
             # no parallel processing
             for this_epoch in epoch_block:
                 logger.info('    computing connectivity for epoch %d'
                             % (epoch_idx + 1))
-
                 # con methods and psd are updated inplace
-                _epoch_spectral_connectivity(
-                    this_epoch, sig_idx, tmin_idx,
-                    tmax_idx, sfreq, mode, window_fun, eigvals, wavelets,
-                    freq_mask, mt_adaptive, idx_map, block_size, psd,
-                    accumulate_psd, con_method_types, con_methods,
-                    n_signals, n_times, accumulate_inplace=True)
+                _epoch_spectral_connectivity(data=this_epoch, **call_params)
                 epoch_idx += 1
         else:
             # process epochs in parallel
@@ -984,12 +855,8 @@ def spectral_connectivity(data, method='coh', indices=None, sfreq=2 * np.pi,
                         % (epoch_idx + 1, epoch_idx + len(epoch_block)))
 
             out = parallel(my_epoch_spectral_connectivity(
-                this_epoch, sig_idx,
-                tmin_idx, tmax_idx, sfreq, mode, window_fun, eigvals,
-                wavelets, freq_mask, mt_adaptive, idx_map, block_size, psd,
-                accumulate_psd, con_method_types, None, n_signals, n_times,
-                accumulate_inplace=False) for this_epoch in epoch_block)
-
+                           data=this_epoch, **call_params)
+                           for this_epoch in epoch_block)
             # do the accumulation
             for this_out in out:
                 for method, parallel_method in zip(con_methods, this_out[0]):
@@ -1005,18 +872,21 @@ def spectral_connectivity(data, method='coh', indices=None, sfreq=2 * np.pi,
         psd /= n_epochs
 
     # compute final connectivity scores
-    con = []
+    con = list()
     for method, n_args in zip(con_methods, n_comp_args):
+        # future estimators will need to be handled here
         if n_args == 3:
             # compute all scores at once
             method.compute_con(slice(0, n_cons), n_epochs)
-        else:
+        elif n_args == 5:
             # compute scores block-wise to save memory
             for i in range(0, n_cons, block_size):
                 con_idx = slice(i, i + block_size)
                 psd_xx = psd[idx_map[0][con_idx]]
                 psd_yy = psd[idx_map[1][con_idx]]
                 method.compute_con(con_idx, n_epochs, psd_xx, psd_yy)
+        else:
+            raise RuntimeError('This should never happen.')
 
         # get the connectivity scores
         this_con = method.con_scores
@@ -1039,9 +909,10 @@ def spectral_connectivity(data, method='coh', indices=None, sfreq=2 * np.pi,
 
     if indices is None:
         # return all-to-all connectivity matrices
-        logger.info('    assembling connectivity matrix')
+        logger.info('    assembling connectivity matrix '
+                    '(filling the upper triangular region of the matrix)')
         con_flat = con
-        con = []
+        con = list()
         for this_con_flat in con_flat:
             this_con = np.zeros((n_signals, n_signals) +
                                 this_con_flat.shape[1:],
@@ -1060,3 +931,172 @@ def spectral_connectivity(data, method='coh', indices=None, sfreq=2 * np.pi,
         freqs = freqs_bands
 
     return con, freqs, times, n_epochs, n_tapers
+
+
+def _prepare_connectivity(epoch_block, tmin, tmax, fmin, fmax, sfreq, indices,
+                          mode, fskip, n_bands,
+                          cwt_freqs, faverage):
+    """Check and precompute dimensions of results data."""
+    first_epoch = epoch_block[0]
+
+    # get the data size and time scale
+    n_signals, n_times_in, times_in = _get_and_verify_data_sizes(first_epoch)
+
+    if times_in is None:
+        # we are not using Epochs or SourceEstimate(s) as input
+        times_in = np.linspace(0.0, n_times_in / sfreq, n_times_in,
+                               endpoint=False)
+
+    n_times_in = len(times_in)
+    mask = _time_mask(times_in, tmin, tmax, sfreq=sfreq)
+    tmin_idx, tmax_idx = np.where(mask)[0][[0, -1]]
+    tmax_idx += 1
+    tmin_true = times_in[tmin_idx]
+    tmax_true = times_in[tmax_idx - 1]  # time of last point used
+
+    times = times_in[tmin_idx:tmax_idx]
+    n_times = len(times)
+
+    if indices is None:
+        logger.info('only using indices for lower-triangular matrix')
+        # only compute r for lower-triangular region
+        indices_use = np.tril_indices(n_signals, -1)
+    else:
+        indices_use = check_indices(indices)
+
+    # number of connectivities to compute
+    n_cons = len(indices_use[0])
+
+    logger.info('    computing connectivity for %d connections'
+                % n_cons)
+    logger.info('    using t=%0.3fs..%0.3fs for estimation (%d points)'
+                % (tmin_true, tmax_true, n_times))
+
+    # get frequencies of interest for the different modes
+    if mode in ('multitaper', 'fourier'):
+        # fmin fmax etc is only supported for these modes
+        # decide which frequencies to keep
+        freqs_all = np.fft.rfftfreq(n_times, 1. / sfreq)
+    elif mode == 'cwt_morlet':
+        # cwt_morlet mode
+        if cwt_freqs is None:
+            raise ValueError('define frequencies of interest using '
+                             'cwt_freqs')
+        else:
+            cwt_freqs = cwt_freqs.astype(np.float)
+        if any(cwt_freqs > (sfreq / 2.)):
+            raise ValueError('entries in cwt_freqs cannot be '
+                             'larger than Nyquist (sfreq / 2)')
+        freqs_all = cwt_freqs
+    else:
+        raise ValueError('mode has an invalid value')
+
+    # check that fmin corresponds to at least 5 cycles
+    five_cycle_freq = 5. * sfreq / float(n_times)
+    if len(fmin) == 1 and fmin[0] == -np.inf:
+        # we use the 5 cycle freq. as default
+        fmin = [five_cycle_freq]
+    else:
+        if any(fmin < five_cycle_freq):
+            warn('fmin corresponds to less than 5 cycles, '
+                 'spectrum estimate will be unreliable')
+
+    # create a frequency mask for all bands
+    freq_mask = np.zeros(len(freqs_all), dtype=np.bool)
+    for f_lower, f_upper in zip(fmin, fmax):
+        freq_mask |= ((freqs_all >= f_lower) & (freqs_all <= f_upper))
+
+    # possibly skip frequency points
+    for pos in range(fskip):
+        freq_mask[pos + 1::fskip + 1] = False
+
+    # the frequency points where we compute connectivity
+    freqs = freqs_all[freq_mask]
+    n_freqs = len(freqs)
+
+    # get the freq. indices and points for each band
+    freq_idx_bands = [np.where((freqs >= fl) & (freqs <= fu))[0]
+                      for fl, fu in zip(fmin, fmax)]
+    freqs_bands = [freqs[freq_idx] for freq_idx in freq_idx_bands]
+
+    # make sure we don't have empty bands
+    for i, n_f_band in enumerate([len(f) for f in freqs_bands]):
+        if n_f_band == 0:
+            raise ValueError('There are no frequency points between '
+                             '%0.1fHz and %0.1fHz. Change the band '
+                             'specification (fmin, fmax) or the '
+                             'frequency resolution.'
+                             % (fmin[i], fmax[i]))
+    if n_bands == 1:
+        logger.info('    frequencies: %0.1fHz..%0.1fHz (%d points)'
+                    % (freqs_bands[0][0], freqs_bands[0][-1],
+                       n_freqs))
+    else:
+        logger.info('    computing connectivity for the bands:')
+        for i, bfreqs in enumerate(freqs_bands):
+            logger.info('     band %d: %0.1fHz..%0.1fHz '
+                        '(%d points)' % (i + 1, bfreqs[0],
+                                         bfreqs[-1], len(bfreqs)))
+    if faverage:
+        logger.info('    connectivity scores will be averaged for '
+                    'each band')
+
+    return (n_cons, times, n_times, times_in, n_times_in, tmin_idx,
+            tmax_idx, n_freqs, freq_mask, freqs, freqs_bands, freq_idx_bands,
+            n_signals, indices_use)
+
+
+def _assemble_spectral_params(mode, n_times, mt_adaptive, mt_bandwidth, sfreq,
+                              mt_low_bias, cwt_n_cycles, cwt_freqs,
+                              freqs, freq_mask):
+    """Prepare time-frequency decomposition."""
+    spectral_params = dict(
+        eigvals=None, window_fun=None, wavelets=None)
+    n_tapers = None
+    n_times_spectrum = 0
+    if mode == 'multitaper':
+        # compute standardized half-bandwidth
+        if mt_bandwidth is not None:
+            half_nbw = float(mt_bandwidth) * n_times / (2 * sfreq)
+        else:
+            half_nbw = 4
+
+        # compute dpss windows
+        n_tapers_max = int(2 * half_nbw)
+        window_fun, eigvals = dpss_windows(n_times, half_nbw,
+                                           n_tapers_max,
+                                           low_bias=mt_low_bias)
+        spectral_params.update(window_fun=window_fun, eigvals=eigvals)
+        n_tapers = len(eigvals)
+        logger.info('    using multitaper spectrum estimation with '
+                    '%d DPSS windows' % n_tapers)
+
+        if mt_adaptive and len(eigvals) < 3:
+            warn('Not adaptively combining the spectral estimators '
+                 'due to a low number of tapers.')
+            mt_adaptive = False
+    elif mode == 'fourier':
+        logger.info('    using FFT with a Hanning window to estimate '
+                    'spectra')
+        spectral_params.update(window_fun=np.hanning(n_times), eigvals=1.)
+    elif mode == 'cwt_morlet':
+        logger.info('    using CWT with Morlet wavelets to estimate '
+                    'spectra')
+
+        # reformat cwt_n_cycles if we have removed some frequencies
+        # using fmin, fmax, fskip
+        cwt_n_cycles = np.asarray((cwt_n_cycles,)).ravel()
+        if len(cwt_n_cycles) > 1:
+            if len(cwt_n_cycles) != len(cwt_freqs):
+                raise ValueError('cwt_n_cycles must be float or an '
+                                 'array with the same size as cwt_freqs')
+            cwt_n_cycles = cwt_n_cycles[freq_mask]
+
+        # get the Morlet wavelets
+        spectral_params.update(
+            wavelets=morlet(sfreq, freqs,
+                            n_cycles=cwt_n_cycles, zero_mean=True))
+        n_times_spectrum = n_times
+    else:
+        raise ValueError('mode has an invalid value')
+    return spectral_params, mt_adaptive, n_times_spectrum, n_tapers
diff --git a/mne/connectivity/tests/test_effective.py b/mne/connectivity/tests/test_effective.py
index 2615f53..20ecfa7 100644
--- a/mne/connectivity/tests/test_effective.py
+++ b/mne/connectivity/tests/test_effective.py
@@ -33,7 +33,7 @@ def test_psi():
 
     cwt_freqs = np.arange(5., 20, 0.5)
     psi_cwt, freqs, times, n_epochs, n_tapers = phase_slope_index(
-        data, mode='cwt_morlet', sfreq=sfreq, cwt_frequencies=cwt_freqs,
+        data, mode='cwt_morlet', sfreq=sfreq, cwt_freqs=cwt_freqs,
         indices=indices)
 
     assert_true(np.all(psi_cwt > 0))
diff --git a/mne/connectivity/tests/test_spectral.py b/mne/connectivity/tests/test_spectral.py
index 9341939..b95ac26 100644
--- a/mne/connectivity/tests/test_spectral.py
+++ b/mne/connectivity/tests/test_spectral.py
@@ -2,19 +2,16 @@ import warnings
 
 import numpy as np
 from numpy.testing import assert_array_almost_equal
+import pytest
 from nose.tools import assert_true, assert_raises
 
 from mne.connectivity import spectral_connectivity
-from mne.connectivity.spectral import _CohEst
+from mne.connectivity.spectral import _CohEst, _get_n_epochs
 
 from mne import SourceEstimate
-from mne.utils import run_tests_if_main, slow_test
-from mne.filter import band_pass_filter
+from mne.utils import run_tests_if_main
+from mne.filter import filter_data
 
-trans_bandwidth = 2.5
-filt_kwargs = dict(filter_length='auto', fir_window='hamming', phase='zero',
-                   l_trans_bandwidth=trans_bandwidth,
-                   h_trans_bandwidth=trans_bandwidth)
 warnings.simplefilter('always')
 
 
@@ -34,12 +31,13 @@ def _stc_gen(data, sfreq, tmin, combo=False):
             yield (arr, stc)
 
 
- at slow_test
+ at pytest.mark.slowtest
 def test_spectral_connectivity():
     """Test frequency-domain connectivity methods"""
     # Use a case known to have no spurious correlations (it would bad if
     # nosetests could randomly fail):
-    np.random.seed(0)
+    rng = np.random.RandomState(0)
+    trans_bandwidth = 2.
 
     sfreq = 50.
     n_signals = 3
@@ -48,16 +46,18 @@ def test_spectral_connectivity():
 
     tmin = 0.
     tmax = (n_times - 1) / sfreq
-    data = np.random.randn(n_epochs, n_signals, n_times)
+    data = rng.randn(n_signals, n_epochs * n_times)
     times_data = np.linspace(tmin, tmax, n_times)
     # simulate connectivity from 5Hz..15Hz
     fstart, fend = 5.0, 15.0
-    for i in range(n_epochs):
-        data[i, 1, :] = band_pass_filter(data[i, 0, :],
-                                         sfreq, fstart, fend,
-                                         **filt_kwargs)
-        # add some noise, so the spectrum is not exactly zero
-        data[i, 1, :] += 1e-2 * np.random.randn(n_times)
+    data[1, :] = filter_data(data[0, :], sfreq, fstart, fend,
+                             filter_length='auto', fir_design='firwin2',
+                             l_trans_bandwidth=trans_bandwidth,
+                             h_trans_bandwidth=trans_bandwidth)
+    # add some noise, so the spectrum is not exactly zero
+    data[1, :] += 1e-2 * rng.randn(n_times * n_epochs)
+    data = data.reshape(n_signals, n_epochs, n_times)
+    data = np.transpose(data, [1, 0, 2])
 
     # First we test some invalid parameters:
     assert_raises(ValueError, spectral_connectivity, data, method='notamethod')
@@ -79,7 +79,7 @@ def test_spectral_connectivity():
     modes = ['multitaper', 'fourier', 'cwt_morlet']
 
     # define some frequencies for cwt
-    cwt_frequencies = np.arange(3, 24.5, 1)
+    cwt_freqs = np.arange(3, 24.5, 1)
 
     for mode in modes:
         for method in methods:
@@ -91,7 +91,7 @@ def test_spectral_connectivity():
 
             if method == 'coh' and mode == 'cwt_morlet':
                 # so we also test using an array for num cycles
-                cwt_n_cycles = 7. * np.ones(len(cwt_frequencies))
+                cwt_n_cycles = 7. * np.ones(len(cwt_freqs))
             else:
                 cwt_n_cycles = 7.
 
@@ -105,7 +105,7 @@ def test_spectral_connectivity():
                 con, freqs, times, n, _ = spectral_connectivity(
                     data, method=method, mode=mode, indices=None, sfreq=sfreq,
                     mt_adaptive=adaptive, mt_low_bias=True,
-                    mt_bandwidth=mt_bandwidth, cwt_frequencies=cwt_frequencies,
+                    mt_bandwidth=mt_bandwidth, cwt_freqs=cwt_freqs,
                     cwt_n_cycles=cwt_n_cycles)
 
                 assert_true(n == n_epochs)
@@ -114,53 +114,40 @@ def test_spectral_connectivity():
                 if mode == 'multitaper':
                     upper_t = 0.95
                     lower_t = 0.5
-                elif mode == 'fourier':
+                else:  # mode == 'fourier' or mode == 'cwt_morlet'
                     # other estimates have higher variance
                     upper_t = 0.8
                     lower_t = 0.75
-                else:  # cwt_morlet
-                    upper_t = 0.64
-                    lower_t = 0.63
 
                 # test the simulated signal
+                gidx = np.searchsorted(freqs, (fstart, fend))
+                bidx = np.searchsorted(freqs,
+                                       (fstart - trans_bandwidth * 2,
+                                        fend + trans_bandwidth * 2))
                 if method == 'coh':
-                    idx = np.searchsorted(freqs, (fstart + trans_bandwidth,
-                                                  fend - trans_bandwidth))
+                    assert_true(np.all(con[1, 0, gidx[0]:gidx[1]] > upper_t),
+                                con[1, 0, gidx[0]:gidx[1]].min())
                     # we see something for zero-lag
-                    assert_true(np.all(con[1, 0, idx[0]:idx[1]] > upper_t),
-                                con[1, 0, idx[0]:idx[1]].min())
-
-                    if mode != 'cwt_morlet':
-                        idx = np.searchsorted(freqs,
-                                              (fstart - trans_bandwidth * 2,
-                                               fend + trans_bandwidth * 2))
-                        assert_true(np.all(con[1, 0, :idx[0]] < lower_t))
-                        assert_true(np.all(con[1, 0, idx[1]:] < lower_t),
-                                    con[1, 0, idx[1:]].max())
+                    assert_true(np.all(con[1, 0, :bidx[0]] < lower_t))
+                    assert_true(np.all(con[1, 0, bidx[1]:] < lower_t),
+                                con[1, 0, bidx[1:]].max())
                 elif method == 'cohy':
-                    idx = np.searchsorted(freqs, (fstart + 1, fend - 1))
                     # imaginary coh will be zero
-                    check = np.imag(con[1, 0, idx[0]:idx[1]])
+                    check = np.imag(con[1, 0, gidx[0]:gidx[1]])
                     assert_true(np.all(check < lower_t), check.max())
                     # we see something for zero-lag
-                    assert_true(np.all(np.abs(con[1, 0, idx[0]:idx[1]]) >
+                    assert_true(np.all(np.abs(con[1, 0, gidx[0]:gidx[1]]) >
                                 upper_t))
-
-                    idx = np.searchsorted(freqs, (fstart - trans_bandwidth * 2,
-                                                  fend + trans_bandwidth * 2))
-                    if mode != 'cwt_morlet':
-                        assert_true(np.all(np.abs(con[1, 0, :idx[0]]) <
-                                    lower_t))
-                        assert_true(np.all(np.abs(con[1, 0, idx[1]:]) <
-                                    lower_t))
+                    assert_true(np.all(np.abs(con[1, 0, :bidx[0]]) <
+                                lower_t))
+                    assert_true(np.all(np.abs(con[1, 0, bidx[1]:]) <
+                                lower_t))
                 elif method == 'imcoh':
-                    idx = np.searchsorted(freqs, (fstart + 1, fend - 1))
                     # imaginary coh will be zero
-                    assert_true(np.all(con[1, 0, idx[0]:idx[1]] < lower_t))
-                    idx = np.searchsorted(freqs, (fstart - 1, fend + 1))
-                    assert_true(np.all(con[1, 0, :idx[0]] < lower_t))
-                    assert_true(np.all(con[1, 0, idx[1]:] < lower_t),
-                                con[1, 0, idx[1]:].max())
+                    assert_true(np.all(con[1, 0, gidx[0]:gidx[1]] < lower_t))
+                    assert_true(np.all(con[1, 0, :bidx[0]] < lower_t))
+                    assert_true(np.all(con[1, 0, bidx[1]:] < lower_t),
+                                con[1, 0, bidx[1]:].max())
 
                 # compute same connections using indices and 2 jobs
                 indices = np.tril_indices(n_signals, -1)
@@ -175,7 +162,7 @@ def test_spectral_connectivity():
                     stc_data, method=test_methods, mode=mode, indices=indices,
                     sfreq=sfreq, mt_adaptive=adaptive, mt_low_bias=True,
                     mt_bandwidth=mt_bandwidth, tmin=tmin, tmax=tmax,
-                    cwt_frequencies=cwt_frequencies,
+                    cwt_freqs=cwt_freqs,
                     cwt_n_cycles=cwt_n_cycles, n_jobs=2)
 
                 assert_true(isinstance(con2, list))
@@ -208,7 +195,7 @@ def test_spectral_connectivity():
                     data, method=method, mode=mode, indices=indices,
                     sfreq=sfreq, fmin=fmin, fmax=fmax, fskip=1, faverage=True,
                     mt_adaptive=adaptive, mt_low_bias=True,
-                    mt_bandwidth=mt_bandwidth, cwt_frequencies=cwt_frequencies,
+                    mt_bandwidth=mt_bandwidth, cwt_freqs=cwt_freqs,
                     cwt_n_cycles=cwt_n_cycles)
 
                 assert_true(isinstance(freqs3, list))
@@ -229,6 +216,12 @@ def test_spectral_connectivity():
                             freq_idx = np.searchsorted(freqs2, freqs3[i])
                             con2_avg = np.mean(con2[j][:, freq_idx], axis=1)
                             assert_array_almost_equal(con2_avg, con3[j][:, i])
-
+    # test _get_n_epochs
+    full_list = list(range(10))
+    out_lens = np.array([len(x) for x in _get_n_epochs(full_list, 4)])
+    assert_true((out_lens == np.array([4, 4, 2])).all())
+    out_lens = np.array([len(x) for x in _get_n_epochs(full_list, 11)])
+    assert_true(len(out_lens) > 0)
+    assert_true(out_lens[0] == 10)
 
 run_tests_if_main()
diff --git a/mne/connectivity/utils.py b/mne/connectivity/utils.py
index 14025b4..987b0fe 100644
--- a/mne/connectivity/utils.py
+++ b/mne/connectivity/utils.py
@@ -5,8 +5,7 @@ import numpy as np
 
 
 def check_indices(indices):
-    """Check indices parameter"""
-
+    """Check indices parameter."""
     if not isinstance(indices, tuple) or len(indices) != 2:
         raise ValueError('indices must be a tuple of length 2')
 
diff --git a/mne/coreg.py b/mne/coreg.py
index 9fb5fd6..a8e8efd 100644
--- a/mne/coreg.py
+++ b/mne/coreg.py
@@ -1,10 +1,11 @@
-"""Coregistration between different coordinate frames"""
+"""Coregistration between different coordinate frames."""
 
 # Authors: Christian Brodbeck <christianbrodbeck at nyu.edu>
 #
 # License: BSD (3-clause)
 
 from .externals.six.moves import configparser
+from .externals.six import string_types
 import fnmatch
 from glob import glob, iglob
 import os
@@ -18,14 +19,16 @@ from functools import reduce
 import numpy as np
 from numpy import dot
 
-from .io import read_fiducials, write_fiducials
+from .io import read_fiducials, write_fiducials, read_info
+from .io.constants import FIFF
 from .label import read_label, Label
 from .source_space import (add_source_space_distances, read_source_spaces,
                            write_source_spaces)
-from .surface import read_surface, write_surface
+from .surface import read_surface, write_surface, _normalize_vectors
 from .bem import read_bem_surfaces, write_bem_surfaces
-from .transforms import rotation, rotation3d, scaling, translation
+from .transforms import rotation, rotation3d, scaling, translation, Transform
 from .utils import get_config, get_subjects_dir, logger, pformat
+from .viz._3d import _fiducial_coords
 from .externals.six.moves import zip
 
 
@@ -39,17 +42,19 @@ head_bem_fname = pformat(bem_fname, name='head')
 fid_fname = pformat(bem_fname, name='fiducials')
 fid_fname_general = os.path.join(bem_dirname, "{head}-fiducials.fif")
 src_fname = os.path.join(bem_dirname, '{subject}-{spacing}-src.fif')
+_head_fnames = (head_bem_fname, pformat(bem_fname, name='head-medium'))
 _high_res_head_fnames = (os.path.join(bem_dirname, '{subject}-head-dense.fif'),
                          os.path.join(surf_dirname, 'lh.seghead'),
                          os.path.join(surf_dirname, 'lh.smseghead'))
 
 
 def _make_writable(fname):
+    """Make a file writable."""
     os.chmod(fname, stat.S_IMODE(os.lstat(fname)[stat.ST_MODE]) | 128)  # write
 
 
 def _make_writable_recursive(path):
-    """Recursively set writable"""
+    """Recursively set writable."""
     if sys.platform.startswith('win'):
         return  # can't safely set perms
     for root, dirs, files in os.walk(path, topdown=False):
@@ -57,25 +62,59 @@ def _make_writable_recursive(path):
             _make_writable(os.path.join(root, f))
 
 
-def _find_high_res_head(subject, subjects_dir):
-    for fname in _high_res_head_fnames:
+def _find_head_bem(subject, subjects_dir, high_res=False):
+    """Find a high resolution head."""
+    # XXX this should be refactored with mne.surface.get_head_surf ...
+    fnames = _high_res_head_fnames if high_res else _head_fnames
+    for fname in fnames:
         path = fname.format(subjects_dir=subjects_dir, subject=subject)
         if os.path.exists(path):
             return path
 
 
-def create_default_subject(mne_root=None, fs_home=None, update=False,
+def coregister_fiducials(info, fiducials, tol=0.01):
+    """Create a head-MRI transform by aligning 3 fiducial points.
+
+    Parameters
+    ----------
+    info : Info
+        Measurement info object with fiducials in head coordinate space.
+    fiducials : str | list of dict
+        Fiducials in MRI coordinate space (either path to a ``*-fiducials.fif``
+        file or list of fiducials as returned by :func:`read_fiducials`.
+
+    Returns
+    -------
+    trans : Transform
+        The device-MRI transform.
+    """
+    if isinstance(info, string_types):
+        info = read_info(info)
+    if isinstance(fiducials, string_types):
+        fiducials, coord_frame_to = read_fiducials(fiducials)
+    else:
+        coord_frame_to = FIFF.FIFFV_COORD_MRI
+    frames_from = {d['coord_frame'] for d in info['dig']}
+    if len(frames_from) > 1:
+        raise ValueError("info contains fiducials from different coordinate "
+                         "frames")
+    else:
+        coord_frame_from = frames_from.pop()
+    coords_from = _fiducial_coords(info['dig'])
+    coords_to = _fiducial_coords(fiducials, coord_frame_to)
+    trans = fit_matched_points(coords_from, coords_to, tol=tol)
+    return Transform(coord_frame_from, coord_frame_to, trans)
+
+
+def create_default_subject(fs_home=None, update=False,
                            subjects_dir=None):
-    """Create an average brain subject for subjects without structural MRI
+    """Create an average brain subject for subjects without structural MRI.
 
     Create a copy of fsaverage from the Freesurfer directory in subjects_dir
     and add auxiliary files from the mne package.
 
     Parameters
     ----------
-    mne_root : None | str
-        The mne root directory (only needed if MNE_ROOT is not specified as
-        environment variable).
     fs_home : None | str
         The freesurfer home directory (only needed if FREESURFER_HOME is not
         specified as environment variable).
@@ -91,24 +130,10 @@ def create_default_subject(mne_root=None, fs_home=None, update=False,
     -----
     When no structural MRI is available for a subject, an average brain can be
     substituted. Freesurfer comes with such an average brain model, and MNE
-    comes with some auxiliary files which make coregistration easier.
-    :py:func:`create_default_subject` copies the relevant files from Freesurfer
-    into the current subjects_dir, and also adds the auxiliary files provided
-    by MNE.
-
-    The files provided by MNE are listed below and can be found under
-    ``share/mne/mne_analyze/fsaverage`` in the MNE directory (see MNE manual
-    section 7.19 Working with the average brain):
-
-    fsaverage_head.fif:
-        The approximate head surface triangulation for fsaverage.
-    fsaverage_inner_skull-bem.fif:
-        The approximate inner skull surface for fsaverage.
-    fsaverage-fiducials.fif:
-        The locations of the fiducial points (LPA, RPA, and nasion).
-    fsaverage-trans.fif:
-        Contains a default MEG-MRI coordinate transformation suitable for
-        fsaverage.
+    comes with some auxiliary files which make coregistration easier (see
+    :ref:`CACGEAFI`). :py:func:`create_default_subject` copies the relevant
+    files from Freesurfer into the current subjects_dir, and also adds the
+    auxiliary files provided by MNE.
     """
     subjects_dir = get_subjects_dir(subjects_dir, raise_error=True)
     if fs_home is None:
@@ -118,12 +143,6 @@ def create_default_subject(mne_root=None, fs_home=None, update=False,
                 "FREESURFER_HOME environment variable not found. Please "
                 "specify the fs_home parameter in your call to "
                 "create_default_subject().")
-    if mne_root is None:
-        mne_root = get_config('MNE_ROOT', mne_root)
-        if mne_root is None:
-            raise ValueError("MNE_ROOT environment variable not found. Please "
-                             "specify the mne_root parameter in your call to "
-                             "create_default_subject().")
 
     # make sure freesurfer files exist
     fs_src = os.path.join(fs_home, 'subjects', 'fsaverage')
@@ -150,36 +169,28 @@ def create_default_subject(mne_root=None, fs_home=None, update=False,
             "subjects_dir %r. Delete or rename the existing fsaverage "
             "subject folder." % ('fsaverage', subjects_dir))
 
-    # make sure mne files exist
-    mne_fname = os.path.join(mne_root, 'share', 'mne', 'mne_analyze',
-                             'fsaverage', 'fsaverage-%s.fif')
-    mne_files = ('fiducials', 'head', 'inner_skull-bem', 'trans')
-    for name in mne_files:
-        fname = mne_fname % name
-        if not os.path.isfile(fname):
-            raise IOError("MNE fsaverage incomplete: %s file not found at "
-                          "%s" % (name, fname))
-
     # copy fsaverage from freesurfer
     logger.info("Copying fsaverage subject from freesurfer directory...")
     if (not update) or not os.path.exists(dest):
         shutil.copytree(fs_src, dest)
         _make_writable_recursive(dest)
 
-    # add files from mne
+    # copy files from mne
+    source_fname = os.path.join(os.path.dirname(__file__), 'data', 'fsaverage',
+                                'fsaverage-%s.fif')
     dest_bem = os.path.join(dest, 'bem')
     if not os.path.exists(dest_bem):
         os.mkdir(dest_bem)
-    logger.info("Copying auxiliary fsaverage files from mne directory...")
+    logger.info("Copying auxiliary fsaverage files from mne...")
     dest_fname = os.path.join(dest_bem, 'fsaverage-%s.fif')
     _make_writable_recursive(dest_bem)
-    for name in mne_files:
+    for name in ('fiducials', 'head', 'inner_skull-bem', 'trans'):
         if not os.path.exists(dest_fname % name):
-            shutil.copy(mne_fname % name, dest_bem)
+            shutil.copy(source_fname % name, dest_bem)
 
 
 def _decimate_points(pts, res=10):
-    """Decimate the number of points using a voxel grid
+    """Decimate the number of points using a voxel grid.
 
     Create a voxel grid with a specified resolution and retain at most one
     point per voxel. For each voxel, the point closest to its center is
@@ -233,7 +244,7 @@ def _decimate_points(pts, res=10):
 
 
 def _trans_from_params(param_info, params):
-    """Convert transformation parameters into a transformation matrix
+    """Convert transformation parameters into a transformation matrix.
 
     Parameters
     ----------
@@ -275,8 +286,9 @@ def _trans_from_params(param_info, params):
 
 def fit_matched_points(src_pts, tgt_pts, rotate=True, translate=True,
                        scale=False, tol=None, x0=None, out='trans'):
-    """Find a transform that minimizes the squared distance between two
-    matching sets of points.
+    """Find a transform between matched sets of points.
+
+    This minimizes the squared distance between two matching sets of points.
 
     Uses :func:`scipy.optimize.leastsq` to find a transformation involving
     a combination of rotation, translation, and scaling (in that order).
@@ -395,7 +407,7 @@ def fit_matched_points(src_pts, tgt_pts, rotate=True, translate=True,
 
 
 def _point_cloud_error(src_pts, tgt_pts):
-    """Find the distance from each source point to its closest target point
+    """Find the distance from each source point to its closest target point.
 
     Parameters
     ----------
@@ -417,7 +429,7 @@ def _point_cloud_error(src_pts, tgt_pts):
 
 
 def _point_cloud_error_balltree(src_pts, tgt_tree):
-    """Find the distance from each source point to its closest target point
+    """Find the distance from each source point to its closest target point.
 
     Uses sklearn.neighbors.BallTree for greater efficiency
 
@@ -440,11 +452,11 @@ def _point_cloud_error_balltree(src_pts, tgt_tree):
 
 def fit_point_cloud(src_pts, tgt_pts, rotate=True, translate=True,
                     scale=0, x0=None, leastsq_args={}, out='params'):
-    """Find a transform that minimizes the squared distance from each source
-    point to its closest target point
+    """Find a transform between unmatched sets of points.
 
-    Uses :func:`scipy.optimize.leastsq` to find a transformation involving
-    a combination of rotation, translation, and scaling (in that order).
+    This minimizes the squared distance from each source point to its closest
+    target point, using :func:`scipy.optimize.leastsq` to find a
+    transformation using rotation, translation, and scaling (in that order).
 
     Parameters
     ----------
@@ -561,7 +573,7 @@ def fit_point_cloud(src_pts, tgt_pts, rotate=True, translate=True,
 
 
 def _find_label_paths(subject='fsaverage', pattern=None, subjects_dir=None):
-    """Find paths to label files in a subject's label directory
+    """Find paths to label files in a subject's label directory.
 
     Parameters
     ----------
@@ -576,7 +588,7 @@ def _find_label_paths(subject='fsaverage', pattern=None, subjects_dir=None):
         (sys.environ['SUBJECTS_DIR'])
 
     Returns
-    ------
+    -------
     paths : list
         List of paths relative to the subject's label directory
     """
@@ -598,7 +610,7 @@ def _find_label_paths(subject='fsaverage', pattern=None, subjects_dir=None):
 
 
 def _find_mri_paths(subject, skip_fiducials, subjects_dir):
-    """Find all files of an mri relevant for source transformation
+    """Find all files of an mri relevant for source transformation.
 
     Parameters
     ----------
@@ -613,7 +625,7 @@ def _find_mri_paths(subject, skip_fiducials, subjects_dir):
 
     Returns
     -------
-    paths | dict
+    paths : dict
         Dictionary whose keys are relevant file type names (str), and whose
         values are lists of paths.
     """
@@ -647,7 +659,7 @@ def _find_mri_paths(subject, skip_fiducials, subjects_dir):
     bem_pattern = pformat(bem_fname, subjects_dir=subjects_dir,
                           subject=subject, name='*-bem')
     re_pattern = pformat(bem_fname, subjects_dir=subjects_dir, subject=subject,
-                         name='(.+)')
+                         name='(.+)').replace('\\', '\\\\')
     for path in iglob(bem_pattern):
         match = re.match(re_pattern, path)
         name = match.group(1)
@@ -696,6 +708,7 @@ def _find_mri_paths(subject, skip_fiducials, subjects_dir):
 
 
 def _find_fiducials_files(subject, subjects_dir):
+    """Find fiducial files."""
     fid = []
     # standard fiducials
     if os.path.exists(fid_fname.format(subjects_dir=subjects_dir,
@@ -705,7 +718,7 @@ def _find_fiducials_files(subject, subjects_dir):
     pattern = pformat(fid_fname_general, subjects_dir=subjects_dir,
                       subject=subject, head='*')
     regex = pformat(fid_fname_general, subjects_dir=subjects_dir,
-                    subject=subject, head='(.+)')
+                    subject=subject, head='(.+)').replace('\\', '\\\\')
     for path in iglob(pattern):
         match = re.match(regex, path)
         head = match.group(1).replace(subject, '{subject}')
@@ -714,7 +727,7 @@ def _find_fiducials_files(subject, subjects_dir):
 
 
 def _is_mri_subject(subject, subjects_dir=None):
-    """Check whether a directory in subjects_dir is an mri subject directory
+    """Check whether a directory in subjects_dir is an mri subject directory.
 
     Parameters
     ----------
@@ -729,12 +742,12 @@ def _is_mri_subject(subject, subjects_dir=None):
         Whether ``subject`` is an mri subject.
     """
     subjects_dir = get_subjects_dir(subjects_dir, raise_error=True)
-    fname = head_bem_fname.format(subjects_dir=subjects_dir, subject=subject)
-    return os.path.exists(fname)
+    return bool(_find_head_bem(subject, subjects_dir) or
+                _find_head_bem(subject, subjects_dir, high_res=True))
 
 
 def _is_scaled_mri_subject(subject, subjects_dir=None):
-    """Check whether a directory in subjects_dir is a scaled mri subject
+    """Check whether a directory in subjects_dir is a scaled mri subject.
 
     Parameters
     ----------
@@ -756,7 +769,7 @@ def _is_scaled_mri_subject(subject, subjects_dir=None):
 
 
 def _mri_subject_has_bem(subject, subjects_dir=None):
-    """Check whether an mri subject has a file matching the bem pattern
+    """Check whether an mri subject has a file matching the bem pattern.
 
     Parameters
     ----------
@@ -778,7 +791,7 @@ def _mri_subject_has_bem(subject, subjects_dir=None):
 
 
 def read_mri_cfg(subject, subjects_dir=None):
-    """Read information from the cfg file of a scaled MRI brain
+    """Read information from the cfg file of a scaled MRI brain.
 
     Parameters
     ----------
@@ -817,7 +830,7 @@ def read_mri_cfg(subject, subjects_dir=None):
 
 
 def _write_mri_config(fname, subject_from, subject_to, scale):
-    """Write the cfg file describing a scaled MRI subject
+    """Write the cfg file describing a scaled MRI subject.
 
     Parameters
     ----------
@@ -851,7 +864,7 @@ def _write_mri_config(fname, subject_from, subject_to, scale):
 
 
 def _scale_params(subject_to, subject_from, scale, subjects_dir):
-    """Assemble parameters for scaling
+    """Assemble parameters for scaling.
 
     Returns
     -------
@@ -900,7 +913,7 @@ def _scale_params(subject_to, subject_from, scale, subjects_dir):
 
 def scale_bem(subject_to, bem_name, subject_from=None, scale=None,
               subjects_dir=None):
-    """Scale a bem file
+    """Scale a bem file.
 
     Parameters
     ----------
@@ -934,14 +947,15 @@ def scale_bem(subject_to, bem_name, subject_from=None, scale=None,
     for surf in surfs:
         surf['rr'] *= scale
         if nn_scale is not None:
+            assert len(surf['nn']) > 0
             surf['nn'] *= nn_scale
-            surf['nn'] /= np.sqrt(np.sum(surf['nn'] ** 2, 1))[:, np.newaxis]
+            _normalize_vectors(surf['nn'])
     write_bem_surfaces(dst, surfs)
 
 
 def scale_labels(subject_to, pattern=None, overwrite=False, subject_from=None,
                  scale=None, subjects_dir=None):
-    """Scale labels to match a brain that was previously created by scaling
+    r"""Scale labels to match a brain that was previously created by scaling.
 
     Parameters
     ----------
@@ -1000,8 +1014,9 @@ def scale_labels(subject_to, pattern=None, overwrite=False, subject_from=None,
 
 
 def scale_mri(subject_from, subject_to, scale, overwrite=False,
-              subjects_dir=None, skip_fiducials=False):
-    """Create a scaled copy of an MRI subject
+              subjects_dir=None, skip_fiducials=False, labels=True,
+              annot=False):
+    """Create a scaled copy of an MRI subject.
 
     Parameters
     ----------
@@ -1018,6 +1033,10 @@ def scale_mri(subject_from, subject_to, scale, overwrite=False,
     skip_fiducials : bool
         Do not scale the MRI fiducials. If False (default), an IOError will be
         raised if no fiducials file can be found.
+    labels : bool
+        Also scale all labels (default True).
+    annot : bool
+        Copy ``*.annot`` files to the new location (default False).
 
     See Also
     --------
@@ -1082,13 +1101,23 @@ def scale_mri(subject_from, subject_to, scale, overwrite=False,
                            subjects_dir)
 
     # labels [in m]
-    scale_labels(subject_to, subject_from=subject_from, scale=scale,
-                 subjects_dir=subjects_dir)
+    os.mkdir(os.path.join(subjects_dir, subject_to, 'label'))
+    if labels:
+        scale_labels(subject_to, subject_from=subject_from, scale=scale,
+                     subjects_dir=subjects_dir)
+
+    # copy *.annot files (they don't contain scale-dependent information)
+    if annot:
+        src_pattern = os.path.join(subjects_dir, subject_from, 'label',
+                                   '*.annot')
+        dst_dir = os.path.join(subjects_dir, subject_to, 'label')
+        for src_file in iglob(src_pattern):
+            shutil.copy(src_file, dst_dir)
 
 
 def scale_source_space(subject_to, src_name, subject_from=None, scale=None,
                        subjects_dir=None, n_jobs=1):
-    """Scale a source space for an mri created with scale_mri()
+    """Scale a source space for an mri created with scale_mri().
 
     Parameters
     ----------
@@ -1121,7 +1150,7 @@ def scale_source_space(subject_to, src_name, subject_from=None, scale=None,
         spacing = src_name  # spacing in mm
         src_pattern = src_fname
     else:
-        match = re.match("(oct|ico)-?(\d+)$", src_name)
+        match = re.match(r"(oct|ico)-?(\d+)$", src_name)
         if match:
             spacing = '-'.join(match.groups())
             src_pattern = src_fname
@@ -1152,7 +1181,7 @@ def scale_source_space(subject_to, src_name, subject_from=None, scale=None,
                 ss['dist_limit'] *= scale
         else:  # non-uniform scaling
             ss['nn'] *= nn_scale
-            ss['nn'] /= np.sqrt(np.sum(ss['nn'] ** 2, 1))[:, np.newaxis]
+            _normalize_vectors(ss['nn'])
             if ss['dist'] is not None:
                 add_dist = True
 
diff --git a/mne/cov.py b/mne/cov.py
index 292ed25..6b2652e 100644
--- a/mne/cov.py
+++ b/mne/cov.py
@@ -4,7 +4,7 @@
 #
 # License: BSD (3-clause)
 
-import copy as cp
+from copy import deepcopy
 from distutils.version import LooseVersion
 import itertools as itt
 from math import log
@@ -15,13 +15,14 @@ from scipy import linalg
 
 from .io.write import start_file, end_file
 from .io.proj import (make_projector, _proj_equal, activate_proj,
-                      _needs_eeg_average_ref_proj)
+                      _needs_eeg_average_ref_proj, _check_projs,
+                      _has_eeg_average_ref_proj)
 from .io import fiff_open
 from .io.pick import (pick_types, pick_channels_cov, pick_channels, pick_info,
                       _picks_by_type, _pick_data_channels)
 
 from .io.constants import FIFF
-from .io.meas_info import read_bad_channels
+from .io.meas_info import read_bad_channels, _simplify_info
 from .io.proj import _read_proj, _write_proj
 from .io.tag import find_tag
 from .io.tree import dir_tree_find
@@ -32,11 +33,12 @@ from .epochs import Epochs
 from .event import make_fixed_length_events
 from .utils import (check_fname, logger, verbose, estimate_rank,
                     _compute_row_norms, check_version, _time_mask, warn,
-                    copy_function_doc_to_method_doc)
+                    copy_function_doc_to_method_doc, _pl)
 from . import viz
 
 from .externals.six.moves import zip
 from .externals.six import string_types
+from .fixes import BaseEstimator
 
 
 def _check_covs_algebra(cov1, cov2):
@@ -51,7 +53,7 @@ def _check_covs_algebra(cov1, cov2):
 
 
 def _get_tslice(epochs, tmin, tmax):
-    """get the slice."""
+    """Get the slice."""
     mask = _time_mask(epochs.times, tmin, tmax, sfreq=epochs.info['sfreq'])
     tstart = np.where(mask)[0][0] if tmin is not None else None
     tend = np.where(mask)[0][-1] + 1 if tmax is not None else None
@@ -60,7 +62,6 @@ def _get_tslice(epochs, tmin, tmax):
 
 
 class Covariance(dict):
-
     """Noise covariance matrix.
 
     .. warning:: This class should not be instantiated directly, but
@@ -96,6 +97,8 @@ class Covariance(dict):
         List of channels' names.
     nfree : int
         Number of degrees of freedom i.e. number of time points used.
+    dim : int
+        The number of channels ``n_channels``.
 
     See Also
     --------
@@ -109,6 +112,7 @@ class Covariance(dict):
                  method=None, loglik=None):
         """Init of covariance."""
         diag = True if data.ndim == 1 else False
+        projs = _check_projs(projs)
         self.update(data=data, dim=len(data), names=names, bads=bads,
                     nfree=nfree, eig=eig, eigvec=eigvec, diag=diag,
                     projs=projs, kind=FIFF.FIFFV_MNE_NOISE_COV)
@@ -146,22 +150,22 @@ class Covariance(dict):
 
         try:
             _write_cov(fid, self)
-        except Exception as inst:
+        except Exception:
             fid.close()
             os.remove(fname)
-            raise inst
+            raise
 
         end_file(fid)
 
     def copy(self):
-        """Copy the Covariance object
+        """Copy the Covariance object.
 
         Returns
         -------
         cov : instance of Covariance
             The copied object.
         """
-        return cp.deepcopy(self)
+        return deepcopy(self)
 
     def as_diag(self):
         """Set covariance to be processed as being diagonal.
@@ -184,7 +188,7 @@ class Covariance(dict):
         self['eigvec'] = None
         return self
 
-    def __repr__(self):
+    def __repr__(self):  # noqa: D105
         if self.data.ndim == 2:
             s = 'size : %s x %s' % self.data.shape
         else:  # ndim == 1
@@ -196,7 +200,7 @@ class Covariance(dict):
     def __add__(self, cov):
         """Add Covariance taking into account number of degrees of freedom."""
         _check_covs_algebra(self, cov)
-        this_cov = cp.deepcopy(cov)
+        this_cov = cov.copy()
         this_cov['data'] = (((this_cov['data'] * this_cov['nfree']) +
                              (self['data'] * self['nfree'])) /
                             (self['nfree'] + this_cov['nfree']))
@@ -239,7 +243,8 @@ def read_cov(fname, verbose=None):
         The name of file containing the covariance matrix. It should end with
         -cov.fif or -cov.fif.gz.
     verbose : bool, str, int, or None (default None)
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -269,7 +274,8 @@ def make_ad_hoc_cov(info, verbose=None):
     info : instance of Info
         Measurement info.
     verbose : bool, str, int, or None (default None)
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -278,10 +284,12 @@ def make_ad_hoc_cov(info, verbose=None):
 
     Notes
     -----
+    This uses values of 5 fT/cm, 20 fT, and 0.2 uV for gradiometers,
+    magnetometers, and EEG channels, respectively.
+
     .. versionadded:: 0.9.0
     """
-    info = pick_info(info, pick_types(info, meg=True, eeg=True, exclude=[]))
-    info._check_consistency()
+    picks = pick_types(info, meg=True, eeg=True, exclude=())
 
     # Standard deviations to be used
     grad_std = 5e-13
@@ -291,12 +299,13 @@ def make_ad_hoc_cov(info, verbose=None):
                 '(MEG grad : %6.1f fT/cm MEG mag : %6.1f fT EEG : %6.1f uV)'
                 % (1e13 * grad_std, 1e15 * mag_std, 1e6 * eeg_std))
 
-    data = np.zeros(len(info['ch_names']))
+    data = np.zeros(len(picks))
     for meg, eeg, val in zip(('grad', 'mag', False), (False, False, True),
                              (grad_std, mag_std, eeg_std)):
-        data[pick_types(info, meg=meg, eeg=eeg)] = val * val
-    return Covariance(data, info['ch_names'], info['bads'], info['projs'],
-                      nfree=0)
+        these_picks = pick_types(info, meg=meg, eeg=eeg)
+        data[np.searchsorted(picks, these_picks)] = val * val
+    ch_names = [info['ch_names'][pick] for pick in picks]
+    return Covariance(data, ch_names, info['bads'], info['projs'], nfree=0)
 
 
 def _check_n_samples(n_samples, n_chan):
@@ -313,7 +322,8 @@ def _check_n_samples(n_samples, n_chan):
 def compute_raw_covariance(raw, tmin=0, tmax=None, tstep=0.2, reject=None,
                            flat=None, picks=None, method='empirical',
                            method_params=None, cv=3, scalings=None, n_jobs=1,
-                           return_estimators=False, verbose=None):
+                           return_estimators=False, reject_by_annotation=True,
+                           verbose=None):
     """Estimate noise covariance matrix from a continuous segment of raw data.
 
     It is typically useful to estimate a noise covariance from empty room
@@ -380,7 +390,7 @@ def compute_raw_covariance(raw, tmin=0, tmax=None, tstep=0.2, reject=None,
 
         .. versionadded:: 0.12
 
-    cv : int | sklearn cross_validation object (default 3)
+    cv : int | sklearn model_selection object (default 3)
         The cross validation method. Defaults to 3, which will
         internally trigger a default 3-fold shuffle split.
 
@@ -404,8 +414,16 @@ def compute_raw_covariance(raw, tmin=0, tmax=None, tstep=0.2, reject=None,
 
         .. versionadded:: 0.12
 
+    reject_by_annotation : bool
+        Whether to reject based on annotations. If True (default), epochs
+        overlapping with segments whose description begins with ``'bad'`` are
+        rejected. If False, no rejection based on annotations is performed.
+
+        .. versionadded:: 0.14.0
+
     verbose : bool | str | int | None (default None)
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -424,8 +442,7 @@ def compute_raw_covariance(raw, tmin=0, tmax=None, tstep=0.2, reject=None,
     tstep = tmax - tmin if tstep is None else float(tstep)
     tstep_m1 = tstep - 1. / raw.info['sfreq']  # inclusive!
     events = make_fixed_length_events(raw, 1, tmin, tmax, tstep)
-    pl = 's' if len(events) != 1 else ''
-    logger.info('Using up to %s segment%s' % (len(events), pl))
+    logger.info('Using up to %s segment%s' % (len(events), _pl(events)))
 
     # don't exclude any bad channels, inverses expect all channels present
     if picks is None:
@@ -437,7 +454,8 @@ def compute_raw_covariance(raw, tmin=0, tmax=None, tstep=0.2, reject=None,
         pick_mask = slice(None)
     epochs = Epochs(raw, events, 1, 0, tstep_m1, baseline=None,
                     picks=picks, reject=reject, flat=flat, verbose=False,
-                    preload=False, proj=False, add_eeg_ref=False)
+                    preload=False, proj=False,
+                    reject_by_annotation=reject_by_annotation)
     if method is None:
         method = 'empirical'
     if isinstance(method, string_types) and method == 'empirical':
@@ -459,8 +477,8 @@ def compute_raw_covariance(raw, tmin=0, tmax=None, tstep=0.2, reject=None,
         logger.info('[done]')
         ch_names = [raw.info['ch_names'][k] for k in picks]
         bads = [b for b in raw.info['bads'] if b in ch_names]
-        projs = cp.deepcopy(raw.info['projs'])
-        return Covariance(data, ch_names, bads, projs, nfree=n_samples)
+        return Covariance(data, ch_names, bads, raw.info['projs'],
+                          nfree=n_samples)
     del picks, pick_mask
 
     # This makes it equivalent to what we used to do (and do above for
@@ -576,7 +594,7 @@ def compute_covariance(epochs, keep_sample_mean=True, tmin=None, tmax=None,
             'pca': {'iter_n_components': None},
             'factor_analysis': {'iter_n_components': None}
 
-    cv : int | sklearn cross_validation object (default 3)
+    cv : int | sklearn model_selection object (default 3)
         The cross validation method. Defaults to 3, which will
         internally trigger a default 3-fold shuffle split.
     scalings : dict | None (default None)
@@ -597,7 +615,8 @@ def compute_covariance(epochs, keep_sample_mean=True, tmin=None, tmax=None,
         have been processed with Maxwell filtering but not transformed
         to the same head position.
     verbose : bool | str | int | or None (default None)
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -631,13 +650,7 @@ def compute_covariance(epochs, keep_sample_mean=True, tmin=None, tmax=None,
            'in a list) are "%s" or None.' % '" or "'.join(accepted_methods))
 
     # scale to natural unit for best stability with MEG/EEG
-    if isinstance(scalings, dict):
-        for k, v in scalings.items():
-            if k not in ('mag', 'grad', 'eeg'):
-                raise ValueError('The keys in `scalings` must be "mag" or'
-                                 '"grad" or "eeg". You gave me: %s' % k)
-    scalings = _handle_default('scalings', scalings)
-
+    scalings = _check_scalings_user(scalings)
     _method_params = {
         'empirical': {'store_precision': False, 'assume_centered': True},
         'diagonal_fixed': {'grad': 0.01, 'mag': 0.01, 'eeg': 0.0,
@@ -698,7 +711,7 @@ def compute_covariance(epochs, keep_sample_mean=True, tmin=None, tmax=None,
 
     bads = epochs[0].info['bads']
     if projs is None:
-        projs = cp.deepcopy(epochs[0].info['projs'])
+        projs = epochs[0].info['projs']
         # make sure Epochs are compatible
         for epochs_t in epochs[1:]:
             if epochs_t.proj != epochs[0].proj:
@@ -706,8 +719,7 @@ def compute_covariance(epochs, keep_sample_mean=True, tmin=None, tmax=None,
             for proj_a, proj_b in zip(epochs_t.info['projs'], projs):
                 if not _proj_equal(proj_a, proj_b):
                     raise ValueError('Epochs must have same projectors')
-    else:
-        projs = cp.deepcopy(projs)
+    projs = _check_projs(projs)
     ch_names = epochs[0].ch_names
 
     # make sure Epochs are compatible
@@ -821,7 +833,7 @@ def compute_covariance(epochs, keep_sample_mean=True, tmin=None, tmax=None,
         cov.update(method=this_method, **data)
         covs.append(cov)
 
-    if ok_sklearn:
+    if ok_sklearn and len(covs) > 1:
         msg = ['log-likelihood on unseen data (descending order):']
         logliks = [(c['method'], c['loglik']) for c in covs]
         logliks.sort(reverse=True, key=lambda c: c[1])
@@ -829,11 +841,11 @@ def compute_covariance(epochs, keep_sample_mean=True, tmin=None, tmax=None,
             msg.append('%s: %0.3f' % (k, v))
         logger.info('\n   '.join(msg))
 
-    if ok_sklearn and not return_estimators:
+    if ok_sklearn and not return_estimators and len(covs) > 1:
         keys, scores = zip(*[(c['method'], c['loglik']) for c in covs])
         out = covs[np.argmax(scores)]
         logger.info('selecting best estimator: {0}'.format(out['method']))
-    elif ok_sklearn:
+    elif ok_sklearn and len(covs) > 1:
         out = covs
         out.sort(key=lambda c: c['loglik'], reverse=True)
     else:
@@ -842,10 +854,23 @@ def compute_covariance(epochs, keep_sample_mean=True, tmin=None, tmax=None,
     return out
 
 
+def _check_scalings_user(scalings):
+    if isinstance(scalings, dict):
+        for k, v in scalings.items():
+            if k not in ('mag', 'grad', 'eeg'):
+                raise ValueError('The keys in `scalings` must be "mag" or'
+                                 '"grad" or "eeg". You gave me: %s' % k)
+    elif scalings is not None and not isinstance(scalings, np.ndarray):
+        raise TypeError('scalings must be a dict, ndarray, or None, got %s'
+                        % type(scalings))
+    scalings = _handle_default('scalings', scalings)
+    return scalings
+
+
 def _compute_covariance_auto(data, method, info, method_params, cv,
                              scalings, n_jobs, stop_early, picks_list,
                              verbose):
-    """docstring for _compute_covariance_auto."""
+    """Compute covariance auto mode."""
     try:
         from sklearn.model_selection import GridSearchCV
     except Exception:  # XXX support sklearn < 0.18
@@ -857,7 +882,6 @@ def _compute_covariance_auto(data, method, info, method_params, cv,
     _apply_scaling_array(data.T, picks_list=picks_list, scalings=scalings)
     estimator_cov_info = list()
     msg = 'Estimating covariance using %s'
-    _RegCovariance, _ShrunkCovariance = _get_covariance_classes()
     for this_method in method:
         data_ = data.copy()
         name = this_method.__name__ if callable(this_method) else this_method
@@ -933,9 +957,13 @@ def _compute_covariance_auto(data, method, info, method_params, cv,
                              ' a .fit method')
         logger.info('Done.')
 
-    logger.info('Using cross-validation to select the best estimator.')
     estimators, _, _ = zip(*estimator_cov_info)
-    logliks = np.array([_cross_val(data, e, cv, n_jobs) for e in estimators])
+    if len(method) > 1:
+        logger.info('Using cross-validation to select the best estimator.')
+        logliks = np.array(
+            [_cross_val(data, e, cv, n_jobs) for e in estimators])
+    else:
+        logliks = [None]
 
     # undo scaling
     for c in estimator_cov_info:
@@ -976,7 +1004,7 @@ def _gaussian_loglik_scorer(est, X, y=None):
 
 
 def _cross_val(data, est, cv, n_jobs):
-    """Helper to compute cross validation."""
+    """Compute cross validation."""
     try:
         from sklearn.model_selection import cross_val_score
     except ImportError:
@@ -988,8 +1016,8 @@ def _cross_val(data, est, cv, n_jobs):
 
 def _auto_low_rank_model(data, mode, n_jobs, method_params, cv,
                          stop_early=True, verbose=None):
-    """compute latent variable models."""
-    method_params = cp.deepcopy(method_params)
+    """Compute latent variable models."""
+    method_params = deepcopy(method_params)
     iter_n_components = method_params.pop('iter_n_components')
     if iter_n_components is None:
         iter_n_components = np.arange(5, data.shape[1], 5)
@@ -1007,7 +1035,7 @@ def _auto_low_rank_model(data, mode, n_jobs, method_params, cv,
     scores.fill(np.nan)
 
     # make sure we don't empty the thing if it's a generator
-    max_n = max(list(cp.deepcopy(iter_n_components)))
+    max_n = max(list(deepcopy(iter_n_components)))
     if max_n > data.shape[1]:
         warn('You are trying to estimate %i components on matrix '
              'with %i features.' % (max_n, data.shape[1]))
@@ -1047,116 +1075,119 @@ def _auto_low_rank_model(data, mode, n_jobs, method_params, cv,
     return est, runtime_info
 
 
-def _get_covariance_classes():
-    """Prepare special cov estimators."""
-    from sklearn.covariance import (EmpiricalCovariance, shrunk_covariance,
-                                    ShrunkCovariance)
-
-    class _RegCovariance(EmpiricalCovariance):
-
-        """Aux class."""
-
-        def __init__(self, info, grad=0.01, mag=0.01, eeg=0.0,
-                     store_precision=False, assume_centered=False):
-            self.info = info
-            self.grad = grad
-            self.mag = mag
-            self.eeg = eeg
-            self.store_precision = store_precision
-            self.assume_centered = assume_centered
-
-        def fit(self, X):
-            EmpiricalCovariance.fit(self, X)
-            self.covariance_ = 0.5 * (self.covariance_ + self.covariance_.T)
-            cov_ = Covariance(
-                data=self.covariance_, names=self.info['ch_names'],
-                bads=self.info['bads'], projs=self.info['projs'],
-                nfree=len(self.covariance_))
-            cov_ = regularize(cov_, self.info, grad=self.grad, mag=self.mag,
-                              eeg=self.eeg, proj=False,
-                              exclude='bads')  # ~proj == important!!
-            self.covariance_ = cov_.data
-            return self
+###############################################################################
+# Sklearn Estimators
+
+class _RegCovariance(BaseEstimator):
+    """Aux class."""
+
+    def __init__(self, info, grad=0.01, mag=0.01, eeg=0.0,
+                 store_precision=False, assume_centered=False):
+        self.info = info
+        self.grad = grad
+        self.mag = mag
+        self.eeg = eeg
+        self.store_precision = store_precision
+        self.assume_centered = assume_centered
+
+    def fit(self, X):
+        """Fit covariance model with classical diagonal regularization."""
+        from sklearn.covariance import EmpiricalCovariance
+        self.estimator_ = EmpiricalCovariance(
+            store_precision=self.store_precision,
+            assume_centered=self.assume_centered)
+
+        self.covariance_ = self.estimator_.fit(X).covariance_
+        self.covariance_ = 0.5 * (self.covariance_ + self.covariance_.T)
+        cov_ = Covariance(
+            data=self.covariance_, names=self.info['ch_names'],
+            bads=self.info['bads'], projs=self.info['projs'],
+            nfree=len(self.covariance_))
+        cov_ = regularize(cov_, self.info, grad=self.grad, mag=self.mag,
+                          eeg=self.eeg, proj=False,
+                          exclude='bads')  # ~proj == important!!
+        self.estimator_.covariance_ = self.covariance_ = cov_.data
+        return self
 
-    class _ShrunkCovariance(ShrunkCovariance):
+    def score(self, X_test, y=None):
+        """Delegate call to modified EmpiricalCovariance instance."""
+        return self.estimator_.score(X_test, y=y)
 
-        """Aux class."""
+    def get_precision(self):
+        """Delegate call to modified EmpiricalCovariance instance."""
+        return self.estimator_.get_precision()
 
-        def __init__(self, store_precision, assume_centered, shrinkage=0.1):
-            self.store_precision = store_precision
-            self.assume_centered = assume_centered
-            self.shrinkage = shrinkage
 
-        def fit(self, X):
-            EmpiricalCovariance.fit(self, X)
-            cov = self.covariance_
+class _ShrunkCovariance(BaseEstimator):
+    """Aux class."""
 
-            if not isinstance(self.shrinkage, (list, tuple)):
-                shrinkage = [('all', self.shrinkage, np.arange(len(cov)))]
-            else:
-                shrinkage = self.shrinkage
-
-            zero_cross_cov = np.zeros_like(cov, dtype=bool)
-            for a, b in itt.combinations(shrinkage, 2):
-                picks_i, picks_j = a[2], b[2]
-                ch_ = a[0], b[0]
-                if 'eeg' in ch_:
-                    zero_cross_cov[np.ix_(picks_i, picks_j)] = True
-                    zero_cross_cov[np.ix_(picks_j, picks_i)] = True
-
-            self.zero_cross_cov_ = zero_cross_cov
-
-            # Apply shrinkage to blocks
-            for ch_type, c, picks in shrinkage:
-                sub_cov = cov[np.ix_(picks, picks)]
-                cov[np.ix_(picks, picks)] = shrunk_covariance(sub_cov,
-                                                              shrinkage=c)
-
-            # Apply shrinkage to cross-cov
-            for a, b in itt.combinations(shrinkage, 2):
-                shrinkage_i, shrinkage_j = a[1], b[1]
-                picks_i, picks_j = a[2], b[2]
-                c_ij = np.sqrt((1. - shrinkage_i) * (1. - shrinkage_j))
-                cov[np.ix_(picks_i, picks_j)] *= c_ij
-                cov[np.ix_(picks_j, picks_i)] *= c_ij
-
-            # Set to zero the necessary cross-cov
-            if np.any(zero_cross_cov):
-                cov[zero_cross_cov] = 0.0
-
-            self.covariance_ = cov
-            return self
+    def __init__(self, store_precision, assume_centered,
+                 shrinkage=0.1):
+
+        self.store_precision = store_precision
+        self.assume_centered = assume_centered
+        self.shrinkage = shrinkage
+
+    def fit(self, X):
+        """Fit covariance model with oracle shrinkage regularization."""
+        from sklearn.covariance import shrunk_covariance
+        from sklearn.covariance import EmpiricalCovariance
+        self.estimator_ = EmpiricalCovariance(
+            store_precision=self.store_precision,
+            assume_centered=self.assume_centered)
+
+        cov = self.estimator_.fit(X).covariance_
+
+        if not isinstance(self.shrinkage, (list, tuple)):
+            shrinkage = [('all', self.shrinkage, np.arange(len(cov)))]
+        else:
+            shrinkage = self.shrinkage
+
+        zero_cross_cov = np.zeros_like(cov, dtype=bool)
+        for a, b in itt.combinations(shrinkage, 2):
+            picks_i, picks_j = a[2], b[2]
+            ch_ = a[0], b[0]
+            if 'eeg' in ch_:
+                zero_cross_cov[np.ix_(picks_i, picks_j)] = True
+                zero_cross_cov[np.ix_(picks_j, picks_i)] = True
+
+        self.zero_cross_cov_ = zero_cross_cov
+
+        # Apply shrinkage to blocks
+        for ch_type, c, picks in shrinkage:
+            sub_cov = cov[np.ix_(picks, picks)]
+            cov[np.ix_(picks, picks)] = shrunk_covariance(sub_cov,
+                                                          shrinkage=c)
+
+        # Apply shrinkage to cross-cov
+        for a, b in itt.combinations(shrinkage, 2):
+            shrinkage_i, shrinkage_j = a[1], b[1]
+            picks_i, picks_j = a[2], b[2]
+            c_ij = np.sqrt((1. - shrinkage_i) * (1. - shrinkage_j))
+            cov[np.ix_(picks_i, picks_j)] *= c_ij
+            cov[np.ix_(picks_j, picks_i)] *= c_ij
+
+        # Set to zero the necessary cross-cov
+        if np.any(zero_cross_cov):
+            cov[zero_cross_cov] = 0.0
+
+        self.estimator_.covariance_ = self.covariance_ = cov
+        return self
+
+    def score(self, X_test, y=None):
+        """Delegate to modified EmpiricalCovariance instance."""
+        from sklearn.covariance import empirical_covariance, log_likelihood
+        # compute empirical covariance of the test set
+        test_cov = empirical_covariance(X_test - self.estimator_.location_,
+                                        assume_centered=True)
+        if np.any(self.zero_cross_cov_):
+            test_cov[self.zero_cross_cov_] = 0.
+        res = log_likelihood(test_cov, self.estimator_.get_precision())
+        return res
 
-        def score(self, X_test, y=None):
-            """Compute the log-likelihood of a Gaussian data set with
-            `self.covariance_` as an estimator of its covariance matrix.
-
-            Parameters
-            ----------
-            X_test : array-like, shape = [n_samples, n_features]
-                Test data of which we compute the likelihood, where n_samples
-                is the number of samples and n_features is the number of
-                features. X_test is assumed to be drawn from the same
-                distribution as the data used in fit (including centering).
-
-            y : not used, present for API consistence purpose.
-
-            Returns
-            -------
-            res : float
-                The likelihood of the data set with `self.covariance_` as an
-                estimator of its covariance matrix.
-            """
-            from sklearn.covariance import empirical_covariance, log_likelihood
-            # compute empirical covariance of the test set
-            test_cov = empirical_covariance(X_test - self.location_,
-                                            assume_centered=True)
-            if np.any(self.zero_cross_cov_):
-                test_cov[self.zero_cross_cov_] = 0.
-            res = log_likelihood(test_cov, self.get_precision())
-            return res
-
-    return _RegCovariance, _ShrunkCovariance
+    def get_precision(self):
+        """Delegate to modified EmpiricalCovariance instance."""
+        return self.estimator_.get_precision()
 
 
 ###############################################################################
@@ -1193,7 +1224,7 @@ def _unpack_epochs(epochs):
 
 
 def _get_ch_whitener(A, pca, ch_type, rank):
-    """"Get whitener params for a set of channels."""
+    """Get whitener params for a set of channels."""
     # whitening operator
     eig, eigvec = linalg.eigh(A, overwrite_a=True)
     eigvec = eigvec.T
@@ -1210,6 +1241,33 @@ def _get_ch_whitener(A, pca, ch_type, rank):
     return eig, eigvec
 
 
+def _get_whitener(noise_cov, info, ch_names, rank, pca, scalings=None):
+    #
+    #   Handle noise cov
+    #
+    noise_cov = prepare_noise_cov(noise_cov, info, ch_names, rank)
+    n_chan = len(ch_names)
+
+    #   Omit the zeroes due to projection
+    eig = noise_cov['eig']
+    nzero = (eig > 0)
+    n_nzero = np.sum(nzero)
+
+    if pca:
+        #   Rows of eigvec are the eigenvectors
+        whitener = noise_cov['eigvec'][nzero] / np.sqrt(eig[nzero])[:, None]
+        logger.info('Reducing data rank to %d' % n_nzero)
+    else:
+        whitener = np.zeros((n_chan, n_chan), dtype=np.float)
+        whitener[nzero, nzero] = 1.0 / np.sqrt(eig[nzero])
+        #   Rows of eigvec are the eigenvectors
+        whitener = np.dot(whitener, noise_cov['eigvec'])
+
+    logger.info('Total rank is %d' % n_nzero)
+
+    return whitener, noise_cov, n_nzero
+
+
 @verbose
 def prepare_noise_cov(noise_cov, info, ch_names, rank=None,
                       scalings=None, verbose=None):
@@ -1235,7 +1293,8 @@ def prepare_noise_cov(noise_cov, info, ch_names, rank=None,
             dict(mag=1e12, grad=1e11, eeg=1e5)
 
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -1253,7 +1312,8 @@ def prepare_noise_cov(noise_cov, info, ch_names, rank=None,
     scalings = _handle_default('scalings_cov_rank', scalings)
 
     # Create the projection operator
-    proj, ncomp, _ = make_projector(info['projs'], ch_names)
+    proj, ncomp, _ = make_projector(info['projs'] + noise_cov['projs'],
+                                    ch_names)
     if ncomp > 0:
         logger.info('    Created an SSP operator (subspace dimension = %d)'
                     % ncomp)
@@ -1300,25 +1360,30 @@ def prepare_noise_cov(noise_cov, info, ch_names, rank=None,
 
     if has_meg:
         C_meg = C[np.ix_(out_meg_idx, out_meg_idx)]
-        this_info = pick_info(info, info_pick_meg)
+        this_info = pick_info(_simplify_info(info), info_pick_meg, copy=False)
         if rank_meg is None:
             rank_meg = _estimate_rank_meeg_cov(C_meg, this_info, scalings)
         eig[out_meg_idx], eigvec[np.ix_(out_meg_idx, out_meg_idx)] = \
             _get_ch_whitener(C_meg, False, 'MEG', rank_meg)
     if has_eeg:
         C_eeg = C[np.ix_(out_eeg_idx, out_eeg_idx)]
-        this_info = pick_info(info, info_pick_eeg)
+        this_info = pick_info(_simplify_info(info), info_pick_eeg, copy=False)
         if rank_eeg is None:
             rank_eeg = _estimate_rank_meeg_cov(C_eeg, this_info, scalings)
         eig[out_eeg_idx], eigvec[np.ix_(out_eeg_idx, out_eeg_idx)], = \
             _get_ch_whitener(C_eeg, False, 'EEG', rank_eeg)
-    if _needs_eeg_average_ref_proj(info):
-        warn('No average EEG reference present in info["projs"], covariance '
-             'may be adversely affected. Consider recomputing covariance using'
-             ' a raw file with an average eeg reference projector added.')
-    noise_cov = cp.deepcopy(noise_cov)
-    noise_cov.update(data=C, eig=eig, eigvec=eigvec, dim=len(ch_names),
-                     diag=False, names=ch_names)
+        if _needs_eeg_average_ref_proj(info) and not \
+                _has_eeg_average_ref_proj(noise_cov['projs']):
+            warn('No average EEG reference present in info["projs"], '
+                 'covariance may be adversely affected. Consider recomputing '
+                 'covariance using with an average eeg reference projector '
+                 'added.')
+    noise_cov = Covariance(
+        data=C, names=ch_names, bads=list(noise_cov['bads']),
+        projs=deepcopy(noise_cov['projs']),
+        nfree=noise_cov['nfree'], eig=eig, eigvec=eigvec,
+        method=noise_cov.get('method', None),
+        loglik=noise_cov.get('loglik', None))
     return noise_cov
 
 
@@ -1330,12 +1395,12 @@ def regularize(cov, info, mag=0.1, grad=0.1, eeg=0.1, exclude='bads',
     channel type separately. Special care is taken to keep the
     rank of the data constant.
 
-    **Note:** This function is kept for reasons of backward-compatibility.
-    Please consider explicitly using the ``method`` parameter in
-    `compute_covariance` to directly combine estimation with regularization
-    in a data-driven fashion see the
-    `faq <http://martinos.org/mne/dev/faq.html#how-should-i-regularize-the-covariance-matrix>`_
-    for more information.
+    .. note:: This function is kept for reasons of backward-compatibility.
+              Please consider explicitly using the ``method`` parameter in
+              :func:`mne.compute_covariance` to directly combine estimation
+              with regularization in a data-driven fashion.
+              See the `faq <http://martinos.org/mne/dev/faq.html#how-should-i-regularize-the-covariance-matrix>`_
+              for more information.
 
     Parameters
     ----------
@@ -1355,7 +1420,7 @@ def regularize(cov, info, mag=0.1, grad=0.1, eeg=0.1, exclude='bads',
     proj : bool (default true)
         Apply or not projections to keep rank of data.
     verbose : bool | str | int | None (default None)
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`).
 
     Returns
     -------
@@ -1364,9 +1429,9 @@ def regularize(cov, info, mag=0.1, grad=0.1, eeg=0.1, exclude='bads',
 
     See Also
     --------
-    compute_covariance
-    """  # noqa
-    cov = cp.deepcopy(cov)
+    mne.compute_covariance
+    """  # noqa: E501
+    cov = cov.copy()
     info._check_consistency()
 
     if exclude is None:
@@ -1386,6 +1451,7 @@ def regularize(cov, info, mag=0.1, grad=0.1, eeg=0.1, exclude='bads',
     ch_names_eeg = [info_ch_names[i] for i in sel_eeg]
     ch_names_mag = [info_ch_names[i] for i in sel_mag]
     ch_names_grad = [info_ch_names[i] for i in sel_grad]
+    del sel_eeg, sel_mag, sel_grad
 
     # This actually removes bad channels from the cov, which is not backward
     # compatible, so let's leave all channels in
@@ -1518,7 +1584,8 @@ def _regularized_covariance(data, reg=None):
 
 @verbose
 def compute_whitener(noise_cov, info, picks=None, rank=None,
-                     scalings=None, verbose=None):
+                     scalings=None, return_rank=False,
+                     verbose=None):
     """Compute whitening matrix.
 
     Parameters
@@ -1538,8 +1605,13 @@ def compute_whitener(noise_cov, info, picks=None, rank=None,
     scalings : dict | None
         The rescaling method to be applied. See documentation of
         ``prepare_noise_cov`` for details.
+    return_rank : bool
+        If True, return the rank used to compute the whitener.
+
+        .. versionadded:: 0.15
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -1547,35 +1619,37 @@ def compute_whitener(noise_cov, info, picks=None, rank=None,
         The whitening matrix.
     ch_names : list
         The channel names.
+    rank : int
+        Rank reduction of the whitener. Returned only if return_rank is True.
     """
     if picks is None:
         picks = pick_types(info, meg=True, eeg=True, ref_meg=False,
                            exclude='bads')
 
-    ch_names = [info['chs'][k]['ch_name'] for k in picks]
+    ch_names = [info['ch_names'][k] for k in picks]
 
-    noise_cov = cp.deepcopy(noise_cov)
-    noise_cov = prepare_noise_cov(noise_cov, info, ch_names,
-                                  rank=rank, scalings=scalings)
-    n_chan = len(ch_names)
+    # XXX this relies on pick_channels, which does not respect order,
+    # so this could create problems if users have reordered their data
+    noise_cov = pick_channels_cov(noise_cov, include=ch_names, exclude=[])
+    if len(noise_cov['data']) != len(ch_names):
+        missing = list(set(ch_names) - set(noise_cov['names']))
+        raise RuntimeError('Not all channels present in noise covariance:\n%s'
+                           % missing)
 
-    W = np.zeros((n_chan, n_chan), dtype=np.float)
-    #
-    #   Omit the zeroes due to projection
-    #
-    eig = noise_cov['eig']
-    nzero = (eig > 0)
-    W[nzero, nzero] = 1.0 / np.sqrt(eig[nzero])
-    #
-    #   Rows of eigvec are the eigenvectors
-    #
-    W = np.dot(W, noise_cov['eigvec'])
+    scalings = _handle_default('scalings_cov_rank', scalings)
+    W, noise_cov, n_nzero = _get_whitener(noise_cov, info, ch_names, rank,
+                                          pca=False, scalings=scalings)
+
+    # Do the back projection
     W = np.dot(noise_cov['eigvec'].T, W)
-    return W, ch_names
+    out = W, ch_names
+    if return_rank:
+        out += (n_nzero,)
+    return out
 
 
 @verbose
-def whiten_evoked(evoked, noise_cov, picks=None, diag=False, rank=None,
+def whiten_evoked(evoked, noise_cov, picks=None, diag=None, rank=None,
                   scalings=None, verbose=None):
     """Whiten evoked data using given noise covariance.
 
@@ -1604,40 +1678,26 @@ def whiten_evoked(evoked, noise_cov, picks=None, diag=False, rank=None,
             dict(mag=1e12, grad=1e11, eeg=1e5)
 
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
     evoked_white : instance of Evoked
         The whitened evoked data.
     """
-    evoked = cp.deepcopy(evoked)
+    evoked = evoked.copy()
     if picks is None:
         picks = pick_types(evoked.info, meg=True, eeg=True)
-    W = _get_whitener_data(evoked.info, noise_cov, picks,
-                           diag=diag, rank=rank, scalings=scalings)
-    evoked.data[picks] = np.sqrt(evoked.nave) * np.dot(W, evoked.data[picks])
-    return evoked
-
 
- at verbose
-def _get_whitener_data(info, noise_cov, picks, diag=False, rank=None,
-                       scalings=None, verbose=None):
-    """Get whitening matrix for a set of data."""
-    ch_names = [info['ch_names'][k] for k in picks]
-    noise_cov = pick_channels_cov(noise_cov, include=ch_names, exclude=[])
-    if len(noise_cov['data']) != len(ch_names):
-        missing = list(set(ch_names) - set(noise_cov['names']))
-        raise RuntimeError('Not all channels present in noise covariance:\n%s'
-                           % missing)
     if diag:
-        noise_cov = cp.deepcopy(noise_cov)
-        noise_cov['data'] = np.diag(np.diag(noise_cov['data']))
+        noise_cov = noise_cov.as_diag()
 
-    scalings = _handle_default('scalings_cov_rank', scalings)
-    W = compute_whitener(noise_cov, info, picks=picks, rank=rank,
-                         scalings=scalings)[0]
-    return W
+    W, rank = compute_whitener(noise_cov, evoked.info, picks=picks,
+                               rank=rank, scalings=scalings)
+
+    evoked.data[picks] = np.sqrt(evoked.nave) * np.dot(W, evoked.data[picks])
+    return evoked
 
 
 @verbose
@@ -1820,12 +1880,17 @@ def _apply_scaling_array(data, picks_list, scalings):
         data *= scalings[:, np.newaxis]  # F - order
 
 
-def _undo_scaling_array(data, picks_list, scalings):
-    scalings = _check_scaling_inputs(data, picks_list, scalings)
+def _invert_scalings(scalings):
     if isinstance(scalings, dict):
         scalings = dict((k, 1. / v) for k, v in scalings.items())
     elif isinstance(scalings, np.ndarray):
         scalings = 1. / scalings
+    return scalings
+
+
+def _undo_scaling_array(data, picks_list, scalings):
+    scalings = _invert_scalings(_check_scaling_inputs(data, picks_list,
+                                                      scalings))
     return _apply_scaling_array(data, picks_list, scalings)
 
 
@@ -1856,11 +1921,8 @@ def _apply_scaling_cov(data, picks_list, scalings):
 
 
 def _undo_scaling_cov(data, picks_list, scalings):
-    scalings = _check_scaling_inputs(data, picks_list, scalings)
-    if isinstance(scalings, dict):
-        scalings = dict((k, 1. / v) for k, v in scalings.items())
-    elif isinstance(scalings, np.ndarray):
-        scalings = 1. / scalings
+    scalings = _invert_scalings(_check_scaling_inputs(data, picks_list,
+                                                      scalings))
     return _apply_scaling_cov(data, picks_list, scalings)
 
 
@@ -1932,7 +1994,7 @@ def _estimate_rank_meeg_signals(data, info, scalings, tol='auto',
 
 def _estimate_rank_meeg_cov(data, info, scalings, tol='auto',
                             return_singular=False):
-    """Estimate rank of M/EEG covariance data, given the covariance
+    """Estimate rank of M/EEG covariance data, given the covariance.
 
     Parameters
     ----------
diff --git a/mne/cuda.py b/mne/cuda.py
index f8d729e..d2660d2 100644
--- a/mne/cuda.py
+++ b/mne/cuda.py
@@ -14,7 +14,7 @@ _multiply_inplace_c128 = _halve_c128 = _real_c128 = None
 
 
 def _get_cudafft():
-    """Helper to deal with scikit-cuda namespace change"""
+    """Deal with scikit-cuda namespace change."""
     try:
         from skcuda import fft
     except ImportError:
@@ -26,7 +26,7 @@ def _get_cudafft():
 
 
 def get_cuda_memory():
-    """Get the amount of free memory for CUDA operations
+    """Get the amount of free memory for CUDA operations.
 
     Returns
     -------
@@ -43,7 +43,7 @@ def get_cuda_memory():
 
 
 def init_cuda(ignore_config=False):
-    """Initialize CUDA functionality
+    """Initialize CUDA functionality.
 
     This function attempts to load the necessary interfaces
     (hardware connectivity) to run CUDA-based filtering. This
@@ -64,14 +64,14 @@ def init_cuda(ignore_config=False):
     # Triage possible errors for informative messaging
     _cuda_capable = False
     try:
-        from pycuda import gpuarray, driver  # noqa
+        from pycuda import gpuarray, driver  # noqa: F401
         from pycuda.elementwise import ElementwiseKernel
     except ImportError:
         warn('module pycuda not found, CUDA not enabled')
         return
     try:
         # Initialize CUDA; happens with importing autoinit
-        import pycuda.autoinit  # noqa
+        import pycuda.autoinit  # noqa: F401
     except ImportError:
         warn('pycuda.autoinit could not be imported, likely a hardware error, '
              'CUDA not enabled%s' % _explain_exception())
@@ -107,7 +107,7 @@ def init_cuda(ignore_config=False):
 # Repeated FFT multiplication
 
 def setup_cuda_fft_multiply_repeated(n_jobs, h_fft):
-    """Set up repeated CUDA FFT multiplication with a given filter
+    """Set up repeated CUDA FFT multiplication with a given filter.
 
     Parameters
     ----------
@@ -181,7 +181,7 @@ def setup_cuda_fft_multiply_repeated(n_jobs, h_fft):
 
 
 def fft_multiply_repeated(h_fft, x, cuda_dict=dict(use_cuda=False)):
-    """Do FFT multiplication by a filter function (possibly using CUDA)
+    """Do FFT multiplication by a filter function (possibly using CUDA).
 
     Parameters
     ----------
@@ -219,7 +219,7 @@ def fft_multiply_repeated(h_fft, x, cuda_dict=dict(use_cuda=False)):
 # FFT Resampling
 
 def setup_cuda_fft_resample(n_jobs, W, new_len):
-    """Set up CUDA FFT resampling
+    """Set up CUDA FFT resampling.
 
     Parameters
     ----------
@@ -296,9 +296,9 @@ def setup_cuda_fft_resample(n_jobs, W, new_len):
     return n_jobs, cuda_dict, W
 
 
-def fft_resample(x, W, new_len, npads, to_removes,
-                 cuda_dict=dict(use_cuda=False)):
-    """Do FFT resampling with a filter function (possibly using CUDA)
+def fft_resample(x, W, new_len, npads, to_removes, cuda_dict=None,
+                 pad='reflect_limited'):
+    """Do FFT resampling with a filter function (possibly using CUDA).
 
     Parameters
     ----------
@@ -315,16 +315,24 @@ def fft_resample(x, W, new_len, npads, to_removes,
         Number of samples to remove after resampling.
     cuda_dict : dict
         Dictionary constructed using setup_cuda_multiply_repeated().
+    pad : str
+        The type of padding to use. Supports all :func:`np.pad` ``mode``
+        options. Can also be "reflect_limited" (default), which pads with a
+        reflected version of each vector mirrored on the first and last values
+        of the vector, followed by zeros.
+
+        .. versionadded:: 0.15
 
     Returns
     -------
     x : 1-d array
         Filtered version of x.
     """
+    cuda_dict = dict(use_cuda=False) if cuda_dict is None else cuda_dict
     # add some padding at beginning and end to make this work a little cleaner
     if x.dtype != np.float64:
         x = x.astype(np.float64)
-    x = _smart_pad(x, npads)
+    x = _smart_pad(x, npads, pad)
     old_len = len(x)
     shorter = new_len < old_len
     if not cuda_dict['use_cuda']:
@@ -381,15 +389,19 @@ def fft_resample(x, W, new_len, npads, to_removes,
 # Misc
 
 # this has to go in mne.cuda instead of mne.filter to avoid import errors
-def _smart_pad(x, n_pad):
-    """Pad vector x
-    """
+def _smart_pad(x, n_pad, pad='reflect_limited'):
+    """Pad vector x."""
+    n_pad = np.asarray(n_pad)
+    assert n_pad.shape == (2,)
     if (n_pad == 0).all():
         return x
     elif (n_pad < 0).any():
         raise RuntimeError('n_pad must be non-negative')
-    # need to pad with zeros if len(x) <= npad
-    l_z_pad = np.zeros(max(n_pad[0] - len(x) + 1, 0), dtype=x.dtype)
-    r_z_pad = np.zeros(max(n_pad[0] - len(x) + 1, 0), dtype=x.dtype)
-    return np.concatenate([l_z_pad, 2 * x[0] - x[n_pad[0]:0:-1], x,
-                           2 * x[-1] - x[-2:-n_pad[1] - 2:-1], r_z_pad])
+    if pad == 'reflect_limited':
+        # need to pad with zeros if len(x) <= npad
+        l_z_pad = np.zeros(max(n_pad[0] - len(x) + 1, 0), dtype=x.dtype)
+        r_z_pad = np.zeros(max(n_pad[0] - len(x) + 1, 0), dtype=x.dtype)
+        return np.concatenate([l_z_pad, 2 * x[0] - x[n_pad[0]:0:-1], x,
+                               2 * x[-1] - x[-2:-n_pad[1] - 2:-1], r_z_pad])
+    else:
+        return np.pad(x, (tuple(n_pad),), pad)
diff --git a/mne/data/__init__.py b/mne/data/__init__.py
index e69de29..6f92b46 100644
--- a/mne/data/__init__.py
+++ b/mne/data/__init__.py
@@ -0,0 +1 @@
+"""MNE-Python data."""
diff --git a/mne/data/coil_def.dat b/mne/data/coil_def.dat
index 3be7fff..d873733 100644
--- a/mne/data/coil_def.dat
+++ b/mne/data/coil_def.dat
@@ -35,6 +35,15 @@
 #
 #	mne_list_coil_def version 1.12 compiled at Jul 12 2016 18:39:09
 #
+#
+#	Coil definitions for artemis123 coils make use of integration points
+# according to the last formula in section 25.4.62 in the
+# "Handbook of Mathematical Functions: With Formulas,
+# Graphs, and Mathematical Tables" edited by Abramowitz and Stegun.
+# http://people.math.sfu.ca/~cbm/aands/abramowitz_and_stegun.pdf
+# They were generated using
+# gist: https://gist.github.com/bloyl/84a2dc04d77ef0f300aef397f57201f2
+#
 3   2       0   2  2.789e-02  1.620e-02	"Neuromag-122 planar gradiometer size = 27.89  mm base = 16.20  mm"
  61.7284  8.100e-03  0.000e+00  0.000e+00  0.000  0.000  1.000
 -61.7284 -8.100e-03  0.000e+00  0.000e+00  0.000  0.000  1.000
@@ -501,3 +510,72 @@
  -0.1250  4.082e-03 -7.071e-03  5.000e-02  0.000  0.000  1.000
  -0.1250 -4.082e-03  7.071e-03  5.000e-02  0.000  0.000  1.000
  -0.1250 -4.082e-03 -7.071e-03  5.000e-02  0.000  0.000  1.000
+2   7501   0    2  1.486e-02  5.740e-02 "Artemis123 axial gradiometer size = 1.486e-02 mm base = 5.740e-02 mm"
+  1.0000  0.000e+00  0.000e+00  0.000e+00  0.000  0.000  1.000
+ -1.0000  0.000e+00  0.000e+00  5.740e-02  0.000  0.000  1.000
+2   7501   1    8  1.486e-02  5.740e-02 "Artemis123 axial gradiometer size = 1.486e-02 mm base = 5.740e-02 mm"
+  0.2500  3.715e-03  3.715e-03  0.000e+00  0.000  0.000  1.000
+  0.2500  3.715e-03 -3.715e-03  0.000e+00  0.000  0.000  1.000
+  0.2500 -3.715e-03  3.715e-03  0.000e+00  0.000  0.000  1.000
+  0.2500 -3.715e-03 -3.715e-03  0.000e+00  0.000  0.000  1.000
+ -0.2500  3.715e-03  3.715e-03  5.740e-02  0.000  0.000  1.000
+ -0.2500  3.715e-03 -3.715e-03  5.740e-02  0.000  0.000  1.000
+ -0.2500 -3.715e-03  3.715e-03  5.740e-02  0.000  0.000  1.000
+ -0.2500 -3.715e-03 -3.715e-03  5.740e-02  0.000  0.000  1.000
+2   7501   2   14  1.486e-02  5.740e-02 "Artemis123 axial gradiometer size = 1.486e-02 mm base = 5.740e-02 mm"
+  0.2500  0.000e+00  0.000e+00  0.000e+00  0.000  0.000  1.000
+  0.1250  6.066e-03  0.000e+00  0.000e+00  0.000  0.000  1.000
+  0.1250 -6.066e-03  0.000e+00  0.000e+00  0.000  0.000  1.000
+  0.1250  3.033e-03  5.253e-03  0.000e+00  0.000  0.000  1.000
+  0.1250  3.033e-03 -5.253e-03  0.000e+00  0.000  0.000  1.000
+  0.1250 -3.033e-03  5.253e-03  0.000e+00  0.000  0.000  1.000
+  0.1250 -3.033e-03 -5.253e-03  0.000e+00  0.000  0.000  1.000
+ -0.2500  0.000e+00  0.000e+00  5.740e-02  0.000  0.000  1.000
+ -0.1250  6.066e-03  0.000e+00  5.740e-02  0.000  0.000  1.000
+ -0.1250 -6.066e-03  0.000e+00  5.740e-02  0.000  0.000  1.000
+ -0.1250  3.033e-03  5.253e-03  5.740e-02  0.000  0.000  1.000
+ -0.1250  3.033e-03 -5.253e-03  5.740e-02  0.000  0.000  1.000
+ -0.1250 -3.033e-03  5.253e-03  5.740e-02  0.000  0.000  1.000
+ -0.1250 -3.033e-03 -5.253e-03  5.740e-02  0.000  0.000  1.000
+1   7502   0    1  1.485e-02  0.000e+00 "Artemis123 reference magnetometer size = 1.485e-02 mm"
+  1.0000  0.000e+00  0.000e+00  0.000e+00  0.000  0.000  1.000
+1   7502   1    4  1.485e-02  0.000e+00 "Artemis123 reference magnetometer size = 1.485e-02 mm"
+  0.2500  3.713e-03  3.713e-03  0.000e+00  0.000  0.000  1.000
+  0.2500  3.713e-03 -3.713e-03  0.000e+00  0.000  0.000  1.000
+  0.2500 -3.713e-03  3.713e-03  0.000e+00  0.000  0.000  1.000
+  0.2500 -3.713e-03 -3.713e-03  0.000e+00  0.000  0.000  1.000
+1   7502   2    7  1.485e-02  0.000e+00 "Artemis123 reference magnetometer size = 1.485e-02 mm"
+  0.2500  0.000e+00  0.000e+00  0.000e+00  0.000  0.000  1.000
+  0.1250  6.064e-03  0.000e+00  0.000e+00  0.000  0.000  1.000
+  0.1250 -6.064e-03  0.000e+00  0.000e+00  0.000  0.000  1.000
+  0.1250  3.032e-03  5.251e-03  0.000e+00  0.000  0.000  1.000
+  0.1250  3.032e-03 -5.251e-03  0.000e+00  0.000  0.000  1.000
+  0.1250 -3.032e-03  5.251e-03  0.000e+00  0.000  0.000  1.000
+  0.1250 -3.032e-03 -5.251e-03  0.000e+00  0.000  0.000  1.000
+2   7503   0    2  1.486e-02  3.000e-02 "Artemis123 reference axial gradiometer size = 1.486e-02 mm base = 3.000e-02 mm"
+  1.0000  0.000e+00  0.000e+00  0.000e+00  0.000  0.000  1.000
+ -1.0000  0.000e+00  0.000e+00  3.000e-02  0.000  0.000  1.000
+2   7503   1    8  1.486e-02  3.000e-02 "Artemis123 reference axial gradiometer size = 1.486e-02 mm base = 3.000e-02 mm"
+  0.2500  3.715e-03  3.715e-03  0.000e+00  0.000  0.000  1.000
+  0.2500  3.715e-03 -3.715e-03  0.000e+00  0.000  0.000  1.000
+  0.2500 -3.715e-03  3.715e-03  0.000e+00  0.000  0.000  1.000
+  0.2500 -3.715e-03 -3.715e-03  0.000e+00  0.000  0.000  1.000
+ -0.2500  3.715e-03  3.715e-03  3.000e-02  0.000  0.000  1.000
+ -0.2500  3.715e-03 -3.715e-03  3.000e-02  0.000  0.000  1.000
+ -0.2500 -3.715e-03  3.715e-03  3.000e-02  0.000  0.000  1.000
+ -0.2500 -3.715e-03 -3.715e-03  3.000e-02  0.000  0.000  1.000
+2   7503   2   14  1.486e-02  3.000e-02 "Artemis123 reference axial gradiometer size = 1.486e-02 mm base = 3.000e-02 mm"
+  0.2500  0.000e+00  0.000e+00  0.000e+00  0.000  0.000  1.000
+  0.1250  6.066e-03  0.000e+00  0.000e+00  0.000  0.000  1.000
+  0.1250 -6.066e-03  0.000e+00  0.000e+00  0.000  0.000  1.000
+  0.1250  3.033e-03  5.253e-03  0.000e+00  0.000  0.000  1.000
+  0.1250  3.033e-03 -5.253e-03  0.000e+00  0.000  0.000  1.000
+  0.1250 -3.033e-03  5.253e-03  0.000e+00  0.000  0.000  1.000
+  0.1250 -3.033e-03 -5.253e-03  0.000e+00  0.000  0.000  1.000
+ -0.2500  0.000e+00  0.000e+00  3.000e-02  0.000  0.000  1.000
+ -0.1250  6.066e-03  0.000e+00  3.000e-02  0.000  0.000  1.000
+ -0.1250 -6.066e-03  0.000e+00  3.000e-02  0.000  0.000  1.000
+ -0.1250  3.033e-03  5.253e-03  3.000e-02  0.000  0.000  1.000
+ -0.1250  3.033e-03 -5.253e-03  3.000e-02  0.000  0.000  1.000
+ -0.1250 -3.033e-03  5.253e-03  3.000e-02  0.000  0.000  1.000
+ -0.1250 -3.033e-03 -5.253e-03  3.000e-02  0.000  0.000  1.000
diff --git a/mne/data/fsaverage/fsaverage-fiducials.fif b/mne/data/fsaverage/fsaverage-fiducials.fif
new file mode 100644
index 0000000..faaae97
Binary files /dev/null and b/mne/data/fsaverage/fsaverage-fiducials.fif differ
diff --git a/mne/data/fsaverage/fsaverage-head.fif b/mne/data/fsaverage/fsaverage-head.fif
new file mode 100644
index 0000000..b66d16a
Binary files /dev/null and b/mne/data/fsaverage/fsaverage-head.fif differ
diff --git a/mne/data/fsaverage/fsaverage-inner_skull-bem.fif b/mne/data/fsaverage/fsaverage-inner_skull-bem.fif
new file mode 100644
index 0000000..6f23294
Binary files /dev/null and b/mne/data/fsaverage/fsaverage-inner_skull-bem.fif differ
diff --git a/mne/data/fsaverage/fsaverage-trans.fif b/mne/data/fsaverage/fsaverage-trans.fif
new file mode 100644
index 0000000..92b5573
Binary files /dev/null and b/mne/data/fsaverage/fsaverage-trans.fif differ
diff --git a/mne/data/image/custom_layout.lout b/mne/data/image/custom_layout.lout
index a2f8ed8..9d38435 100644
--- a/mne/data/image/custom_layout.lout
+++ b/mne/data/image/custom_layout.lout
@@ -1,24 +1,23 @@
-    0.00     0.00     0.08     0.04
-000     0.13     0.34     0.07     0.05 0
-001     0.16     0.49     0.07     0.05 1
-002     0.18     0.63     0.07     0.05 2
-003     0.25     0.52     0.07     0.05 3
-004     0.32     0.63     0.07     0.05 4
-005     0.34     0.49     0.07     0.05 5
-006     0.36     0.35     0.07     0.05 6
-007     0.44     0.35     0.07     0.05 7
-008     0.45     0.49     0.07     0.05 8
-009     0.45     0.63     0.07     0.05 9
-010     0.48     0.53     0.07     0.05 10
-011     0.52     0.44     0.07     0.05 11
-012     0.56     0.35     0.07     0.05 12
-013     0.56     0.49     0.07     0.05 13
-014     0.56     0.63     0.07     0.05 14
-015     0.69     0.62     0.07     0.05 15
-016     0.69     0.55     0.07     0.05 16
-017     0.69     0.49     0.07     0.05 17
-018     0.69     0.42     0.07     0.05 18
-019     0.69     0.34     0.07     0.05 19
-020     0.77     0.35     0.07     0.05 20
-021     0.77     0.49     0.07     0.05 21
-022     0.77     0.63     0.07     0.05 22
+    0.00     0.00     0.03     0.03
+000     0.49     0.74     0.07     0.05 0
+001     0.50     0.77     0.07     0.05 1
+002     0.53     0.79     0.07     0.05 2
+003     0.56     0.75     0.07     0.05 3
+004     0.62     0.76     0.07     0.05 4
+005     0.61     0.72     0.07     0.05 5
+006     0.58     0.70     0.07     0.05 6
+007     0.56     0.65     0.07     0.05 7
+008     0.53     0.60     0.07     0.05 8
+009     0.50     0.55     0.07     0.05 9
+010     0.58     0.57     0.07     0.05 10
+011     0.61     0.49     0.07     0.05 11
+012     0.63     0.55     0.07     0.05 12
+013     0.65     0.60     0.07     0.05 13
+014     0.74     0.41     0.07     0.05 14
+015     0.70     0.45     0.07     0.05 15
+016     0.65     0.48     0.07     0.05 16
+017     0.63     0.42     0.07     0.05 17
+018     0.67     0.39     0.07     0.05 18
+019     0.60     0.36     0.07     0.05 19
+020     0.64     0.33     0.07     0.05 20
+021     0.69     0.32     0.07     0.05 21
diff --git a/mne/datasets/__init__.py b/mne/datasets/__init__.py
index b97da34..e94e552 100644
--- a/mne/datasets/__init__.py
+++ b/mne/datasets/__init__.py
@@ -1,14 +1,20 @@
-"""Demo datasets
+"""Functions for fetching remote datasets.
+
+See `datasets`_ for more information.
 """
 
+from . import fieldtrip_cmc
 from . import brainstorm
+from . import visual_92_categories
 from . import eegbci
+from . import hf_sef
 from . import megsim
 from . import misc
+from . import mtrf
 from . import sample
 from . import somato
 from . import multimodal
 from . import spm_face
 from . import testing
 from . import _fake
-from .utils import _download_all_example_data
+from .utils import _download_all_example_data, fetch_hcp_mmp_parcellation
diff --git a/mne/datasets/_fake/__init__.py b/mne/datasets/_fake/__init__.py
index b807fc4..57b8d21 100644
--- a/mne/datasets/_fake/__init__.py
+++ b/mne/datasets/_fake/__init__.py
@@ -1,4 +1,3 @@
-"""MNE sample dataset
-"""
+"""Fake dataset for testing."""
 
 from ._fake import data_path, get_version
diff --git a/mne/datasets/_fake/_fake.py b/mne/datasets/_fake/_fake.py
index 580253b..bbd5df5 100644
--- a/mne/datasets/_fake/_fake.py
+++ b/mne/datasets/_fake/_fake.py
@@ -10,7 +10,7 @@ from ..utils import (_data_path, _data_path_doc,
 
 @verbose
 def data_path(path=None, force_update=False, update_path=False,
-              download=True, verbose=None):
+              download=True, verbose=None):  # noqa: D103
     return _data_path(path=path, force_update=force_update,
                       update_path=update_path, name='fake',
                       download=download)
@@ -19,7 +19,7 @@ data_path.__doc__ = _data_path_doc.format(name='fake',
                                           conf='MNE_DATASETS_FAKE_PATH')
 
 
-def get_version():
+def get_version():  # noqa: D103
     return _get_version('fake')
 
 get_version.__doc__ = _version_doc.format(name='fake')
diff --git a/mne/datasets/brainstorm/__init__.py b/mne/datasets/brainstorm/__init__.py
index 9e129ad..8dcf9b7 100644
--- a/mne/datasets/brainstorm/__init__.py
+++ b/mne/datasets/brainstorm/__init__.py
@@ -1,5 +1,4 @@
-"""Brainstorm Dataset
-"""
+"""Brainstorm datasets."""
 
 from . import (bst_raw, bst_resting, bst_auditory, bst_phantom_ctf,
                bst_phantom_elekta)
diff --git a/mne/datasets/brainstorm/bst_auditory.py b/mne/datasets/brainstorm/bst_auditory.py
index 22af474..0b656a7 100644
--- a/mne/datasets/brainstorm/bst_auditory.py
+++ b/mne/datasets/brainstorm/bst_auditory.py
@@ -3,7 +3,6 @@
 # License: BSD (3-clause)
 
 from functools import partial
-import os.path as op
 
 from ...utils import verbose
 from ..utils import (has_dataset, _data_path, _get_version, _version_doc,
@@ -30,15 +29,11 @@ URL: http://neuroimage.usc.edu/brainstorm/DatasetAuditory
 
 @verbose
 def data_path(path=None, force_update=False, update_path=True, download=True,
-              verbose=None):
-    archive_name = dict(brainstorm='bst_auditory.tar.gz')
-    data_path = _data_path(path=path, force_update=force_update,
-                           update_path=update_path, name='brainstorm',
-                           download=download, archive_name=archive_name)
-    if data_path != '':
-        return op.join(data_path, 'bst_auditory')
-    else:
-        return data_path
+              verbose=None):  # noqa: D103
+    return _data_path(path=path, force_update=force_update,
+                      update_path=update_path, name='brainstorm',
+                      download=download, archive_name='bst_auditory.tar.gz')
+
 
 _data_path_doc = _data_path_doc.format(name='brainstorm',
                                        conf='MNE_DATASETS_BRAINSTORM_DATA'
@@ -48,14 +43,14 @@ _data_path_doc = _data_path_doc.replace('brainstorm dataset',
 data_path.__doc__ = _data_path_doc
 
 
-def get_version():
+def get_version():  # noqa: D103
     return _get_version('brainstorm')
 
+
 get_version.__doc__ = _version_doc.format(name='brainstorm')
 
 
 def description():
-    """Get description of brainstorm (bst_auditory) dataset
-    """
+    """Get description of brainstorm (bst_auditory) dataset."""
     for desc in _description.splitlines():
         print(desc)
diff --git a/mne/datasets/brainstorm/bst_phantom_ctf.py b/mne/datasets/brainstorm/bst_phantom_ctf.py
index ab10b63..2141691 100644
--- a/mne/datasets/brainstorm/bst_phantom_ctf.py
+++ b/mne/datasets/brainstorm/bst_phantom_ctf.py
@@ -3,7 +3,6 @@
 # License: BSD (3-clause)
 
 from functools import partial
-import os.path as op
 
 from ...utils import verbose
 from ..utils import (has_dataset, _data_path, _get_version, _version_doc,
@@ -19,15 +18,11 @@ URL: http://neuroimage.usc.edu/brainstorm/Tutorials/PhantomCtf
 
 @verbose
 def data_path(path=None, force_update=False, update_path=True, download=True,
-              verbose=None):
-    archive_name = dict(brainstorm='bst_phantom_ctf.tar.gz')
-    data_path = _data_path(path=path, force_update=force_update,
-                           update_path=update_path, name='brainstorm',
-                           download=download, archive_name=archive_name)
-    if data_path != '':
-        return op.join(data_path, 'bst_phantom_ctf')
-    else:
-        return data_path
+              verbose=None):  # noqa: D103
+    return _data_path(path=path, force_update=force_update,
+                      update_path=update_path, name='brainstorm',
+                      download=download, archive_name='bst_phantom_ctf.tar.gz')
+
 
 _data_path_doc = _data_path_doc.format(name='brainstorm',
                                        conf='MNE_DATASETS_BRAINSTORM_DATA'
@@ -37,14 +32,14 @@ _data_path_doc = _data_path_doc.replace('brainstorm dataset',
 data_path.__doc__ = _data_path_doc
 
 
-def get_version():
+def get_version():  # noqa: D103
     return _get_version('brainstorm')
 
+
 get_version.__doc__ = _version_doc.format(name='brainstorm')
 
 
 def description():
-    """Get description of brainstorm (bst_phantom_ctf) dataset
-    """
+    """Get description of brainstorm (bst_phantom_ctf) dataset."""
     for desc in _description.splitlines():
         print(desc)
diff --git a/mne/datasets/brainstorm/bst_phantom_elekta.py b/mne/datasets/brainstorm/bst_phantom_elekta.py
index 3c66b8b..3f321e6 100644
--- a/mne/datasets/brainstorm/bst_phantom_elekta.py
+++ b/mne/datasets/brainstorm/bst_phantom_elekta.py
@@ -3,7 +3,6 @@
 # License: BSD (3-clause)
 
 from functools import partial
-import os.path as op
 
 from ...utils import verbose
 from ..utils import (has_dataset, _data_path, _get_version, _version_doc,
@@ -19,15 +18,12 @@ URL: http://neuroimage.usc.edu/brainstorm/Tutorials/PhantomElekta
 
 @verbose
 def data_path(path=None, force_update=False, update_path=True, download=True,
-              verbose=None):
-    archive_name = dict(brainstorm='bst_phantom_elekta.tar.gz')
-    data_path = _data_path(path=path, force_update=force_update,
-                           update_path=update_path, name='brainstorm',
-                           download=download, archive_name=archive_name)
-    if data_path != '':
-        return op.join(data_path, 'bst_phantom_elekta')
-    else:
-        return data_path
+              verbose=None):  # noqa: D103
+    return _data_path(path=path, force_update=force_update,
+                      update_path=update_path, name='brainstorm',
+                      download=download,
+                      archive_name='bst_phantom_elekta.tar.gz')
+
 
 _data_path_doc = _data_path_doc.format(name='brainstorm',
                                        conf='MNE_DATASETS_BRAINSTORM_DATA'
@@ -38,14 +34,14 @@ _data_path_doc = _data_path_doc.replace('brainstorm dataset',
 data_path.__doc__ = _data_path_doc
 
 
-def get_version():
+def get_version():  # noqa: D103
     return _get_version('brainstorm')
 
+
 get_version.__doc__ = _version_doc.format(name='brainstorm')
 
 
 def description():
-    """Get description of brainstorm (bst_phantom_elekta) dataset
-    """
+    """Get description of brainstorm (bst_phantom_elekta) dataset."""
     for desc in _description.splitlines():
         print(desc)
diff --git a/mne/datasets/brainstorm/bst_raw.py b/mne/datasets/brainstorm/bst_raw.py
index 6d94854..348b90f 100644
--- a/mne/datasets/brainstorm/bst_raw.py
+++ b/mne/datasets/brainstorm/bst_raw.py
@@ -3,7 +3,6 @@
 # License: BSD (3-clause)
 
 from functools import partial
-import os.path as op
 
 from ...utils import verbose
 from ..utils import (has_dataset, _data_path, _get_version, _version_doc,
@@ -28,15 +27,10 @@ URL: http://neuroimage.usc.edu/brainstorm/DatasetMedianNerveCtf
 
 @verbose
 def data_path(path=None, force_update=False, update_path=True, download=True,
-              verbose=None):
-    archive_name = dict(brainstorm='bst_raw.tar.gz')
-    data_path = _data_path(path=path, force_update=force_update,
-                           update_path=update_path, name='brainstorm',
-                           download=download, archive_name=archive_name)
-    if data_path != '':
-        return op.join(data_path, 'bst_raw')
-    else:
-        return data_path
+              verbose=None):    # noqa: D103
+    return _data_path(path=path, force_update=force_update,
+                      update_path=update_path, name='brainstorm',
+                      download=download, archive_name='bst_raw.tar.gz')
 
 
 _data_path_doc = _data_path_doc.format(name='brainstorm',
@@ -47,14 +41,14 @@ _data_path_doc = _data_path_doc.replace('brainstorm dataset',
 data_path.__doc__ = _data_path_doc
 
 
-def get_version():
+def get_version():  # noqa: D103
     return _get_version('brainstorm')
 
+
 get_version.__doc__ = _version_doc.format(name='brainstorm')
 
 
-def description():
-    """Get description of brainstorm (bst_raw) dataset
-    """
+def description():  # noqa: D103
+    """Get description of brainstorm (bst_raw) dataset."""
     for desc in _description.splitlines():
         print(desc)
diff --git a/mne/datasets/brainstorm/bst_resting.py b/mne/datasets/brainstorm/bst_resting.py
index 2077fd3..64fb32f 100644
--- a/mne/datasets/brainstorm/bst_resting.py
+++ b/mne/datasets/brainstorm/bst_resting.py
@@ -3,7 +3,6 @@
 # License: BSD (3-clause)
 
 from functools import partial
-import os.path as op
 
 from ...utils import verbose
 from ..utils import (has_dataset, _data_path, _get_version, _version_doc,
@@ -21,15 +20,11 @@ URL: http://neuroimage.usc.edu/brainstorm/DatasetResting
 
 @verbose
 def data_path(path=None, force_update=False, update_path=True, download=True,
-              verbose=None):
-    archive_name = dict(brainstorm='bst_resting.tar.gz')
-    data_path = _data_path(path=path, force_update=force_update,
-                           update_path=update_path, name='brainstorm',
-                           download=download, archive_name=archive_name)
-    if data_path != '':
-        return op.join(data_path, 'bst_resting')
-    else:
-        return data_path
+              verbose=None):  # noqa: D103
+    return _data_path(path=path, force_update=force_update,
+                      update_path=update_path, name='brainstorm',
+                      download=download, archive_name='bst_resting.tar.gz')
+
 
 _data_path_doc = _data_path_doc.format(name='brainstorm',
                                        conf='MNE_DATASETS_BRAINSTORM_DATA'
@@ -39,14 +34,14 @@ _data_path_doc = _data_path_doc.replace('brainstorm dataset',
 data_path.__doc__ = _data_path_doc
 
 
-def get_version():
+def get_version():  # noqa: D103
     return _get_version('brainstorm')
 
+
 get_version.__doc__ = _version_doc.format(name='brainstorm')
 
 
 def description():
-    """Get description of brainstorm (bst_resting) dataset
-    """
+    """Get description of brainstorm (bst_resting) dataset."""
     for desc in _description.splitlines():
         print(desc)
diff --git a/mne/datasets/eegbci/__init__.py b/mne/datasets/eegbci/__init__.py
index 4a47873..de68ecf 100644
--- a/mne/datasets/eegbci/__init__.py
+++ b/mne/datasets/eegbci/__init__.py
@@ -1,4 +1,3 @@
-"""EEG Motor Movement/Imagery Dataset
-"""
+"""EEG Motor Movement/Imagery Dataset."""
 
 from .eegbci import data_path, load_data
diff --git a/mne/datasets/eegbci/eegbci.py b/mne/datasets/eegbci/eegbci.py
index 274b66e..f77b80c 100644
--- a/mne/datasets/eegbci/eegbci.py
+++ b/mne/datasets/eegbci/eegbci.py
@@ -14,10 +14,10 @@ EEGMI_URL = 'http://www.physionet.org/physiobank/database/eegmmidb/'
 @verbose
 def data_path(url, path=None, force_update=False, update_path=None,
               verbose=None):
-    """Get path to local copy of EEGMMI dataset URL
+    """Get path to local copy of EEGMMI dataset URL.
 
     This is a low-level function useful for getting a local copy of a
-    remote EEGBCI dataet.
+    remote EEGBCI dataset [1]_ which is available at PhysioNet [2]_.
 
     Parameters
     ----------
@@ -26,10 +26,9 @@ def data_path(url, path=None, force_update=False, update_path=None,
     path : None | str
         Location of where to look for the EEGBCI data storing location.
         If None, the environment variable or config parameter
-        MNE_DATASETS_EEGBCI_PATH is used. If it doesn't exist, the
-        "mne-python/examples" directory is used. If the EEGBCI dataset
-        is not found under the given path (e.g., as
-        "mne-python/examples/MNE-eegbci-data"), the data
+        ``MNE_DATASETS_EEGBCI_PATH`` is used. If it doesn't exist, the
+        "~/mne_data" directory is used. If the EEGBCI dataset
+        is not found under the given path, the data
         will be automatically downloaded to the specified folder.
     force_update : bool
         Force update of the dataset even if a local copy exists.
@@ -37,7 +36,7 @@ def data_path(url, path=None, force_update=False, update_path=None,
         If True, set the MNE_DATASETS_EEGBCI_PATH in mne-python
         config to the given path. If None, the user is prompted.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`).
 
     Returns
     -------
@@ -57,17 +56,17 @@ def data_path(url, path=None, force_update=False, update_path=None,
     and prompt the user to save the 'datasets' path to the mne-python config,
     if it isn't there already.
 
-    The EEGBCI dataset is documented in the following publication:
-        Schalk, G., McFarland, D.J., Hinterberger, T., Birbaumer, N.,
-        Wolpaw, J.R. (2004) BCI2000: A General-Purpose Brain-Computer Interface
-        (BCI) System. IEEE TBME 51(6):1034-1043
-    The data set is available at PhysioNet:
-        Goldberger AL, Amaral LAN, Glass L, Hausdorff JM, Ivanov PCh, Mark RG,
-        Mietus JE, Moody GB, Peng C-K, Stanley HE. (2000) PhysioBank,
-        PhysioToolkit, and PhysioNet: Components of a New Research Resource for
-        Complex Physiologic Signals. Circulation 101(23):e215-e220
-    """  # noqa
-
+    References
+    ----------
+    .. [1] Schalk, G., McFarland, D.J., Hinterberger, T., Birbaumer, N.,
+           Wolpaw, J.R. (2004) BCI2000: A General-Purpose Brain-Computer
+           Interface (BCI) System. IEEE TBME 51(6):1034-1043
+    .. [2] Goldberger AL, Amaral LAN, Glass L, Hausdorff JM, Ivanov PCh,
+           Mark RG, Mietus JE, Moody GB, Peng C-K, Stanley HE. (2000)
+           PhysioBank, PhysioToolkit, and PhysioNet: Components of a New
+           Research Resource for Complex Physiologic Signals.
+           Circulation 101(23):e215-e220
+    """  # noqa: E501
     key = 'MNE_DATASETS_EEGBCI_PATH'
     name = 'EEGBCI'
     path = _get_path(path, key, name)
@@ -89,31 +88,36 @@ def data_path(url, path=None, force_update=False, update_path=None,
 
 @verbose
 def load_data(subject, runs, path=None, force_update=False, update_path=None,
-              base_url=EEGMI_URL, verbose=None):
-    """Get paths to local copy of EEGBCI dataset files
+              base_url=EEGMI_URL, verbose=None):  # noqa: D301
+    """Get paths to local copies of EEGBCI dataset files.
+
+    This will fetch data for the EEGBCI dataset [1]_, which is also
+    available at PhysioNet [2]_.
 
     Parameters
     ----------
     subject : int
         The subject to use. Can be in the range of 1-109 (inclusive).
-    runs : int | list of ints
-        The runs to use. Can be a list or a single number. The runs correspond
-        to the following tasks:
-              run | task
-        ----------+-----------------------------------------
-                1 | Baseline, eyes open
-                2 | Baseline, eyes closed
-         3, 7, 11 | Motor execution: left vs right hand
-         4, 8, 12 | Motor imagery: left vs right hand
-         5, 9, 13 | Motor execution: hands vs feet
-        6, 10, 14 | Motor imagery: hands vs feet
+    runs : int | list of int
+        The runs to use. The runs correspond to:
+
+        =========  ===================================
+        run        task
+        =========  ===================================
+        1          Baseline, eyes open
+        2          Baseline, eyes closed
+        3, 7, 11   Motor execution: left vs right hand
+        4, 8, 12   Motor imagery: left vs right hand
+        5, 9, 13   Motor execution: hands vs feet
+        6, 10, 14  Motor imagery: hands vs feet
+        =========  ===================================
+
     path : None | str
         Location of where to look for the EEGBCI data storing location.
         If None, the environment variable or config parameter
-        MNE_DATASETS_EEGBCI_PATH is used. If it doesn't exist, the
-        "mne-python/examples" directory is used. If the EEGBCI dataset
-        is not found under the given path (e.g., as
-        "mne-python/examples/MEGSIM"), the data
+        ``MNE_DATASETS_EEGBCI_PATH`` is used. If it doesn't exist, the
+        "~/mne_data" directory is used. If the EEGBCI dataset
+        is not found under the given path, the data
         will be automatically downloaded to the specified folder.
     force_update : bool
         Force update of the dataset even if a local copy exists.
@@ -121,7 +125,8 @@ def load_data(subject, runs, path=None, force_update=False, update_path=None,
         If True, set the MNE_DATASETS_EEGBCI_PATH in mne-python
         config to the given path. If None, the user is prompted.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -141,15 +146,16 @@ def load_data(subject, runs, path=None, force_update=False, update_path=None,
     user to save the 'datasets' path to the  mne-python config, if it isn't
     there already.
 
-    The EEGBCI dataset is documented in the following publication:
-        Schalk, G., McFarland, D.J., Hinterberger, T., Birbaumer, N.,
-        Wolpaw, J.R. (2004) BCI2000: A General-Purpose Brain-Computer Interface
-        (BCI) System. IEEE TBME 51(6):1034-1043
-    The data set is available at PhysioNet:
-        Goldberger AL, Amaral LAN, Glass L, Hausdorff JM, Ivanov PCh, Mark RG,
-        Mietus JE, Moody GB, Peng C-K, Stanley HE. (2000) PhysioBank,
-        PhysioToolkit, and PhysioNet: Components of a New Research Resource for
-        Complex Physiologic Signals. Circulation 101(23):e215-e220
+    References
+    ----------
+    .. [1] Schalk, G., McFarland, D.J., Hinterberger, T., Birbaumer, N.,
+           Wolpaw, J.R. (2004) BCI2000: A General-Purpose Brain-Computer
+           Interface (BCI) System. IEEE TBME 51(6):1034-1043
+    .. [2] Goldberger AL, Amaral LAN, Glass L, Hausdorff JM, Ivanov PCh,
+           Mark RG, Mietus JE, Moody GB, Peng C-K, Stanley HE. (2000)
+           PhysioBank, PhysioToolkit, and PhysioNet: Components of a New
+           Research Resource for Complex Physiologic Signals.
+           Circulation 101(23):e215-e220
     """
     if not hasattr(runs, '__iter__'):
         runs = [runs]
diff --git a/mne/datasets/fieldtrip_cmc/__init__.py b/mne/datasets/fieldtrip_cmc/__init__.py
new file mode 100644
index 0000000..328d81f
--- /dev/null
+++ b/mne/datasets/fieldtrip_cmc/__init__.py
@@ -0,0 +1,3 @@
+"""fieldtrip Cortico-Muscular Coherence (CMC) Dataset."""
+
+from .fieldtrip_cmc import data_path, get_version
diff --git a/mne/datasets/fieldtrip_cmc/fieldtrip_cmc.py b/mne/datasets/fieldtrip_cmc/fieldtrip_cmc.py
new file mode 100644
index 0000000..5695c48
--- /dev/null
+++ b/mne/datasets/fieldtrip_cmc/fieldtrip_cmc.py
@@ -0,0 +1,32 @@
+# Authors: Chris Holdgraf <choldgraf at berkeley.edu>
+#          Alexandre Barachant <alexandre.barachant at gmail.com>
+#
+# License: BSD Style.
+
+from functools import partial
+
+from ...utils import verbose
+from ..utils import (has_dataset, _data_path, _data_path_doc,
+                     _get_version, _version_doc)
+
+
+data_name = "fieldtrip_cmc"
+conf_name = "MNE_DATASETS_FIELDTRIP_CMC_PATH"
+has_mtrf_data = partial(has_dataset, name=data_name)
+
+
+ at verbose
+def data_path(path=None, force_update=False, update_path=True, download=True,
+              verbose=None):  # noqa: D103
+    return _data_path(path=path, force_update=force_update,
+                      update_path=update_path, name=data_name,
+                      download=download)
+
+data_path.__doc__ = _data_path_doc.format(name=data_name,
+                                          conf=conf_name)
+
+
+def get_version():  # noqa: D103
+    return _get_version(data_name)
+
+get_version.__doc__ = _version_doc.format(name=data_name)
diff --git a/mne/datasets/hf_sef/__init__.py b/mne/datasets/hf_sef/__init__.py
new file mode 100644
index 0000000..08fe8ca
--- /dev/null
+++ b/mne/datasets/hf_sef/__init__.py
@@ -0,0 +1,3 @@
+"""HF-SEF dataset."""
+
+from .hf_sef import data_path
diff --git a/mne/datasets/hf_sef/hf_sef.py b/mne/datasets/hf_sef/hf_sef.py
new file mode 100644
index 0000000..fd44644
--- /dev/null
+++ b/mne/datasets/hf_sef/hf_sef.py
@@ -0,0 +1,99 @@
+#!/usr/bin/env python2
+# -*- coding: utf-8 -*-
+# Authors: Jussi Nurminen <jnu at iki.fi>
+# License: BSD Style.
+
+
+import tarfile
+import os.path as op
+import os
+from ...utils import _fetch_file, verbose
+from ..utils import _get_path, logger, _do_path_update
+
+
+ at verbose
+def data_path(dataset='evoked', path=None, force_update=False,
+              update_path=True, verbose=None):
+    u"""Get path to local copy of the high frequency SEF dataset.
+
+    Gets a local copy of the high frequency SEF MEG dataset [1]_.
+
+    Parameters
+    ----------
+    dataset : 'evoked' | 'raw'
+        Whether to get the main dataset (evoked, structural and the rest) or
+        the separate dataset containing raw MEG data only.
+    path : None | str
+        Where to look for the HF-SEF data storing location.
+        If None, the environment variable or config parameter
+        ``MNE_DATASETS_HF_SEF_PATH`` is used. If it doesn't exist, the
+        "~/mne_data" directory is used. If the HF-SEF dataset
+        is not found under the given path, the data
+        will be automatically downloaded to the specified folder.
+    force_update : bool
+        Force update of the dataset even if a local copy exists.
+    update_path : bool | None
+        If True, set the MNE_DATASETS_HF_SEF_PATH in mne-python
+        config to the given path. If None, the user is prompted.
+    verbose : bool, str, int, or None
+        If not None, override default verbose level (see :func:`mne.verbose`).
+
+    Returns
+    -------
+    path : str
+        Local path to the directory where the HF-SEF data is stored.
+
+    References
+    ----------
+    .. [1] Nurminen, J., Paananen, H., Mäkelä, J. (2017): High frequency
+           somatosensory MEG dataset. https://doi.org/10.5281/zenodo.889234
+    """
+    key = 'MNE_DATASETS_HF_SEF_PATH'
+    name = 'HF_SEF'
+    path = _get_path(path, key, name)
+    destdir = op.join(path, 'HF_SEF')
+
+    urls = {'evoked':
+            'https://zenodo.org/record/889235/files/hf_sef_evoked.tar.gz',
+            'raw':
+            'https://zenodo.org/record/889296/files/hf_sef_raw.tar.gz'}
+    if dataset not in urls:
+        raise ValueError('Invalid dataset specified')
+    url = urls[dataset]
+    fn = url.split('/')[-1]  # pick the filename from the url
+    archive = op.join(destdir, fn)
+
+    # check for existence of evoked and raw sets
+    has = dict()
+    subjdir = op.join(destdir, 'subjects')
+    megdir_a = op.join(destdir, 'MEG', 'subject_a')
+    has['evoked'] = op.isdir(destdir) and op.isdir(subjdir)
+    has['raw'] = op.isdir(destdir) and any(['raw' in fn_ for fn_ in
+                                            os.listdir(megdir_a)])
+
+    if not has[dataset] or force_update:
+        if not op.isdir(destdir):
+            os.mkdir(destdir)
+        _fetch_file(url, archive)
+
+        with tarfile.open(archive) as tar:
+            logger.info('Decompressing %s' % archive)
+            for member in tar.getmembers():
+                # strip the leading dirname 'hf_sef/' from the archive paths
+                # this should be fixed when making next version of archives
+                member.name = member.name[7:]
+                try:
+                    tar.extract(member, destdir)
+                except IOError:
+                    # check whether file exists but could not be overwritten
+                    fn_full = op.join(destdir, member.name)
+                    if op.isfile(fn_full):
+                        os.remove(fn_full)
+                        tar.extract(member, destdir)
+                    else:  # some more sinister cause for IOError
+                        raise
+
+        os.remove(archive)
+
+    path = _do_path_update(path, update_path, key, name)
+    return destdir
diff --git a/mne/datasets/megsim/__init__.py b/mne/datasets/megsim/__init__.py
index 24babeb..0b3d69f 100644
--- a/mne/datasets/megsim/__init__.py
+++ b/mne/datasets/megsim/__init__.py
@@ -1,4 +1,3 @@
-"""MEGSIM dataset
-"""
+"""MEGSIM dataset."""
 
 from .megsim import data_path, load_data
diff --git a/mne/datasets/megsim/megsim.py b/mne/datasets/megsim/megsim.py
index 88338cd..006538e 100644
--- a/mne/datasets/megsim/megsim.py
+++ b/mne/datasets/megsim/megsim.py
@@ -15,10 +15,10 @@ from .urls import (url_match, valid_data_types, valid_data_formats,
 @verbose
 def data_path(url, path=None, force_update=False, update_path=None,
               verbose=None):
-    """Get path to local copy of MEGSIM dataset URL
+    """Get path to local copy of MEGSIM dataset URL.
 
     This is a low-level function useful for getting a local copy of a
-    remote MEGSIM dataset.
+    remote MEGSIM dataset [1]_.
 
     Parameters
     ----------
@@ -27,10 +27,9 @@ def data_path(url, path=None, force_update=False, update_path=None,
     path : None | str
         Location of where to look for the MEGSIM data storing location.
         If None, the environment variable or config parameter
-        MNE_DATASETS_MEGSIM_PATH is used. If it doesn't exist, the
-        "mne-python/examples" directory is used. If the MEGSIM dataset
-        is not found under the given path (e.g., as
-        "mne-python/examples/MEGSIM"), the data
+        ``MNE_DATASETS_MEGSIM_PATH`` is used. If it doesn't exist, the
+        "~/mne_data" directory is used. If the MEGSIM dataset
+        is not found under the given path, the data
         will be automatically downloaded to the specified folder.
     force_update : bool
         Force update of the dataset even if a local copy exists.
@@ -38,7 +37,7 @@ def data_path(url, path=None, force_update=False, update_path=None,
         If True, set the MNE_DATASETS_MEGSIM_PATH in mne-python
         config to the given path. If None, the user is prompted.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`).
 
     Returns
     -------
@@ -59,12 +58,13 @@ def data_path(url, path=None, force_update=False, update_path=None,
     folder, and prompt the user to save the 'datasets' path to the mne-python
     config, if it isn't there already.
 
-    The MEGSIM dataset is documented in the following publication:
-        Aine CJ, Sanfratello L, Ranken D, Best E, MacArthur JA, Wallace T,
-        Gilliam K, Donahue CH, Montano R, Bryant JE, Scott A, Stephen JM
-        (2012) MEG-SIM: A Web Portal for Testing MEG Analysis Methods using
-        Realistic Simulated and Empirical Data. Neuroinform 10:141-158
-    """  # noqa
+    References
+    ----------
+    .. [1] Aine CJ, Sanfratello L, Ranken D, Best E, MacArthur JA, Wallace T,
+           Gilliam K, Donahue CH, Montano R, Bryant JE, Scott A, Stephen JM
+           (2012) MEG-SIM: A Web Portal for Testing MEG Analysis Methods using
+           Realistic Simulated and Empirical Data. Neuroinform 10:141-158
+    """  # noqa: E501
     key = 'MNE_DATASETS_MEGSIM_PATH'
     name = 'MEGSIM'
     path = _get_path(path, key, name)
@@ -103,7 +103,9 @@ def data_path(url, path=None, force_update=False, update_path=None,
 @verbose
 def load_data(condition='visual', data_format='raw', data_type='experimental',
               path=None, force_update=False, update_path=None, verbose=None):
-    """Get path to local copy of MEGSIM dataset type
+    """Get path to local copy of MEGSIM dataset type.
+
+    The MEGSIM dataset is described in [1]_.
 
     Parameters
     ----------
@@ -116,18 +118,17 @@ def load_data(condition='visual', data_format='raw', data_type='experimental',
     path : None | str
         Location of where to look for the MEGSIM data storing location.
         If None, the environment variable or config parameter
-        MNE_DATASETS_MEGSIM_PATH is used. If it doesn't exist, the
-        "mne-python/examples" directory is used. If the MEGSIM dataset
-        is not found under the given path (e.g., as
-        "mne-python/examples/MEGSIM"), the data
+        ``MNE_DATASETS_MEGSIM_PATH`` is used. If it doesn't exist, the
+        "~/mne_data" directory is used. If the MEGSIM dataset
+        is not found under the given path, the data
         will be automatically downloaded to the specified folder.
     force_update : bool
         Force update of the dataset even if a local copy exists.
     update_path : bool | None
-        If True, set the MNE_DATASETS_MEGSIM_PATH in mne-python
+        If True, set the ``MNE_DATASETS_MEGSIM_PATH`` in mne-python
         config to the given path. If None, the user is prompted.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`).
 
     Returns
     -------
@@ -145,13 +146,13 @@ def load_data(condition='visual', data_format='raw', data_type='experimental',
     'datasets' folder, and prompt the user to save the 'datasets' path to the
     mne-python config, if it isn't there already.
 
-    The MEGSIM dataset is documented in the following publication:
-        Aine CJ, Sanfratello L, Ranken D, Best E, MacArthur JA, Wallace T,
-        Gilliam K, Donahue CH, Montano R, Bryant JE, Scott A, Stephen JM
-        (2012) MEG-SIM: A Web Portal for Testing MEG Analysis Methods using
-        Realistic Simulated and Empirical Data. Neuroinform 10:141-158
-    """  # noqa
-
+    References
+    ----------
+    .. [1] Aine CJ, Sanfratello L, Ranken D, Best E, MacArthur JA, Wallace T,
+           Gilliam K, Donahue CH, Montano R, Bryant JE, Scott A, Stephen JM
+           (2012) MEG-SIM: A Web Portal for Testing MEG Analysis Methods using
+           Realistic Simulated and Empirical Data. Neuroinform 10:141-158
+    """  # noqa: E501
     if not condition.lower() in valid_conditions:
         raise ValueError('Unknown condition "%s"' % condition)
     if data_format not in valid_data_formats:
diff --git a/mne/datasets/megsim/urls.py b/mne/datasets/megsim/urls.py
index 0c316ec..3d572af 100644
--- a/mne/datasets/megsim/urls.py
+++ b/mne/datasets/megsim/urls.py
@@ -27,7 +27,7 @@ urls = ['/empdata/neuromag/visual/subject1_day1_vis_raw.fif',
         '/simdata/neuromag/visual/M87174545_vis_sim5_4mm_30na_neuro_rn.fif',
 
         '/simdata_singleTrials/subject1_singleTrials_VisWorkingMem_fif.zip',
-        '/simdata_singleTrials/subject1_singleTrials_VisWorkingMem_withOsc_fif.zip',  # noqa
+        '/simdata_singleTrials/subject1_singleTrials_VisWorkingMem_withOsc_fif.zip',  # noqa: E501
         '/simdata_singleTrials/4545_sim_oscOnly_v1_IPS_ILOG_30hzAdded.fif',
 
         '/index.html',
@@ -158,7 +158,7 @@ conditions = np.atleast_1d(conditions)
 
 
 def url_match(condition, data_format, data_type):
-    """Function to match MEGSIM data files"""
+    """Match MEGSIM data files."""
     inds = np.logical_and(conditions == condition, data_formats == data_format)
     inds = np.logical_and(inds, data_types == data_type)
     inds = np.logical_and(inds, data_formats == data_format)
@@ -173,7 +173,7 @@ def url_match(condition, data_format, data_type):
 
 
 def _load_all_data():
-    """Helper for downloading all megsim datasets."""
+    """Download all megsim datasets."""
     from .megsim import data_path
     for url in urls:
         data_path(url_root + url)
diff --git a/mne/datasets/misc/__init__.py b/mne/datasets/misc/__init__.py
index 195a1a9..921759f 100644
--- a/mne/datasets/misc/__init__.py
+++ b/mne/datasets/misc/__init__.py
@@ -1,4 +1,3 @@
-"""MNE misc dataset
-"""
+"""MNE misc dataset."""
 
 from ._misc import data_path
diff --git a/mne/datasets/misc/_misc.py b/mne/datasets/misc/_misc.py
index 22dac64..f51f18c 100644
--- a/mne/datasets/misc/_misc.py
+++ b/mne/datasets/misc/_misc.py
@@ -9,7 +9,7 @@ from ..utils import _data_path, _data_path_doc
 
 @verbose
 def data_path(path=None, force_update=False, update_path=True,
-              download=True, verbose=None):
+              download=True, verbose=None):  # noqa: D103
     return _data_path(path=path, force_update=force_update,
                       update_path=update_path, name='misc',
                       download=download)
diff --git a/mne/datasets/mtrf/__init__.py b/mne/datasets/mtrf/__init__.py
new file mode 100644
index 0000000..27318ce
--- /dev/null
+++ b/mne/datasets/mtrf/__init__.py
@@ -0,0 +1,3 @@
+"""mTRF Dataset."""
+
+from .mtrf import data_path
diff --git a/mne/datasets/mtrf/mtrf.py b/mne/datasets/mtrf/mtrf.py
new file mode 100644
index 0000000..a11369f
--- /dev/null
+++ b/mne/datasets/mtrf/mtrf.py
@@ -0,0 +1,30 @@
+# Authors: Chris Holdgraf <choldgraf at berkeley.edu>
+#
+# License: BSD Style.
+
+from functools import partial
+
+from ...utils import verbose
+from ..utils import (has_dataset, _data_path, _data_path_doc,
+                     _get_version, _version_doc)
+
+
+data_name = 'mtrf'
+has_mtrf_data = partial(has_dataset, name=data_name)
+
+
+ at verbose
+def data_path(path=None, force_update=False, update_path=True, download=True,
+              verbose=None):  # noqa: D103
+    return _data_path(path=path, force_update=force_update,
+                      update_path=update_path, name=data_name,
+                      download=download)
+
+data_path.__doc__ = _data_path_doc.format(name=data_name,
+                                          conf='MNE_DATASETS_MTRF_PATH')
+
+
+def get_version():  # noqa: D103
+    return _get_version(data_name)
+
+get_version.__doc__ = _version_doc.format(name=data_name)
diff --git a/mne/datasets/multimodal/__init__.py b/mne/datasets/multimodal/__init__.py
index 947071e..d297ad7 100644
--- a/mne/datasets/multimodal/__init__.py
+++ b/mne/datasets/multimodal/__init__.py
@@ -1,4 +1,3 @@
-"""Multimodal dataset
-"""
+"""Multimodal dataset."""
 
 from .multimodal import data_path, has_multimodal_data, get_version
diff --git a/mne/datasets/multimodal/multimodal.py b/mne/datasets/multimodal/multimodal.py
index d8eb31a..6d0f0b0 100644
--- a/mne/datasets/multimodal/multimodal.py
+++ b/mne/datasets/multimodal/multimodal.py
@@ -15,7 +15,7 @@ has_multimodal_data = partial(has_dataset, name='multimodal')
 
 @verbose
 def data_path(path=None, force_update=False, update_path=True, download=True,
-              verbose=None):
+              verbose=None):  # noqa: D103
     return _data_path(path=path, force_update=force_update,
                       update_path=update_path, name='multimodal',
                       download=download)
@@ -24,7 +24,7 @@ data_path.__doc__ = _data_path_doc.format(name='multimodal',
                                           conf='MNE_DATASETS_MULTIMODAL_PATH')
 
 
-def get_version():
+def get_version():  # noqa: D103
     return _get_version('multimodal')
 
 get_version.__doc__ = _version_doc.format(name='multimodal')
diff --git a/mne/datasets/sample/__init__.py b/mne/datasets/sample/__init__.py
index 6b1faf2..b848a90 100644
--- a/mne/datasets/sample/__init__.py
+++ b/mne/datasets/sample/__init__.py
@@ -1,5 +1,4 @@
-"""MNE sample dataset
-"""
+"""MNE sample dataset."""
 
 from .sample import (data_path, has_sample_data, get_version,
                      requires_sample_data)
diff --git a/mne/datasets/sample/sample.py b/mne/datasets/sample/sample.py
index 68b977d..0f7d10c 100644
--- a/mne/datasets/sample/sample.py
+++ b/mne/datasets/sample/sample.py
@@ -17,7 +17,7 @@ has_sample_data = partial(has_dataset, name='sample')
 
 @verbose
 def data_path(path=None, force_update=False, update_path=True, download=True,
-              verbose=None):
+              verbose=None):  # noqa: D103
     return _data_path(path=path, force_update=force_update,
                       update_path=update_path, name='sample',
                       download=download)
@@ -26,7 +26,7 @@ data_path.__doc__ = _data_path_doc.format(name='sample',
                                           conf='MNE_DATASETS_SAMPLE_PATH')
 
 
-def get_version():
+def get_version():  # noqa: D103
     return _get_version('sample')
 
 get_version.__doc__ = _version_doc.format(name='sample')
diff --git a/mne/datasets/somato/__init__.py b/mne/datasets/somato/__init__.py
index aa3f82d..c4f93b2 100644
--- a/mne/datasets/somato/__init__.py
+++ b/mne/datasets/somato/__init__.py
@@ -1,4 +1,3 @@
-"""Somatosensory dataset
-"""
+"""Somatosensory dataset."""
 
 from .somato import data_path, has_somato_data, get_version
diff --git a/mne/datasets/somato/somato.py b/mne/datasets/somato/somato.py
index fd11302..33f88c1 100644
--- a/mne/datasets/somato/somato.py
+++ b/mne/datasets/somato/somato.py
@@ -15,7 +15,7 @@ has_somato_data = partial(has_dataset, name='somato')
 
 @verbose
 def data_path(path=None, force_update=False, update_path=True, download=True,
-              verbose=None):
+              verbose=None):  # noqa: D103
     return _data_path(path=path, force_update=force_update,
                       update_path=update_path, name='somato',
                       download=download)
@@ -24,7 +24,7 @@ data_path.__doc__ = _data_path_doc.format(name='somato',
                                           conf='MNE_DATASETS_SOMATO_PATH')
 
 
-def get_version():
+def get_version():  # noqa: D103
     return _get_version('somato')
 
 get_version.__doc__ = _version_doc.format(name='somato')
diff --git a/mne/datasets/spm_face/__init__.py b/mne/datasets/spm_face/__init__.py
index 5da98fd..dfe2edd 100644
--- a/mne/datasets/spm_face/__init__.py
+++ b/mne/datasets/spm_face/__init__.py
@@ -1,4 +1,3 @@
-"""SPM face dataset
-"""
+"""SPM face dataset."""
 
 from .spm_data import data_path, has_spm_data, get_version, requires_spm_data
diff --git a/mne/datasets/spm_face/spm_data.py b/mne/datasets/spm_face/spm_data.py
index c476d2a..9fc1d01 100644
--- a/mne/datasets/spm_face/spm_data.py
+++ b/mne/datasets/spm_face/spm_data.py
@@ -16,7 +16,7 @@ has_spm_data = partial(has_dataset, name='spm')
 
 @verbose
 def data_path(path=None, force_update=False, update_path=True, download=True,
-              verbose=None):
+              verbose=None):  # noqa: D103
     return _data_path(path=path, force_update=force_update,
                       update_path=update_path, name='spm',
                       download=download)
@@ -25,7 +25,7 @@ data_path.__doc__ = _data_path_doc.format(name='spm',
                                           conf='MNE_DATASETS_SPM_DATA_PATH')
 
 
-def get_version():
+def get_version():  # noqa: D103
     return _get_version('spm')
 
 get_version.__doc__ = _version_doc.format(name='spm')
diff --git a/mne/datasets/testing/__init__.py b/mne/datasets/testing/__init__.py
index c12ea77..53560be 100644
--- a/mne/datasets/testing/__init__.py
+++ b/mne/datasets/testing/__init__.py
@@ -1,4 +1,3 @@
-"""MNE testing dataset
-"""
+"""MNE testing dataset."""
 
 from ._testing import data_path, requires_testing_data, get_version
diff --git a/mne/datasets/testing/_testing.py b/mne/datasets/testing/_testing.py
index 658a963..5c01161 100644
--- a/mne/datasets/testing/_testing.py
+++ b/mne/datasets/testing/_testing.py
@@ -17,7 +17,7 @@ has_testing_data = partial(has_dataset, name='testing')
 
 @verbose
 def data_path(path=None, force_update=False, update_path=True,
-              download=True, verbose=None):
+              download=True, verbose=None):  # noqa: D103
     # Make sure we don't do something stupid
     if download and \
             get_config('MNE_SKIP_TESTING_DATASET_TESTS', 'false') == 'true':
@@ -30,7 +30,7 @@ data_path.__doc__ = _data_path_doc.format(name='testing',
                                           conf='MNE_DATASETS_TESTING_PATH')
 
 
-def get_version():
+def get_version():  # noqa: D103
     return _get_version('testing')
 
 get_version.__doc__ = _version_doc.format(name='testing')
diff --git a/mne/datasets/tests/test_datasets.py b/mne/datasets/tests/test_datasets.py
index cdfdef1..9d1ce74 100644
--- a/mne/datasets/tests/test_datasets.py
+++ b/mne/datasets/tests/test_datasets.py
@@ -8,10 +8,10 @@ from mne.utils import _TempDir, run_tests_if_main, requires_good_network
 
 
 def test_datasets():
-    """Test simple dataset functions
-    """
+    """Test simple dataset functions."""
     for dname in ('sample', 'somato', 'spm_face', 'testing',
-                  'bst_raw', 'bst_auditory', 'bst_resting'):
+                  'bst_raw', 'bst_auditory', 'bst_resting',
+                  'visual_92_categories', 'fieldtrip_cmc'):
         if dname.startswith('bst'):
             dataset = getattr(datasets.brainstorm, dname)
         else:
@@ -33,8 +33,7 @@ def test_datasets():
 
 @requires_good_network
 def test_megsim():
-    """Test MEGSIM URL handling
-    """
+    """Test MEGSIM URL handling."""
     data_dir = _TempDir()
     paths = datasets.megsim.load_data(
         'index', 'text', 'text', path=data_dir, update_path=False)
@@ -44,8 +43,7 @@ def test_megsim():
 
 @requires_good_network
 def test_downloads():
-    """Test dataset URL handling
-    """
+    """Test dataset URL handling."""
     # Try actually downloading a dataset
     data_dir = _TempDir()
     path = datasets._fake.data_path(path=data_dir, update_path=False)
diff --git a/mne/datasets/utils.py b/mne/datasets/utils.py
index 3ea20c4..33f4076 100644
--- a/mne/datasets/utils.py
+++ b/mne/datasets/utils.py
@@ -4,35 +4,41 @@
 #          Denis Egnemann <denis.engemann at gmail.com>
 # License: BSD Style.
 
+from collections import OrderedDict
 import os
 import os.path as op
 import shutil
 import tarfile
 import stat
 import sys
+import zipfile
+from distutils.version import LooseVersion
+
+import numpy as np
 
 from .. import __version__ as mne_version
-from ..utils import get_config, set_config, _fetch_file, logger, warn, verbose
+from ..label import read_labels_from_annot, Label, write_labels_to_annot
+from ..utils import (get_config, set_config, _fetch_file, logger, warn,
+                     verbose, get_subjects_dir, md5sum)
 from ..externals.six import string_types
 from ..externals.six.moves import input
 
 
-_data_path_doc = """Get path to local copy of {name} dataset
+_data_path_doc = """Get path to local copy of {name} dataset.
 
     Parameters
     ----------
     path : None | str
         Location of where to look for the {name} dataset.
         If None, the environment variable or config parameter
-        {conf} is used. If it doesn't exist, the
-        "mne-python/examples" directory is used. If the {name} dataset
-        is not found under the given path (e.g., as
-        "mne-python/examples/MNE-{name}-data"), the data
+        ``{conf}`` is used. If it doesn't exist, the
+        "~/mne_data" directory is used. If the {name} dataset
+        is not found under the given path, the data
         will be automatically downloaded to the specified folder.
     force_update : bool
         Force update of the {name} dataset even if a local copy exists.
     update_path : bool | None
-        If True, set the {conf} in mne-python
+        If True, set the ``{conf}`` in mne-python
         config to the given path. If None, the user is prompted.
     download : bool
         If False and the {name} dataset has not been downloaded yet,
@@ -40,7 +46,7 @@ _data_path_doc = """Get path to local copy of {name} dataset
         '' (empty string). This is mostly used for debugging purposes
         and can be safely ignored by most users.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`).
 
     Returns
     -------
@@ -49,7 +55,7 @@ _data_path_doc = """Get path to local copy of {name} dataset
 """
 
 
-_version_doc = """Get version of the local {name} dataset
+_version_doc = """Get version of the local {name} dataset.
 
     Returns
     -------
@@ -78,9 +84,69 @@ If you reference this dataset in your publications, please:
 For questions, please contact Francois Tadel (francois.tadel at mcgill.ca).
 """
 
+_hcp_mmp_license_text = """
+License
+-------
+I request access to data collected by the Washington University - University
+of Minnesota Consortium of the Human Connectome Project (WU-Minn HCP), and
+I agree to the following:
+
+1. I will not attempt to establish the identity of or attempt to contact any
+   of the included human subjects.
+
+2. I understand that under no circumstances will the code that would link
+   these data to Protected Health Information be given to me, nor will any
+   additional information about individual human subjects be released to me
+   under these Open Access Data Use Terms.
+
+3. I will comply with all relevant rules and regulations imposed by my
+   institution. This may mean that I need my research to be approved or
+   declared exempt by a committee that oversees research on human subjects,
+   e.g. my IRB or Ethics Committee. The released HCP data are not considered
+   de-identified, insofar as certain combinations of HCP Restricted Data
+   (available through a separate process) might allow identification of
+   individuals.  Different committees operate under different national, state
+   and local laws and may interpret regulations differently, so it is
+   important to ask about this. If needed and upon request, the HCP will
+   provide a certificate stating that you have accepted the HCP Open Access
+   Data Use Terms.
+
+4. I may redistribute original WU-Minn HCP Open Access data and any derived
+   data as long as the data are redistributed under these same Data Use Terms.
+
+5. I will acknowledge the use of WU-Minn HCP data and data derived from
+   WU-Minn HCP data when publicly presenting any results or algorithms
+   that benefitted from their use.
+
+   1. Papers, book chapters, books, posters, oral presentations, and all
+      other printed and digital presentations of results derived from HCP
+      data should contain the following wording in the acknowledgments
+      section: "Data were provided [in part] by the Human Connectome
+      Project, WU-Minn Consortium (Principal Investigators: David Van Essen
+      and Kamil Ugurbil; 1U54MH091657) funded by the 16 NIH Institutes and
+      Centers that support the NIH Blueprint for Neuroscience Research; and
+      by the McDonnell Center for Systems Neuroscience at Washington
+      University."
+
+   2. Authors of publications or presentations using WU-Minn HCP data
+      should cite relevant publications describing the methods used by the
+      HCP to acquire and process the data. The specific publications that
+      are appropriate to cite in any given study will depend on what HCP
+      data were used and for what purposes. An annotated and appropriately
+      up-to-date list of publications that may warrant consideration is
+      available at http://www.humanconnectome.org/about/acknowledgehcp.html
+
+   3. The WU-Minn HCP Consortium as a whole should not be included as an
+      author of publications or presentations if this authorship would be
+      based solely on the use of WU-Minn HCP data.
+
+6. Failure to abide by these guidelines will result in termination of my
+   privileges to access WU-Minn HCP data.
+"""
+
 
 def _dataset_version(path, name):
-    """Get the version of the dataset"""
+    """Get the version of the dataset."""
     ver_fname = op.join(path, 'version.txt')
     if op.exists(ver_fname):
         with open(ver_fname, 'r') as fid:
@@ -94,7 +160,7 @@ def _dataset_version(path, name):
 
 
 def _get_path(path, key, name):
-    """Helper to get a dataset path"""
+    """Get a dataset path."""
     # 1. Input
     if path is not None:
         if not isinstance(path, string_types):
@@ -124,7 +190,7 @@ def _get_path(path, key, name):
 
 
 def _do_path_update(path, update_path, key, name):
-    """Helper to update path"""
+    """Update path."""
     path = op.abspath(path)
     if update_path is None:
         if get_config(key, '') != path:
@@ -149,8 +215,7 @@ def _do_path_update(path, update_path, key, name):
 def _data_path(path=None, force_update=False, update_path=True, download=True,
                name=None, check_version=False, return_version=False,
                archive_name=None):
-    """Aux function
-    """
+    """Aux function."""
     key = {
         'fake': 'MNE_DATASETS_FAKE_PATH',
         'misc': 'MNE_DATASETS_MISC_PATH',
@@ -160,85 +225,138 @@ def _data_path(path=None, force_update=False, update_path=True, download=True,
         'brainstorm': 'MNE_DATASETS_BRAINSTORM_PATH',
         'testing': 'MNE_DATASETS_TESTING_PATH',
         'multimodal': 'MNE_DATASETS_MULTIMODAL_PATH',
+        'visual_92_categories': 'MNE_DATASETS_VISUAL_92_CATEGORIES_PATH',
+        'mtrf': 'MNE_DATASETS_MTRF_PATH',
+        'fieldtrip_cmc': 'MNE_DATASETS_FIELDTRIP_CMC_PATH'
     }[name]
 
     path = _get_path(path, key, name)
     # To update the testing or misc dataset, push commits, then make a new
     # release on GitHub. Then update the "releases" variable:
-    releases = dict(testing='0.25', misc='0.1')
+    releases = dict(testing='0.41', misc='0.3')
     # And also update the "hashes['testing']" variable below.
 
     # To update any other dataset, update the data archive itself (upload
     # an updated version) and update the hash.
+
+    # try to match url->archive_name->folder_name
+    urls = dict(  # the URLs to use
+        brainstorm=dict(
+            bst_auditory='https://osf.io/5t9n8/download',
+            bst_phantom_ctf='https://osf.io/sxr8y/download',
+            bst_phantom_elekta='https://osf.io/dpcku/download',
+            bst_raw='https://osf.io/9675n/download',
+            bst_resting='https://osf.io/m7bd3/download'),
+        fake='https://github.com/mne-tools/mne-testing-data/raw/master/'
+             'datasets/foo.tgz',
+        misc='https://codeload.github.com/mne-tools/mne-misc-data/'
+             'tar.gz/%s' % releases['misc'],
+        sample="https://osf.io/86qa2/download",
+        somato='https://osf.io/tp4sg/download',
+        spm='https://osf.io/je4s8/download',
+        testing='https://codeload.github.com/mne-tools/mne-testing-data/'
+                'tar.gz/%s' % releases['testing'],
+        multimodal='https://ndownloader.figshare.com/files/5999598',
+        visual_92_categories=[
+            'https://osf.io/8ejrs/download',
+            'https://osf.io/t4yjp/download'],
+        mtrf="https://superb-dca2.dl.sourceforge.net/project/aespa/"
+             "mTRF_1.5.zip",
+        fieldtrip_cmc='ftp://ftp.fieldtriptoolbox.org/pub/fieldtrip/'
+                      'tutorial/SubjectCMC.zip',
+    )
+    # filename of the resulting downloaded archive (only needed if the URL
+    # name does not match resulting filename)
     archive_names = dict(
         misc='mne-misc-data-%s.tar.gz' % releases['misc'],
+        multimodal='MNE-multimodal-data.tar.gz',
         sample='MNE-sample-data-processed.tar.gz',
         somato='MNE-somato-data.tar.gz',
         spm='MNE-spm-face.tar.gz',
         testing='mne-testing-data-%s.tar.gz' % releases['testing'],
-        multimodal='MNE-multimodal-data.tar.gz',
-        fake='foo.tgz',
+        visual_92_categories=['MNE-visual_92_categories-data-part1.tar.gz',
+                              'MNE-visual_92_categories-data-part2.tar.gz'],
+    )
+    # original folder names that get extracted (only needed if the
+    # archive does not extract the right folder name; e.g., usually GitHub)
+    folder_origs = dict(  # not listed means None (no need to move)
+        misc='mne-misc-data-%s' % releases['misc'],
+        testing='mne-testing-data-%s' % releases['testing'],
     )
-    if archive_name is not None:
-        archive_names.update(archive_name)
+    # finally, where we want them to extract to (only needed if the folder name
+    # is not the same as the last bit of the archive name without the file
+    # extension)
     folder_names = dict(
         brainstorm='MNE-brainstorm-data',
         fake='foo',
         misc='MNE-misc-data',
+        mtrf='mTRF_1.5',
         sample='MNE-sample-data',
-        somato='MNE-somato-data',
-        multimodal='MNE-multimodal-data',
-        spm='MNE-spm-face',
         testing='MNE-testing-data',
-    )
-    urls = dict(
-        brainstorm='https://mne-tools.s3.amazonaws.com/datasets/'
-                   'MNE-brainstorm-data/%s',
-        fake='https://github.com/mne-tools/mne-testing-data/raw/master/'
-             'datasets/%s',
-        misc='https://codeload.github.com/mne-tools/mne-misc-data/'
-             'tar.gz/%s' % releases['misc'],
-        sample="https://mne-tools.s3.amazonaws.com/datasets/%s",
-        somato='https://mne-tools.s3.amazonaws.com/datasets/%s',
-        spm='https://mne-tools.s3.amazonaws.com/datasets/%s',
-        testing='https://codeload.github.com/mne-tools/mne-testing-data/'
-                'tar.gz/%s' % releases['testing'],
-        multimodal='https://ndownloader.figshare.com/files/5999598',
+        visual_92_categories='MNE-visual_92_categories-data',
+        fieldtrip_cmc='MNE-fieldtrip_cmc-data'
     )
     hashes = dict(
-        brainstorm=None,
+        brainstorm=dict(
+            bst_auditory='fa371a889a5688258896bfa29dd1700b',
+            bst_phantom_ctf='80819cb7f5b92d1a5289db3fb6acb33c',
+            bst_phantom_elekta='1badccbe17998d18cc373526e86a7aaf',
+            bst_raw='f82ba1f17b2e7a2d96995c1c08e1cc8d',
+            bst_resting='a14186aebe7bd2aaa2d28db43aa6587e'),
         fake='3194e9f7b46039bb050a74f3e1ae9908',
-        misc='f0708d8914cf2692fee7b6c9f105e71c',
-        sample='1d5da3a809fded1ef5734444ab5bf857',
+        misc='d822a720ef94302467cb6ad1d320b669',
+        sample='fc2d5b9eb0a144b1d6ba84dc3b983602',
         somato='f3e3a8441477bb5bacae1d0c6e0964fb',
-        spm='f61041e3f3f2ba0def8a2ca71592cc41',
-        testing='217aed43e361c86b622dc0363ae3cef4',
+        spm='9f43f67150e3b694b523a21eb929ea75',
+        testing='6e7f0f2506b98cad0f2161162ef5f9f3',
         multimodal='26ec847ae9ab80f58f204d09e2c08367',
+        visual_92_categories=['74f50bbeb65740903eadc229c9fa759f',
+                              '203410a98afc9df9ae8ba9f933370e20'],
+        mtrf='273a390ebbc48da2c3184b01a82e4636',
+        fieldtrip_cmc='6f9fd6520f9a66e20994423808d2528c'
     )
-    folder_origs = dict(  # not listed means None
-        misc='mne-misc-data-%s' % releases['misc'],
-        testing='mne-testing-data-%s' % releases['testing'],
-    )
-    folder_name = folder_names[name]
-    archive_name = archive_names[name]
-    hash_ = hashes[name]
+    assert set(hashes.keys()) == set(urls.keys())
     url = urls[name]
+    hash_ = hashes[name]
     folder_orig = folder_origs.get(name, None)
-    if '%s' in url:
-        url = url % archive_name
-
-    folder_path = op.join(path, folder_name)
     if name == 'brainstorm':
-        extract_path = folder_path
-        folder_path = op.join(folder_path, archive_names[name].split('.')[0])
-
-    rm_archive = False
-    martinos_path = '/cluster/fusion/sample_data/' + archive_name
-    neurospin_path = '/neurospin/tmp/gramfort/' + archive_name
-
-    if not op.exists(folder_path) and not download:
+        assert archive_name is not None
+        url = [url[archive_name.split('.')[0]]]
+        folder_path = [op.join(path, folder_names[name],
+                               archive_name.split('.')[0])]
+        hash_ = [hash_[archive_name.split('.')[0]]]
+        archive_name = [archive_name]
+    else:
+        url = [url] if not isinstance(url, list) else url
+        hash_ = [hash_] if not isinstance(hash_, list) else hash_
+        archive_name = archive_names.get(name)
+        if archive_name is None:
+            archive_name = [u.split('/')[-1] for u in url]
+        if not isinstance(archive_name, list):
+            archive_name = [archive_name]
+        folder_path = [op.join(path, folder_names.get(name, a.split('.')[0]))
+                       for a in archive_name]
+    if not isinstance(folder_orig, list):
+        folder_orig = [folder_orig] * len(url)
+    folder_path = [op.abspath(f) for f in folder_path]
+    assert hash_ is not None
+    assert all(isinstance(x, list) for x in (url, archive_name, hash_,
+                                             folder_path))
+    assert len(url) == len(archive_name) == len(hash_) == len(folder_path)
+    logger.debug('URL:          %s' % (url,))
+    logger.debug('archive_name: %s' % (archive_name,))
+    logger.debug('hash:         %s' % (hash_,))
+    logger.debug('folder_path:  %s' % (folder_path,))
+
+    need_download = any(not op.exists(f) for f in folder_path)
+    if need_download and not download:
         return ''
-    if not op.exists(folder_path) or force_update:
+
+    if need_download or force_update:
+        logger.debug('Downloading: need_download=%s, force_update=%s'
+                     % (need_download, force_update))
+        for f in folder_path:
+            logger.debug('  Exists: %s: %s' % (f, op.exists(f)))
         if name == 'brainstorm':
             if '--accept-brainstorm-license' in sys.argv:
                 answer = 'y'
@@ -247,105 +365,143 @@ def _data_path(path=None, force_update=False, update_path=True, download=True,
             if answer.lower() != 'y':
                 raise RuntimeError('You must agree to the license to use this '
                                    'dataset')
-        logger.info('Downloading or reinstalling '
-                    'data archive %s at location %s' % (archive_name, path))
-
-        if op.exists(martinos_path):
-            archive_name = martinos_path
-        elif op.exists(neurospin_path):
-            archive_name = neurospin_path
-        else:
-            archive_name = op.join(path, archive_name)
-            rm_archive = True
-            fetch_archive = True
-            if op.exists(archive_name):
-                msg = ('Archive already exists. Overwrite it (y/[n])? ')
-                answer = input(msg)
-                if answer.lower() == 'y':
-                    os.remove(archive_name)
-                else:
-                    fetch_archive = False
-
-            if fetch_archive:
-                _fetch_file(url, archive_name, print_destination=False,
-                            hash_=hash_)
-
-        if op.exists(folder_path):
-            def onerror(func, path, exc_info):
-                """Deal with access errors (e.g. testing dataset read-only)"""
-                # Is the error an access error ?
-                do = False
-                if not os.access(path, os.W_OK):
-                    perm = os.stat(path).st_mode | stat.S_IWUSR
-                    os.chmod(path, perm)
-                    do = True
-                if not os.access(op.dirname(path), os.W_OK):
-                    dir_perm = (os.stat(op.dirname(path)).st_mode |
-                                stat.S_IWUSR)
-                    os.chmod(op.dirname(path), dir_perm)
-                    do = True
-                if do:
-                    func(path)
-                else:
-                    raise
-            shutil.rmtree(folder_path, onerror=onerror)
-
-        logger.info('Decompressing the archive: %s' % archive_name)
-        logger.info('(please be patient, this can take some time)')
-        for ext in ['gz', 'bz2']:  # informed guess (and the only 2 options).
-            try:
-                if name != 'brainstorm':
-                    extract_path = path
-                tf = tarfile.open(archive_name, 'r:%s' % ext)
-                tf.extractall(path=extract_path)
-                tf.close()
-                break
-            except tarfile.ReadError as err:
-                logger.info('%s is %s trying "bz2"' % (archive_name, err))
-        if folder_orig is not None:
-            shutil.move(op.join(path, folder_orig), folder_path)
-
-        if rm_archive:
-            os.remove(archive_name)
-
-    path = _do_path_update(path, update_path, key, name)
-    path = op.join(path, folder_name)
+        assert len(url) == len(hash_)
+        assert len(url) == len(archive_name)
+        assert len(url) == len(folder_orig)
+        assert len(url) == len(folder_path)
+        assert len(url) > 0
+        # 1. Get all the archives
+        full_name = list()
+        for u, an, h, fo in zip(url, archive_name, hash_, folder_orig):
+            remove_archive, full = _download(path, u, an, h)
+            full_name.append(full)
+        del archive_name
+        # 2. Extract all of the files
+        remove_dir = True
+        for u, fp, an, h, fo in zip(url, folder_path, full_name, hash_,
+                                    folder_orig):
+            _extract(path, name, fp, an, fo, remove_dir)
+            remove_dir = False  # only do on first iteration
+        # 3. Remove all of the archives
+        if remove_archive:
+            for an in full_name:
+                os.remove(op.join(path, an))
+
+    logger.info('Successfully extracted to: %s' % folder_path)
+
+    _do_path_update(path, update_path, key, name)
+    path = folder_path[0]
 
     # compare the version of the dataset and mne
     data_version = _dataset_version(path, name)
-    try:
-        from distutils.version import LooseVersion as LV
-    except:
-        warn('Could not determine %s dataset version; dataset could '
-             'be out of date. Please install the "distutils" package.'
-             % name)
-    else:  # 0.7 < 0.7.git shoud be False, therefore strip
-        if check_version and LV(data_version) < LV(mne_version.strip('.git')):
-            warn('The {name} dataset (version {current}) is older than '
-                 'mne-python (version {newest}). If the examples fail, '
-                 'you may need to update the {name} dataset by using '
-                 'mne.datasets.{name}.data_path(force_update=True)'.format(
-                     name=name, current=data_version, newest=mne_version))
+    # 0.7 < 0.7.git shoud be False, therefore strip
+    if check_version and (LooseVersion(data_version) <
+                          LooseVersion(mne_version.strip('.git'))):
+        warn('The {name} dataset (version {current}) is older than '
+             'mne-python (version {newest}). If the examples fail, '
+             'you may need to update the {name} dataset by using '
+             'mne.datasets.{name}.data_path(force_update=True)'.format(
+                 name=name, current=data_version, newest=mne_version))
     return (path, data_version) if return_version else path
 
 
+def _download(path, url, archive_name, hash_):
+    """Download and extract an archive, completing the filename."""
+    martinos_path = '/cluster/fusion/sample_data/' + archive_name
+    neurospin_path = '/neurospin/tmp/gramfort/' + archive_name
+    remove_archive = False
+    if op.exists(martinos_path):
+        full_name = martinos_path
+    elif op.exists(neurospin_path):
+        full_name = neurospin_path
+    else:
+        full_name = op.join(path, archive_name)
+        remove_archive = True
+        fetch_archive = True
+        if op.exists(full_name):
+            logger.info('Archive exists (%s), checking hash %s.'
+                        % (archive_name, hash_,))
+            md5 = md5sum(full_name)
+            fetch_archive = False
+            if md5 != hash_:
+                if input('Archive already exists but the hash does not match: '
+                         '%s\nOverwrite (y/[n])?'
+                         % (archive_name,)).lower() == 'y':
+                    os.remove(full_name)
+                    fetch_archive = True
+        if fetch_archive:
+            logger.info('Downloading archive %s to %s' % (archive_name, path))
+            _fetch_file(url, full_name, print_destination=False,
+                        hash_=hash_)
+    return remove_archive, full_name
+
+
+def _extract(path, name, folder_path, archive_name, folder_orig, remove_dir):
+    if op.exists(folder_path) and remove_dir:
+        logger.info('Removing old directory: %s' % (folder_path,))
+
+        def onerror(func, path, exc_info):
+            """Deal with access errors (e.g. testing dataset read-only)."""
+            # Is the error an access error ?
+            do = False
+            if not os.access(path, os.W_OK):
+                perm = os.stat(path).st_mode | stat.S_IWUSR
+                os.chmod(path, perm)
+                do = True
+            if not os.access(op.dirname(path), os.W_OK):
+                dir_perm = (os.stat(op.dirname(path)).st_mode |
+                            stat.S_IWUSR)
+                os.chmod(op.dirname(path), dir_perm)
+                do = True
+            if do:
+                func(path)
+            else:
+                raise
+        shutil.rmtree(folder_path, onerror=onerror)
+
+    logger.info('Decompressing the archive: %s' % archive_name)
+    logger.info('(please be patient, this can take some time)')
+    if name == 'fieldtrip_cmc':
+        extract_path = folder_path
+    elif name == 'brainstorm':
+        extract_path = op.join(*op.split(folder_path)[:-1])
+    else:
+        extract_path = path
+    if archive_name.endswith('.zip'):
+        with zipfile.ZipFile(archive_name, 'r') as ff:
+            ff.extractall(extract_path)
+    else:
+        if archive_name.endswith('.bz2'):
+            ext = 'bz2'
+        else:
+            ext = 'gz'
+        with tarfile.open(archive_name, 'r:%s' % ext) as tf:
+            tf.extractall(path=extract_path)
+
+    if folder_orig is not None:
+        shutil.move(op.join(path, folder_orig), folder_path)
+
+
 def _get_version(name):
-    """Helper to get a dataset version"""
+    """Get a dataset version."""
     if not has_dataset(name):
         return None
     return _data_path(name=name, return_version=True)[1]
 
 
 def has_dataset(name):
-    """Helper for dataset presence"""
+    """Check for dataset presence."""
     endswith = {
         'brainstorm': 'MNE_brainstorm-data',
+        'fieldtrip_cmc': 'MNE-fieldtrip_cmc-data',
         'fake': 'foo',
         'misc': 'MNE-misc-data',
         'sample': 'MNE-sample-data',
         'somato': 'MNE-somato-data',
         'spm': 'MNE-spm-face',
+        'multimodal': 'MNE-multimodal-data',
         'testing': 'MNE-testing-data',
+        'visual_92_categories': 'visual_92_categories-data',
     }[name]
     archive_name = None
     if name == 'brainstorm':
@@ -357,18 +513,21 @@ def has_dataset(name):
 
 @verbose
 def _download_all_example_data(verbose=True):
-    """Helper to download all datasets used in examples and tutorials"""
+    """Download all datasets used in examples and tutorials."""
     # This function is designed primarily to be used by CircleCI. It has
     # verbose=True by default so we get nice status messages
     # Consider adding datasets from here to CircleCI for PR-auto-build
     from . import (sample, testing, misc, spm_face, somato, brainstorm, megsim,
-                   eegbci, multimodal)
+                   eegbci, multimodal, hf_sef, mtrf, fieldtrip_cmc)
     sample.data_path()
     testing.data_path()
     misc.data_path()
     spm_face.data_path()
     somato.data_path()
+    hf_sef.data_path()
     multimodal.data_path()
+    mtrf.data_path()
+    fieldtrip_cmc.data_path()
     sys.argv += ['--accept-brainstorm-license']
     try:
         brainstorm.bst_raw.data_path()
@@ -383,7 +542,153 @@ def _download_all_example_data(verbose=True):
                      data_type='experimental', update_path=True)
     megsim.load_data(condition='visual', data_format='evoked',
                      data_type='simulation', update_path=True)
-    url_root = 'http://www.physionet.org/physiobank/database/eegmmidb/'
-    eegbci.data_path(url_root + 'S001/S001R06.edf', update_path=True)
-    eegbci.data_path(url_root + 'S001/S001R10.edf', update_path=True)
-    eegbci.data_path(url_root + 'S001/S001R14.edf', update_path=True)
+    eegbci.load_data(1, [6, 10, 14], update_path=True)
+    sys.argv += ['--accept-hcpmmp-license']
+    try:
+        fetch_hcp_mmp_parcellation()
+    finally:
+        sys.argv.pop(-1)
+
+
+ at verbose
+def fetch_hcp_mmp_parcellation(subjects_dir=None, combine=True, verbose=None):
+    """Fetch the HCP-MMP parcellation.
+
+    This will download and install the HCP-MMP parcellation [1]_ files for
+    FreeSurfer's fsaverage [2]_ to the specified directory.
+
+    Parameters
+    ----------
+    subjects_dir : str | None
+        The subjects directory to use. The file will be placed in
+        ``subjects_dir + '/fsaverage/label'``.
+    combine : bool
+        If True, also produce the combined/reduced set of 23 labels per
+        hemisphere as ``HCPMMP1_combined.annot`` [3]_.
+    verbose : bool, str, int, or None
+        If not None, override default verbose level (see mne.verbose).
+
+    Notes
+    -----
+    Use of this parcellation is subject to terms of use on the
+    `HCP-MMP webpage <https://balsa.wustl.edu/WN56>`_.
+
+    References
+    ----------
+    .. [1] Glasser MF et al. (2016) A multi-modal parcellation of human
+           cerebral cortex. Nature 536:171-178.
+    .. [2] Mills K (2016) HCP-MMP1.0 projected on fsaverage.
+           https://figshare.com/articles/HCP-MMP1_0_projected_on_fsaverage/3498446/2
+    .. [3] Glasser MF et al. (2016) Supplemental information.
+           https://images.nature.com/full/nature-assets/nature/journal/v536/n7615/extref/nature18933-s3.pdf
+    """  # noqa: E501
+    subjects_dir = get_subjects_dir(subjects_dir, raise_error=True)
+    destination = op.join(subjects_dir, 'fsaverage', 'label')
+    fnames = [op.join(destination, '%s.HCPMMP1.annot' % hemi)
+              for hemi in ('lh', 'rh')]
+    if not all(op.isfile(fname) for fname in fnames):
+        if '--accept-hcpmmp-license' in sys.argv:
+            answer = 'y'
+        else:
+            answer = input('%s\nAgree (y/[n])? ' % _hcp_mmp_license_text)
+        if answer.lower() != 'y':
+            raise RuntimeError('You must agree to the license to use this '
+                               'dataset')
+        _fetch_file('https://ndownloader.figshare.com/files/5528816',
+                    fnames[0], hash_='46a102b59b2fb1bb4bd62d51bf02e975')
+        _fetch_file('https://ndownloader.figshare.com/files/5528819',
+                    fnames[1], hash_='75e96b331940227bbcb07c1c791c2463')
+    if combine:
+        fnames = [op.join(destination, '%s.HCPMMP1_combined.annot' % hemi)
+                  for hemi in ('lh', 'rh')]
+        if all(op.isfile(fname) for fname in fnames):
+            return
+        # otherwise, let's make them
+        logger.info('Creating combined labels')
+        groups = OrderedDict([
+            ('Primary Visual Cortex (V1)',
+             ('V1',)),
+            ('Early Visual Cortex',
+             ('V2', 'V3', 'V4')),
+            ('Dorsal Stream Visual Cortex',
+             ('V3A', 'V3B', 'V6', 'V6A', 'V7', 'IPS1')),
+            ('Ventral Stream Visual Cortex',
+             ('V8', 'VVC', 'PIT', 'FFC', 'VMV1', 'VMV2', 'VMV3')),
+            ('MT+ Complex and Neighboring Visual Areas',
+             ('V3CD', 'LO1', 'LO2', 'LO3', 'V4t', 'FST', 'MT', 'MST', 'PH')),
+            ('Somatosensory and Motor Cortex',
+             ('4', '3a', '3b', '1', '2')),
+            ('Paracentral Lobular and Mid Cingulate Cortex',
+             ('24dd', '24dv', '6mp', '6ma', 'SCEF', '5m', '5L', '5mv',)),
+            ('Premotor Cortex',
+             ('55b', '6d', '6a', 'FEF', '6v', '6r', 'PEF')),
+            ('Posterior Opercular Cortex',
+             ('43', 'FOP1', 'OP4', 'OP1', 'OP2-3', 'PFcm')),
+            ('Early Auditory Cortex',
+             ('A1', 'LBelt', 'MBelt', 'PBelt', 'RI')),
+            ('Auditory Association Cortex',
+             ('A4', 'A5', 'STSdp', 'STSda', 'STSvp', 'STSva', 'STGa', 'TA2',)),
+            ('Insular and Frontal Opercular Cortex',
+             ('52', 'PI', 'Ig', 'PoI1', 'PoI2', 'FOP2', 'FOP3',
+              'MI', 'AVI', 'AAIC', 'Pir', 'FOP4', 'FOP5')),
+            ('Medial Temporal Cortex',
+             ('H', 'PreS', 'EC', 'PeEc', 'PHA1', 'PHA2', 'PHA3',)),
+            ('Lateral Temporal Cortex',
+             ('PHT', 'TE1p', 'TE1m', 'TE1a', 'TE2p', 'TE2a',
+              'TGv', 'TGd', 'TF',)),
+            ('Temporo-Parieto-Occipital Junction',
+             ('TPOJ1', 'TPOJ2', 'TPOJ3', 'STV', 'PSL',)),
+            ('Superior Parietal Cortex',
+             ('LIPv', 'LIPd', 'VIP', 'AIP', 'MIP',
+              '7PC', '7AL', '7Am', '7PL', '7Pm',)),
+            ('Inferior Parietal Cortex',
+             ('PGp', 'PGs', 'PGi', 'PFm', 'PF', 'PFt', 'PFop',
+              'IP0', 'IP1', 'IP2',)),
+            ('Posterior Cingulate Cortex',
+             ('DVT', 'ProS', 'POS1', 'POS2', 'RSC', 'v23ab', 'd23ab',
+              '31pv', '31pd', '31a', '23d', '23c', 'PCV', '7m',)),
+            ('Anterior Cingulate and Medial Prefrontal Cortex',
+             ('33pr', 'p24pr', 'a24pr', 'p24', 'a24', 'p32pr', 'a32pr', 'd32',
+              'p32', 's32', '8BM', '9m', '10v', '10r', '25',)),
+            ('Orbital and Polar Frontal Cortex',
+             ('47s', '47m', 'a47r', '11l', '13l',
+              'a10p', 'p10p', '10pp', '10d', 'OFC', 'pOFC',)),
+            ('Inferior Frontal Cortex',
+             ('44', '45', 'IFJp', 'IFJa', 'IFSp', 'IFSa', '47l', 'p47r',)),
+            ('DorsoLateral Prefrontal Cortex',
+             ('8C', '8Av', 'i6-8', 's6-8', 'SFL', '8BL', '9p', '9a', '8Ad',
+              'p9-46v', 'a9-46v', '46', '9-46d',)),
+            ('???',
+             ('???',))])
+        assert len(groups) == 23
+        labels_out = list()
+
+        for hemi in ('lh', 'rh'):
+            labels = read_labels_from_annot('fsaverage', 'HCPMMP1', hemi=hemi,
+                                            subjects_dir=subjects_dir)
+            label_names = [
+                '???' if label.name.startswith('???') else
+                label.name.split('_')[1] for label in labels]
+            used = np.zeros(len(labels), bool)
+            for key, want in groups.items():
+                assert '\t' not in key
+                these_labels = [li for li, label_name in enumerate(label_names)
+                                if label_name in want]
+                assert not used[these_labels].any()
+                assert len(these_labels) == len(want)
+                used[these_labels] = True
+                these_labels = [labels[li] for li in these_labels]
+                # take a weighted average to get the color
+                # (here color == task activation)
+                w = np.array([len(label.vertices) for label in these_labels])
+                w = w / float(w.sum())
+                color = np.dot(w, [label.color for label in these_labels])
+                these_labels = sum(these_labels,
+                                   Label([], subject='fsaverage', hemi=hemi))
+                these_labels.name = key
+                these_labels.color = color
+                labels_out.append(these_labels)
+            assert used.all()
+        assert len(labels_out) == 46
+        write_labels_to_annot(labels_out, 'fsaverage', 'HCPMMP1_combined',
+                              hemi='both', subjects_dir=subjects_dir)
diff --git a/mne/datasets/visual_92_categories/__init__.py b/mne/datasets/visual_92_categories/__init__.py
new file mode 100644
index 0000000..b2a09ef
--- /dev/null
+++ b/mne/datasets/visual_92_categories/__init__.py
@@ -0,0 +1,5 @@
+"""MNE visual_92_categories dataset."""
+
+from .visual_92_categories import (data_path, has_visual_92_categories_data,
+                                   get_version,
+                                   requires_visual_92_categories_data)
diff --git a/mne/datasets/visual_92_categories/visual_92_categories.py b/mne/datasets/visual_92_categories/visual_92_categories.py
new file mode 100644
index 0000000..fee50aa
--- /dev/null
+++ b/mne/datasets/visual_92_categories/visual_92_categories.py
@@ -0,0 +1,78 @@
+# License: BSD Style.
+
+from functools import partial
+
+import numpy as np
+
+from ...utils import verbose, get_config
+from ..utils import (has_dataset, _data_path, _data_path_doc, _get_version,
+                     _version_doc)
+
+
+has_visual_92_categories_data = partial(has_dataset,
+                                        name='visual_92_categories')
+
+
+ at verbose
+def data_path(path=None, force_update=False, update_path=True, download=True,
+              verbose=None):
+    """
+    Get path to local copy of visual_92_categories dataset.
+
+    .. note:: The dataset contains four fif-files, the trigger files and the T1
+              mri image. This dataset is rather big in size (more than 5 GB).
+
+    Parameters
+    ----------
+    path : None | str
+        Location of where to look for the visual_92_categories data storing
+        location. If None, the environment variable or config parameter
+        MNE_DATASETS_VISUAL_92_CATEGORIES_PATH is used. If it doesn't exist,
+        the "mne-python/examples" directory is used. If the
+        visual_92_categories dataset is not found under the given path (e.g.,
+        as "mne-python/examples/MNE-visual_92_categories-data"), the data
+        will be automatically downloaded to the specified folder.
+    force_update : bool
+        Force update of the dataset even if a local copy exists.
+    update_path : bool | None
+        If True, set the MNE_DATASETS_VISUAL_92_CATEGORIES_PATH in mne-python
+        config to the given path. If None, the user is prompted.
+    verbose : bool, str, int, or None
+        If not None, override default verbose level (see mne.verbose).
+
+    Returns
+    -------
+    path : list of str
+        Local path to the given data file. This path is contained inside a list
+        of length one, for compatibility.
+
+    Notes
+    -----
+    The visual_92_categories dataset is documented in the following publication
+        Radoslaw M. Cichy, Dimitrios Pantazis, Aude Oliva (2014) Resolving
+        human object recognition in space and time. doi: 10.1038/NN.3635
+    """
+    return _data_path(path=path, force_update=force_update,
+                      update_path=update_path, name='visual_92_categories',
+                      download=download)
+
+data_path.__doc__ = _data_path_doc.format(
+    name='visual_92_categories', conf='MNE_DATASETS_VISUAL_92_CATEGORIES_PATH')
+
+
+def get_version():
+    """Get dataset version."""
+    return _get_version('visual_92_categories')
+
+get_version.__doc__ = _version_doc.format(name='visual_92_categories')
+
+
+# Allow forcing of visual_92_categories dataset skip
+def _skip_visual_92_categories_data():
+    skip_testing = (get_config('MNE_SKIP_VISUAL_92_CATEGORIES_DATASET_TESTS',
+                               'false') == 'true')
+    skip = skip_testing or not has_visual_92_categories_data()
+    return skip
+
+requires_visual_92_categories_data = np.testing.dec.skipif(
+    _skip_visual_92_categories_data, 'Requires visual_92_categories dataset')
diff --git a/mne/decoding/__init__.py b/mne/decoding/__init__.py
index 9764863..5b68d71 100644
--- a/mne/decoding/__init__.py
+++ b/mne/decoding/__init__.py
@@ -1,9 +1,14 @@
+"""Decoding and encoding, including machine learning and receptive fields."""
+
 from .transformer import Scaler, FilterEstimator
-from .transformer import (PSDEstimator, EpochsVectorizer, Vectorizer,
+from .transformer import (PSDEstimator, Vectorizer,
                           UnsupervisedSpatialFilter, TemporalFilter)
 from .mixin import TransformerMixin
-from .base import BaseEstimator, LinearModel
-from .csp import CSP
+from .base import BaseEstimator, LinearModel, get_coef, cross_val_multiscore
+from .csp import CSP, SPoC
 from .ems import compute_ems, EMS
 from .time_gen import GeneralizationAcrossTime, TimeDecoding
 from .time_frequency import TimeFrequency
+from .receptive_field import ReceptiveField
+from .time_delaying_ridge import TimeDelayingRidge
+from .search_light import SlidingEstimator, GeneralizingEstimator
diff --git a/mne/decoding/base.py b/mne/decoding/base.py
index 5f845c2..8285918 100644
--- a/mne/decoding/base.py
+++ b/mne/decoding/base.py
@@ -1,204 +1,40 @@
-"""Base class copy from sklearn.base"""
+"""Base class copy from sklearn.base."""
 # Authors: Gael Varoquaux <gael.varoquaux at normalesup.org>
 #          Romain Trachel <trachelr at gmail.com>
 #          Alexandre Gramfort <alexandre.gramfort at telecom-paristech.fr>
+#          Jean-Remi King <jeanremi.king at gmail.com>
 #
 # License: BSD (3-clause)
 
-import warnings
 import numpy as np
-
-from ..externals.six import iteritems
-from ..fixes import _get_args
-from ..utils import check_version
-
-
-class BaseEstimator(object):
-    """Base class for all estimators in scikit-learn
-    Notes
-    -----
-    All estimators should specify all the parameters that can be set
-    at the class level in their ``__init__`` as explicit keyword
-    arguments (no ``*args`` or ``**kwargs``).
-    """
-
-    @classmethod
-    def _get_param_names(cls):
-        """Get parameter names for the estimator"""
-        # fetch the constructor or the original constructor before
-        # deprecation wrapping if any
-        init = getattr(cls.__init__, 'deprecated_original', cls.__init__)
-        if init is object.__init__:
-            # No explicit constructor to introspect
-            return []
-
-        # introspect the constructor arguments to find the model parameters
-        # to represent
-        args, varargs = _get_args(init, varargs=True)
-        if varargs is not None:
-            raise RuntimeError("scikit-learn estimators should always "
-                               "specify their parameters in the signature"
-                               " of their __init__ (no varargs)."
-                               " %s doesn't follow this convention."
-                               % (cls, ))
-        # Remove 'self'
-        # XXX: This is going to fail if the init is a staticmethod, but
-        # who would do this?
-        args.pop(0)
-        args.sort()
-        return args
-
-    def get_params(self, deep=True):
-        """Get parameters for this estimator.
-
-        Parameters
-        ----------
-        deep : boolean, optional
-            If True, will return the parameters for this estimator and
-            contained subobjects that are estimators.
-
-        Returns
-        -------
-        params : mapping of string to any
-            Parameter names mapped to their values.
-        """
-        out = dict()
-        for key in self._get_param_names():
-            # We need deprecation warnings to always be on in order to
-            # catch deprecated param values.
-            # This is set in utils/__init__.py but it gets overwritten
-            # when running under python3 somehow.
-            warnings.simplefilter("always", DeprecationWarning)
-            try:
-                with warnings.catch_warnings(record=True) as w:
-                    value = getattr(self, key, None)
-                if len(w) and w[0].category == DeprecationWarning:
-                    # if the parameter is deprecated, don't show it
-                    continue
-            finally:
-                warnings.filters.pop(0)
-
-            # XXX: should we rather test if instance of estimator?
-            if deep and hasattr(value, 'get_params'):
-                deep_items = value.get_params().items()
-                out.update((key + '__' + k, val) for k, val in deep_items)
-            out[key] = value
-        return out
-
-    def set_params(self, **params):
-        """Set the parameters of this estimator.
-        The method works on simple estimators as well as on nested objects
-        (such as pipelines). The former have parameters of the form
-        ``<component>__<parameter>`` so that it's possible to update each
-        component of a nested object.
-        Returns
-        -------
-        self
-        """
-        if not params:
-            # Simple optimisation to gain speed (inspect is slow)
-            return self
-        valid_params = self.get_params(deep=True)
-        for key, value in iteritems(params):
-            split = key.split('__', 1)
-            if len(split) > 1:
-                # nested objects case
-                name, sub_name = split
-                if name not in valid_params:
-                    raise ValueError('Invalid parameter %s for estimator %s. '
-                                     'Check the list of available parameters '
-                                     'with `estimator.get_params().keys()`.' %
-                                     (name, self))
-                sub_object = valid_params[name]
-                sub_object.set_params(**{sub_name: value})
-            else:
-                # simple objects case
-                if key not in valid_params:
-                    raise ValueError('Invalid parameter %s for estimator %s. '
-                                     'Check the list of available parameters '
-                                     'with `estimator.get_params().keys()`.' %
-                                     (key, self.__class__.__name__))
-                setattr(self, key, value)
-        return self
-
-    def __repr__(self):
-        class_name = self.__class__.__name__
-        return '%s(%s)' % (class_name, _pprint(self.get_params(deep=False),
-                                               offset=len(class_name),),)
-
-
-###############################################################################
-def _pprint(params, offset=0, printer=repr):
-    """Pretty print the dictionary 'params'
-
-    Parameters
-    ----------
-    params: dict
-        The dictionary to pretty print
-    offset: int
-        The offset in characters to add at the beginning of each line.
-    printer:
-        The function to convert entries to strings, typically
-        the builtin str or repr
-
-    """
-    # Do a multi-line justified repr:
-    options = np.get_printoptions()
-    np.set_printoptions(precision=5, threshold=64, edgeitems=2)
-    params_list = list()
-    this_line_length = offset
-    line_sep = ',\n' + (1 + offset // 2) * ' '
-    for i, (k, v) in enumerate(sorted(iteritems(params))):
-        if type(v) is float:
-            # use str for representing floating point numbers
-            # this way we get consistent representation across
-            # architectures and versions.
-            this_repr = '%s=%s' % (k, str(v))
-        else:
-            # use repr of the rest
-            this_repr = '%s=%s' % (k, printer(v))
-        if len(this_repr) > 500:
-            this_repr = this_repr[:300] + '...' + this_repr[-100:]
-        if i > 0:
-            if (this_line_length + len(this_repr) >= 75 or '\n' in this_repr):
-                params_list.append(line_sep)
-                this_line_length = len(line_sep)
-            else:
-                params_list.append(', ')
-                this_line_length += 2
-        params_list.append(this_repr)
-        this_line_length += len(this_repr)
-
-    np.set_printoptions(**options)
-    lines = ''.join(params_list)
-    # Strip trailing space to avoid nightmare in doctests
-    lines = '\n'.join(l.rstrip(' ') for l in lines.split('\n'))
-    return lines
+import time
+import numbers
+from ..parallel import parallel_func
+from ..fixes import BaseEstimator, is_classifier
+from ..utils import check_version, logger, warn
 
 
 class LinearModel(BaseEstimator):
-    """
-    This object clones a Linear Model from scikit-learn
-    and updates the attributes for each fit. The linear model coefficients
-    (filters) are used to extract discriminant neural sources from
-    the measured data. This class implements the computation of patterns
-    which provides neurophysiologically interpretable information [1],
-    in the sense that significant nonzero weights are only observed at channels
-    where activity is related to discriminant neural sources.
+    """Compute and store patterns from linear models.
+
+    The linear model coefficients (filters) are used to extract discriminant
+    neural sources from the measured data. This class computes the
+    corresponding patterns of these linear filters to make them more
+    interpretable [1]_.
 
     Parameters
     ----------
     model : object | None
         A linear model from scikit-learn with a fit method
-        that updates a coef_ attribute.
-        If None the model will be LogisticRegression
+        that updates a ``coef_`` attribute.
+        If None the model will be LogisticRegression.
 
     Attributes
     ----------
-    filters_ : ndarray
-        If fit, the filters used to decompose the data, else None.
-    patterns_ : ndarray
-        If fit, the patterns used to restore M/EEG signals, else None.
+    ``filters_`` : ndarray, shape ([n_targets], n_features)
+        If fit, the filters used to decompose the data.
+    ``patterns_`` : ndarray, shape ([n_targets], n_features)
+        If fit, the patterns used to restore M/EEG signals.
 
     Notes
     -----
@@ -206,71 +42,87 @@ class LinearModel(BaseEstimator):
 
     See Also
     --------
-    ICA
     CSP
-    xDawn
+    mne.preprocessing.ICA
+    mne.preprocessing.Xdawn
 
     References
     ----------
-    [1] Haufe, S., Meinecke, F., Gorgen, K., Dahne, S., Haynes, J.-D.,
-    Blankertz, B., & Biebmann, F. (2014). On the interpretation of
-    weight vectors of linear models in multivariate neuroimaging.
-    NeuroImage, 87, 96-110.
+    .. [1] Haufe, S., Meinecke, F., Gorgen, K., Dahne, S., Haynes, J.-D.,
+           Blankertz, B., & Biebmann, F. (2014). On the interpretation of
+           weight vectors of linear models in multivariate neuroimaging.
+           NeuroImage, 87, 96-110.
     """
-    def __init__(self, model=None):
+
+    def __init__(self, model=None):  # noqa: D102
         if model is None:
             from sklearn.linear_model import LogisticRegression
             model = LogisticRegression()
 
         self.model = model
-        self.patterns_ = None
-        self.filters_ = None
+        self._estimator_type = getattr(model, "_estimator_type", None)
 
     def fit(self, X, y):
         """Estimate the coefficients of the linear model.
-        Save the coefficients in the attribute filters_ and
-        computes the attribute patterns_ using [1].
+
+        Save the coefficients in the attribute ``filters_`` and
+        computes the attribute ``patterns_``.
 
         Parameters
         ----------
-        X : array, shape (n_epochs, n_features)
-            The data to estimate the coeffiscient.
-        y : array, shape (n_epochs,)
-            The class for each epoch.
+        X : array, shape (n_samples, n_features)
+            The training input samples to estimate the linear coefficients.
+        y : array, shape (n_samples, [n_targets])
+            The target values.
 
         Returns
         -------
         self : instance of LinearModel
             Returns the modified instance.
-
-        References
-        ----------
         """
+        X, y = np.asarray(X), np.asarray(y)
+        if X.ndim != 2:
+            raise ValueError('LinearModel only accepts 2-dimensional X, got '
+                             '%s instead.' % (X.shape,))
+        if y.ndim > 2:
+            raise ValueError('LinearModel only accepts up to 2-dimensional y, '
+                             'got %s instead.' % (y.shape,))
+
         # fit the Model
         self.model.fit(X, y)
-        # computes the patterns
-        assert hasattr(self.model, 'coef_'), \
-            "model needs a coef_ attribute to compute the patterns"
-        self.patterns_ = np.dot(X.T, np.dot(X, self.model.coef_.T))
-        self.filters_ = self.model.coef_
+
+        # Computes patterns using Haufe's trick: A = Cov_X . W . Precision_Y
+
+        inv_Y = 1.
+        X = X - X.mean(0, keepdims=True)
+        if y.ndim == 2 and y.shape[1] != 1:
+            y = y - y.mean(0, keepdims=True)
+            inv_Y = np.linalg.pinv(np.cov(y.T))
+        self.patterns_ = np.cov(X.T).dot(self.filters_.T.dot(inv_Y)).T
 
         return self
 
-    def transform(self, X, y=None):
+    @property
+    def filters_(self):
+        if not hasattr(self.model, 'coef_'):
+            raise ValueError('model does not have a `coef_` attribute.')
+        filters = self.model.coef_
+        if filters.ndim == 2 and filters.shape[0] == 1:
+            filters = filters[0]
+        return filters
+
+    def transform(self, X):
         """Transform the data using the linear model.
 
         Parameters
         ----------
-        X : array, shape (n_epochs, n_features)
+        X : array, shape (n_samples, n_features)
             The data to transform.
-        y : array, shape (n_epochs,)
-            The class for each epoch.
 
         Returns
         -------
-        y_pred : array, shape (n_epochs,)
-            Predicted class label per epoch.
-
+        y_pred : array, shape (n_samples,)
+            The predicted targets.
         """
         return self.model.transform(X)
 
@@ -279,383 +131,84 @@ class LinearModel(BaseEstimator):
 
         Parameters
         ----------
-        X : array, shape (n_epochs, n_features)
-            The data to transform.
-        y : array, shape (n_epochs,)
-            The class for each epoch.
+        X : array, shape (n_samples, n_features)
+            The training input samples to estimate the linear coefficients.
+        y : array, shape (n_samples,)
+            The target values.
 
         Returns
         -------
-        y_pred : array, shape (n_epochs,)
-            Predicted class label per epoch.
+        y_pred : array, shape (n_samples,)
+            The predicted targets.
 
         """
         return self.fit(X, y).transform(X)
 
     def predict(self, X):
-        """Computes predictions of y from X.
+        """Compute predictions of y from X.
 
         Parameters
         ----------
-        X : array, shape (n_epochs, n_features)
+        X : array, shape (n_samples, n_features)
             The data used to compute the predictions.
 
         Returns
         -------
-        y_pred : array, shape (n_epochs,)
+        y_pred : array, shape (n_samples,)
             The predictions.
         """
         return self.model.predict(X)
 
-    def score(self, X, y):
-        """
-        Returns the score of the linear model computed
-        on the given test data.
+    def predict_proba(self, X):
+        """Compute probabilistic predictions of y from X.
 
         Parameters
         ----------
-        X : array, shape (n_epochs, n_features)
-            The data to transform.
-        y : array, shape (n_epochs,)
-            The class for each epoch.
+        X : array, shape (n_samples, n_features)
+            The data used to compute the predictions.
 
         Returns
         -------
-        score : float
-            Score of the linear model
-
+        y_pred : array, shape (n_samples, n_classes)
+            The probabilities.
         """
-        return self.model.score(X, y)
+        return self.model.predict_proba(X)
 
-    def plot_patterns(self, info, times=None, ch_type=None, layout=None,
-                      vmin=None, vmax=None, cmap='RdBu_r', sensors=True,
-                      colorbar=True, scale=None, scale_time=1e3, unit='a.u.',
-                      res=64, size=1, cbar_fmt='%3.1f',
-                      name_format='%01d ms', proj=False, show=True,
-                      show_names=False, title=None, mask=None,
-                      mask_params=None, outlines='head', contours=6,
-                      image_interp='bilinear', average=None, head_pos=None):
-        """
-        Plot topographic patterns of the linear model.
-        The patterns explain how the measured data was generated
-        from the neural sources (a.k.a. the forward model).
+    def decision_function(self, X):
+        """Compute distance from the decision function of y from X.
 
         Parameters
         ----------
-        info : instance of Info
-            Info dictionary of the epochs used to fit the linear model.
-            If not possible, consider using ``create_info``.
-        times : float | array of floats | None.
-            The time point(s) to plot. If None, the number of ``axes``
-            determines the amount of time point(s). If ``axes`` is also None,
-            10 topographies will be shown with a regular time spacing between
-            the first and last time instant.
-        ch_type : 'mag' | 'grad' | 'planar1' | 'planar2' | 'eeg' | None
-            The channel type to plot. For 'grad', the gradiometers are
-            collected in pairs and the RMS for each pair is plotted.
-            If None, then first available channel type from order given
-            above is used. Defaults to None.
-        layout : None | Layout
-            Layout instance specifying sensor positions (does not need to be
-            specified for Neuromag data). If possible, the correct layout file
-            is inferred from the data; if no appropriate layout file was found
-            the layout is automatically generated from the sensor locations.
-        vmin : float | callable
-            The value specfying the lower bound of the color range.
-            If None, and vmax is None, -vmax is used. Else np.min(data).
-            If callable, the output equals vmin(data).
-        vmax : float | callable
-            The value specfying the upper bound of the color range.
-            If None, the maximum absolute value is used. If vmin is None,
-            but vmax is not, defaults to np.min(data).
-            If callable, the output equals vmax(data).
-        cmap : matplotlib colormap | (colormap, bool) | 'interactive' | None
-            Colormap to use. If tuple, the first value indicates the colormap
-            to use and the second value is a boolean defining interactivity. In
-            interactive mode the colors are adjustable by clicking and dragging
-            the colorbar with left and right mouse button. Left mouse button
-            moves the scale up and down and right mouse button adjusts the
-            range. Hitting space bar resets the range. Up and down arrows can
-            be used to change the colormap. If None, 'Reds' is used for all
-            positive data, otherwise defaults to 'RdBu_r'. If 'interactive',
-            translates to (None, True). Defaults to 'RdBu_r'.
-
-            .. warning::  Interactive mode works smoothly only for a small
-                amount of topomaps.
-
-        sensors : bool | str
-            Add markers for sensor locations to the plot. Accepts matplotlib
-            plot format string (e.g., 'r+' for red plusses). If True,
-            a circle will be used (via .add_artist). Defaults to True.
-        colorbar : bool
-            Plot a colorbar.
-        scale : dict | float | None
-            Scale the data for plotting. If None, defaults to 1e6 for eeg, 1e13
-            for grad and 1e15 for mag.
-        scale_time : float | None
-            Scale the time labels. Defaults to 1e3.
-        unit : dict | str | None
-            The unit of the channel type used for colorbar label. If
-            scale is None the unit is automatically determined.
-        res : int
-            The resolution of the topomap image (n pixels along each side).
-        size : float
-            Side length per topomap in inches.
-        cbar_fmt : str
-            String format for colorbar values.
-        name_format : str
-            String format for topomap values. Defaults to "%03f ms"
-        proj : bool | 'interactive'
-            If true SSP projections are applied before display.
-            If 'interactive', a check box for reversible selection
-            of SSP projection vectors will be show.
-        show : bool
-            Show figure if True.
-        show_names : bool | callable
-            If True, show channel names on top of the map. If a callable is
-            passed, channel names will be formatted using the callable; e.g.,
-            to delete the prefix 'MEG ' from all channel names, pass the
-            function lambda x: x.replace('MEG ', ''). If `mask` is not None,
-            only significant sensors will be shown.
-        title : str | None
-            Title. If None (default), no title is displayed.
-        mask : ndarray of bool, shape (n_channels, n_times) | None
-            The channels to be marked as significant at a given time point.
-            Indices set to `True` will be considered. Defaults to None.
-        mask_params : dict | None
-            Additional plotting parameters for plotting significant sensors.
-            Default (None) equals::
-
-                dict(marker='o', markerfacecolor='w', markeredgecolor='k',
-                     linewidth=0, markersize=4)
-
-        outlines : 'head' | 'skirt' | dict | None
-            The outlines to be drawn. If 'head', the default head scheme will
-            be drawn. If 'skirt' the head scheme will be drawn, but sensors are
-            allowed to be plotted outside of the head circle. If dict, each key
-            refers to a tuple of x and y positions, the values in 'mask_pos'
-            will serve as image mask, and the 'autoshrink' (bool) field will
-            trigger automated shrinking of the positions due to points outside
-            the outline. Alternatively, a matplotlib patch object can be passed
-            for advanced masking options, either directly or as a function that
-            returns patches (required for multi-axis plots). If None, nothing
-            will be drawn. Defaults to 'head'.
-        contours : int | False | None
-            The number of contour lines to draw.
-            If 0, no contours will be drawn.
-        image_interp : str
-            The image interpolation to be used.
-            All matplotlib options are accepted.
-        average : float | None
-            The time window around a given time to be used for averaging
-            (seconds). For example, 0.01 would translate into window that
-            starts 5 ms before and ends 5 ms after a given time point.
-            Defaults to None, which means no averaging.
-        head_pos : dict | None
-            If None (default), the sensors are positioned such that they span
-            the head circle. If dict, can have entries 'center' (tuple) and
-            'scale' (tuple) for what the center and scale of the head
-            should be relative to the electrode locations.
+        X : array, shape (n_samples, n_features)
+            The data used to compute the predictions.
 
         Returns
         -------
-        fig : instance of matplotlib.figure.Figure
-           The figure.
+        y_pred : array, shape (n_samples, n_classes)
+            The distances.
         """
+        return self.model.decision_function(X)
 
-        from .. import EvokedArray
-
-        if times is None:
-            tmin = 0
-            times = 'auto'
-        else:
-            tmin = times[0]
-
-        # create an evoked
-        patterns = EvokedArray(self.patterns_.reshape(info['nchan'], -1),
-                               info, tmin=tmin)
-        # the call plot_topomap
-        return patterns.plot_topomap(times=times, ch_type=ch_type,
-                                     layout=layout, vmin=vmin, vmax=vmax,
-                                     cmap=cmap, colorbar=colorbar, res=res,
-                                     cbar_fmt=cbar_fmt, sensors=sensors,
-                                     scale=scale, scale_time=scale_time,
-                                     time_format=name_format, size=size,
-                                     show_names=show_names, unit=unit,
-                                     mask_params=mask_params,
-                                     mask=mask, outlines=outlines,
-                                     contours=contours, title=title,
-                                     image_interp=image_interp, show=show,
-                                     head_pos=head_pos, average=average)
-
-    def plot_filters(self, info, times=None, ch_type=None, layout=None,
-                     vmin=None, vmax=None, cmap='RdBu_r', sensors=True,
-                     colorbar=True, scale=None, scale_time=1e3, unit='a.u.',
-                     res=64, size=1, cbar_fmt='%3.1f',
-                     name_format='%01d ms', proj=False, show=True,
-                     show_names=False, title=None, mask=None,
-                     mask_params=None, outlines='head', contours=6,
-                     image_interp='bilinear', average=None, head_pos=None):
-        """
-        Plot topographic filters of the linear model.
-        The filters are used to extract discriminant neural sources from
-        the measured data (a.k.a. the backward model).
+    def score(self, X, y):
+        """Score the linear model computed on the given test data.
 
         Parameters
         ----------
-        info : instance of Info
-            Info dictionary of the epochs used to fit the linear model.
-            If not possible, consider using ``create_info``.
-        times : float | array of floats | None.
-            The time point(s) to plot. If None, the number of ``axes``
-            determines the amount of time point(s). If ``axes`` is also None,
-            10 topographies will be shown with a regular time spacing between
-            the first and last time instant.
-        ch_type : 'mag' | 'grad' | 'planar1' | 'planar2' | 'eeg' | None
-            The channel type to plot. For 'grad', the gradiometers are
-            collected in pairs and the RMS for each pair is plotted.
-            If None, then first available channel type from order given
-            above is used. Defaults to None.
-        layout : None | Layout
-            Layout instance specifying sensor positions (does not need to be
-            specified for Neuromag data). If possible, the correct layout file
-            is inferred from the data; if no appropriate layout file was found
-            the layout is automatically generated from the sensor locations.
-        vmin : float | callable
-            The value specfying the lower bound of the color range.
-            If None, and vmax is None, -vmax is used. Else np.min(data).
-            If callable, the output equals vmin(data).
-        vmax : float | callable
-            The value specfying the upper bound of the color range.
-            If None, the maximum absolute value is used. If vmin is None,
-            but vmax is not, defaults to np.min(data).
-            If callable, the output equals vmax(data).
-        cmap : matplotlib colormap | (colormap, bool) | 'interactive' | None
-            Colormap to use. If tuple, the first value indicates the colormap
-            to use and the second value is a boolean defining interactivity. In
-            interactive mode the colors are adjustable by clicking and dragging
-            the colorbar with left and right mouse button. Left mouse button
-            moves the scale up and down and right mouse button adjusts the
-            range. Hitting space bar resets the range. Up and down arrows can
-            be used to change the colormap. If None, 'Reds' is used for all
-            positive data, otherwise defaults to 'RdBu_r'. If 'interactive',
-            translates to (None, True). Defaults to 'RdBu_r'.
-
-            .. warning::  Interactive mode works smoothly only for a small
-                amount of topomaps.
-
-        sensors : bool | str
-            Add markers for sensor locations to the plot. Accepts matplotlib
-            plot format string (e.g., 'r+' for red plusses). If True,
-            a circle will be used (via .add_artist). Defaults to True.
-        colorbar : bool
-            Plot a colorbar.
-        scale : dict | float | None
-            Scale the data for plotting. If None, defaults to 1e6 for eeg, 1e13
-            for grad and 1e15 for mag.
-        scale_time : float | None
-            Scale the time labels. Defaults to 1e3.
-        unit : dict | str | None
-            The unit of the channel type used for colorbar label. If
-            scale is None the unit is automatically determined.
-        res : int
-            The resolution of the topomap image (n pixels along each side).
-        size : float
-            Side length per topomap in inches.
-        cbar_fmt : str
-            String format for colorbar values.
-        name_format : str
-            String format for topomap values. Defaults to "%03f ms"
-        proj : bool | 'interactive'
-            If true SSP projections are applied before display.
-            If 'interactive', a check box for reversible selection
-            of SSP projection vectors will be show.
-        show : bool
-            Show figure if True.
-        show_names : bool | callable
-            If True, show channel names on top of the map. If a callable is
-            passed, channel names will be formatted using the callable; e.g.,
-            to delete the prefix 'MEG ' from all channel names, pass the
-            function lambda x: x.replace('MEG ', ''). If `mask` is not None,
-            only significant sensors will be shown.
-        title : str | None
-            Title. If None (default), no title is displayed.
-        mask : ndarray of bool, shape (n_channels, n_times) | None
-            The channels to be marked as significant at a given time point.
-            Indices set to `True` will be considered. Defaults to None.
-        mask_params : dict | None
-            Additional plotting parameters for plotting significant sensors.
-            Default (None) equals::
-
-                dict(marker='o', markerfacecolor='w', markeredgecolor='k',
-                     linewidth=0, markersize=4)
-
-        outlines : 'head' | 'skirt' | dict | None
-            The outlines to be drawn. If 'head', the default head scheme will
-            be drawn. If 'skirt' the head scheme will be drawn, but sensors are
-            allowed to be plotted outside of the head circle. If dict, each key
-            refers to a tuple of x and y positions, the values in 'mask_pos'
-            will serve as image mask, and the 'autoshrink' (bool) field will
-            trigger automated shrinking of the positions due to points outside
-            the outline. Alternatively, a matplotlib patch object can be passed
-            for advanced masking options, either directly or as a function that
-            returns patches (required for multi-axis plots). If None, nothing
-            will be drawn. Defaults to 'head'.
-        contours : int | False | None
-            The number of contour lines to draw.
-            If 0, no contours will be drawn.
-        image_interp : str
-            The image interpolation to be used.
-            All matplotlib options are accepted.
-        average : float | None
-            The time window around a given time to be used for averaging
-            (seconds). For example, 0.01 would translate into window that
-            starts 5 ms before and ends 5 ms after a given time point.
-            Defaults to None, which means no averaging.
-        head_pos : dict | None
-            If None (default), the sensors are positioned such that they span
-            the head circle. If dict, can have entries 'center' (tuple) and
-            'scale' (tuple) for what the center and scale of the head
-            should be relative to the electrode locations.
+        X : array, shape (n_samples, n_features)
+            The data to transform.
+        y : array, shape (n_samples,)
+            The target values.
 
         Returns
         -------
-        fig : instance of matplotlib.figure.Figure
-           The figure.
+        score : float
+            Score of the linear model
         """
-
-        from .. import EvokedArray
-
-        if times is None:
-            tmin = 0
-            times = 'auto'
-        else:
-            tmin = times[0]
-
-        # create an evoked
-        filters = EvokedArray(self.filters_.T.reshape(info['nchan'], -1),
-                              info, tmin=tmin)
-        # the call plot_topomap
-        return filters.plot_topomap(times=times, ch_type=ch_type,
-                                    layout=layout, vmin=vmin, vmax=vmax,
-                                    cmap=cmap, colorbar=colorbar, res=res,
-                                    cbar_fmt=cbar_fmt, sensors=sensors,
-                                    scale=scale, scale_time=scale_time,
-                                    time_format=name_format, size=size,
-                                    show_names=show_names, unit=unit,
-                                    mask_params=mask_params,
-                                    mask=mask, outlines=outlines,
-                                    contours=contours, title=title,
-                                    image_interp=image_interp, show=show,
-                                    head_pos=head_pos, average=average)
+        return self.model.score(X, y)
 
 
 def _set_cv(cv, estimator=None, X=None, y=None):
-    """ Set the default cross-validation depending on whether clf is classifier
-        or regressor. """
-
-    from sklearn.base import is_classifier
-
+    """Set the default CV depending on whether clf is classifier/regressor."""
     # Detect whether classification or regression
     if estimator in ['classifier', 'regressor']:
         est_is_classifier = estimator == 'classifier'
@@ -707,8 +260,7 @@ def _set_cv(cv, estimator=None, X=None, y=None):
 
 
 def _check_estimator(estimator, get_params=True):
-    """Check whether an object has the fit, transform, fit_transform and
-    get_params methods required by scikit-learn"""
+    """Check whether an object has the methods required by sklearn."""
     valid_methods = ('predict', 'transform', 'predict_proba',
                      'decision_function')
     if (
@@ -723,3 +275,271 @@ def _check_estimator(estimator, get_params=True):
         raise ValueError('estimator must be a scikit-learn transformer or an '
                          'estimator with the get_params method that allows '
                          'cloning.')
+
+
+def _get_inverse_funcs(estimator, terminal=True):
+    """Retrieve the inverse functions of an pipeline or an estimator."""
+    inverse_func = [False]
+    if hasattr(estimator, 'steps'):
+        # if pipeline, retrieve all steps by nesting
+        inverse_func = list()
+        for _, est in estimator.steps:
+            inverse_func.extend(_get_inverse_funcs(est, terminal=False))
+    elif hasattr(estimator, 'inverse_transform'):
+        # if not pipeline attempt to retrieve inverse function
+        inverse_func = [estimator.inverse_transform]
+
+    # If terminal node, check that that the last estimator is a classifier,
+    # and remove it from the transformers.
+    if terminal:
+        last_is_estimator = inverse_func[-1] is False
+        all_invertible = not(False in inverse_func[:-1])
+        if last_is_estimator and all_invertible:
+            # keep all inverse transformation and remove last estimation
+            inverse_func = inverse_func[:-1]
+        else:
+            inverse_func = list()
+
+    return inverse_func
+
+
+def get_coef(estimator, attr='filters_', inverse_transform=False):
+    """Retrieve the coefficients of an estimator ending with a Linear Model.
+
+    This is typically useful to retrieve "spatial filters" or "spatial
+    patterns" of decoding models [1]_.
+
+    Parameters
+    ----------
+    estimator : object | None
+        An estimator from scikit-learn.
+    attr : str
+        The name of the coefficient attribute to retrieve, typically
+        ``'filters_'`` (default) or ``'patterns_'``.
+    inverse_transform : bool
+        If True, returns the coefficients after inverse transforming them with
+        the transformer steps of the estimator.
+
+    Returns
+    -------
+    coef : array
+        The coefficients.
+
+    References
+    ----------
+    .. [1] Haufe, S., Meinecke, F., Gorgen, K., Dahne, S., Haynes, J.-D.,
+       Blankertz, B., & Biessmann, F. (2014). On the interpretation of weight
+       vectors of linear models in multivariate neuroimaging. NeuroImage, 87,
+       96-110. doi:10.1016/j.neuroimage.2013.10.067.
+    """
+    # Get the coefficients of the last estimator in case of nested pipeline
+    est = estimator
+    while hasattr(est, 'steps'):
+        est = est.steps[-1][1]
+
+    # If SlidingEstimator, loop across estimators
+    if hasattr(est, 'estimators_'):
+        coef = list()
+        for this_est in est.estimators_:
+            coef.append(get_coef(this_est, attr, inverse_transform))
+        coef = np.transpose(coef)
+    elif not hasattr(est, attr):
+        raise ValueError('This estimator does not have a %s '
+                         'attribute.' % attr)
+    else:
+        coef = getattr(est, attr)
+
+    # inverse pattern e.g. to get back physical units
+    if inverse_transform:
+        if not hasattr(estimator, 'steps') and not hasattr(est, 'estimators_'):
+            raise ValueError('inverse_transform can only be applied onto '
+                             'pipeline estimators.')
+        # The inverse_transform parameter will call this method on any
+        # estimator contained in the pipeline, in reverse order.
+        for inverse_func in _get_inverse_funcs(estimator)[::-1]:
+            coef = inverse_func(np.array([coef]))[0]
+    return coef
+
+
+def cross_val_multiscore(estimator, X, y=None, groups=None, scoring=None,
+                         cv=None, n_jobs=1, verbose=0, fit_params=None,
+                         pre_dispatch='2*n_jobs'):
+    """Evaluate a score by cross-validation.
+
+    Parameters
+    ----------
+    estimator : estimator object implementing 'fit'
+        The object to use to fit the data.
+    X : array-like, shape (n_samples, n_dimensional_features,)
+        The data to fit. Can be, for example a list, or an array at least 2d.
+    y : array-like, shape (n_samples, n_targets,)
+        The target variable to try to predict in the case of
+        supervised learning.
+    groups : array-like, with shape (n_samples,)
+        Group labels for the samples used while splitting the dataset into
+        train/test set.
+    scoring : string, callable | None
+        A string (see model evaluation documentation) or
+        a scorer callable object / function with signature
+        ``scorer(estimator, X, y)``.
+    cv : int, cross-validation generator | iterable
+        Determines the cross-validation splitting strategy.
+        Possible inputs for cv are:
+
+        - None, to use the default 3-fold cross validation,
+        - integer, to specify the number of folds in a ``(Stratified)KFold``,
+        - An object to be used as a cross-validation generator.
+        - An iterable yielding train, test splits.
+
+        For integer/None inputs, if the estimator is a classifier and ``y`` is
+        either binary or multiclass,
+        :class:`sklearn.model_selection.StratifiedKFold` is used. In all
+        other cases, :class:`sklearn.model_selection.KFold` is used.
+    n_jobs : integer, optional
+        The number of CPUs to use to do the computation. -1 means
+        'all CPUs'.
+    verbose : integer, optional
+        The verbosity level.
+    fit_params : dict, optional
+        Parameters to pass to the fit method of the estimator.
+    pre_dispatch : int, or string, optional
+        Controls the number of jobs that get dispatched during parallel
+        execution. Reducing this number can be useful to avoid an
+        explosion of memory consumption when more jobs get dispatched
+        than CPUs can process. This parameter can be:
+
+        - None, in which case all the jobs are immediately
+          created and spawned. Use this for lightweight and
+          fast-running jobs, to avoid delays due to on-demand
+          spawning of the jobs
+        - An int, giving the exact number of total jobs that are
+          spawned
+        - A string, giving an expression as a function of n_jobs,
+          as in '2*n_jobs'
+
+    Returns
+    -------
+    scores : array of float, shape (n_splits,) | shape (n_splits, n_scores)
+        Array of scores of the estimator for each run of the cross validation.
+    """
+    # This code is copied from sklearn
+
+    from sklearn.base import clone
+    from sklearn.utils import indexable
+    from sklearn.metrics.scorer import check_scoring
+    from sklearn.model_selection._split import check_cv
+
+    X, y, groups = indexable(X, y, groups)
+
+    cv = check_cv(cv, y, classifier=is_classifier(estimator))
+    cv_iter = list(cv.split(X, y, groups))
+    scorer = check_scoring(estimator, scoring=scoring)
+    # We clone the estimator to make sure that all the folds are
+    # independent, and that it is pickle-able.
+    # Note: this parallelization is implemented using MNE Parallel
+    parallel, p_func, n_jobs = parallel_func(_fit_and_score, n_jobs,
+                                             pre_dispatch=pre_dispatch)
+    scores = parallel(p_func(clone(estimator), X, y, scorer, train, test,
+                             verbose, None, fit_params)
+                      for train, test in cv_iter)
+    return np.array(scores)[:, 0, ...]  # flatten over joblib output.
+
+
+def _fit_and_score(estimator, X, y, scorer, train, test, verbose,
+                   parameters, fit_params, return_train_score=False,
+                   return_parameters=False, return_n_test_samples=False,
+                   return_times=False, error_score='raise'):
+    """Fit estimator and compute scores for a given dataset split."""
+    #  This code is adapted from sklearn
+    from sklearn.model_selection._validation import _index_param_value
+    from sklearn.utils.metaestimators import _safe_split
+    from sklearn.utils.validation import _num_samples
+
+    if verbose > 1:
+        if parameters is None:
+            msg = ''
+        else:
+            msg = '%s' % (', '.join('%s=%s' % (k, v)
+                          for k, v in parameters.items()))
+        print("[CV] %s %s" % (msg, (64 - len(msg)) * '.'))
+
+    # Adjust length of sample weights
+    fit_params = fit_params if fit_params is not None else {}
+    fit_params = dict([(k, _index_param_value(X, v, train))
+                      for k, v in fit_params.items()])
+
+    if parameters is not None:
+        estimator.set_params(**parameters)
+
+    start_time = time.time()
+
+    X_train, y_train = _safe_split(estimator, X, y, train)
+    X_test, y_test = _safe_split(estimator, X, y, test, train)
+
+    try:
+        if y_train is None:
+            estimator.fit(X_train, **fit_params)
+        else:
+            estimator.fit(X_train, y_train, **fit_params)
+
+    except Exception as e:
+        # Note fit time as time until error
+        fit_time = time.time() - start_time
+        score_time = 0.0
+        if error_score == 'raise':
+            raise
+        elif isinstance(error_score, numbers.Number):
+            test_score = error_score
+            if return_train_score:
+                train_score = error_score
+            warn("Classifier fit failed. The score on this train-test"
+                 " partition for these parameters will be set to %f. "
+                 "Details: \n%r" % (error_score, e))
+        else:
+            raise ValueError("error_score must be the string 'raise' or a"
+                             " numeric value. (Hint: if using 'raise', please"
+                             " make sure that it has been spelled correctly.)")
+
+    else:
+        fit_time = time.time() - start_time
+        test_score = _score(estimator, X_test, y_test, scorer)
+        score_time = time.time() - start_time - fit_time
+        if return_train_score:
+            train_score = _score(estimator, X_train, y_train, scorer)
+
+    if verbose > 2:
+        msg += ", score=%f" % test_score
+    if verbose > 1:
+        total_time = score_time + fit_time
+        end_msg = "%s, total=%s" % (msg, logger.short_format_time(total_time))
+        print("[CV] %s %s" % ((64 - len(end_msg)) * '.', end_msg))
+
+    ret = [train_score, test_score] if return_train_score else [test_score]
+
+    if return_n_test_samples:
+        ret.append(_num_samples(X_test))
+    if return_times:
+        ret.extend([fit_time, score_time])
+    if return_parameters:
+        ret.append(parameters)
+    return ret
+
+
+def _score(estimator, X_test, y_test, scorer):
+    """Compute the score of an estimator on a given test set.
+
+    This code is the same as sklearn.model_selection._validation._score
+    but accepts to output arrays instead of floats.
+    """
+    if y_test is None:
+        score = scorer(estimator, X_test)
+    else:
+        score = scorer(estimator, X_test, y_test)
+    if hasattr(score, 'item'):
+        try:
+            # e.g. unwrap memmapped scalars
+            score = score.item()
+        except ValueError:
+            # non-scalar?
+            pass
+    return score
diff --git a/mne/decoding/csp.py b/mne/decoding/csp.py
index 2d34303..6c8b2db 100644
--- a/mne/decoding/csp.py
+++ b/mne/decoding/csp.py
@@ -19,7 +19,7 @@ from ..utils import warn
 
 
 class CSP(TransformerMixin, BaseEstimator):
-    """M/EEG signal decomposition using the Common Spatial Patterns (CSP).
+    u"""M/EEG signal decomposition using the Common Spatial Patterns (CSP).
 
     This object can be used as a supervised decomposition to estimate
     spatial filters for feature extraction in a 2 class decoding problem.
@@ -50,34 +50,44 @@ class CSP(TransformerMixin, BaseEstimator):
         If 'average_power' then self.transform will return the average power of
         each spatial filter. If 'csp_space' self.transform will return the data
         in CSP space. Defaults to 'average_power'.
+    norm_trace : bool
+        Normalize class covariance by its trace. Defaults to True. Trace
+        normalization is a step of the original CSP algorithm [1]_ to eliminate
+        magnitude variations in the EEG between individuals. It is not applied
+        in more recent work [2]_, [3]_ and can have a negative impact on
+        patterns ordering.
 
     Attributes
     ----------
-    ``filters_`` : ndarray, shape (n_channels, n_channels)
+    ``filters_`` : ndarray, shape (n_components, n_channels)
         If fit, the CSP components used to decompose the data, else None.
-    ``patterns_`` : ndarray, shape (n_channels, n_channels)
+    ``patterns_`` : ndarray, shape (n_components, n_channels)
         If fit, the CSP patterns used to restore M/EEG signals, else None.
     ``mean_`` : ndarray, shape (n_components,)
         If fit, the mean squared power for each component.
     ``std_`` : ndarray, shape (n_components,)
         If fit, the std squared power for each component.
 
+    See Also
+    --------
+    mne.preprocessing.Xdawn, SPoC
+
     References
     ----------
-    [1] Zoltan J. Koles, Michael S. Lazar, Steven Z. Zhou. Spatial Patterns
-        Underlying Population Differences in the Background EEG. Brain
-        Topography 2(4), 275-284, 1990.
-    [2] Benjamin Blankertz, Ryota Tomioka, Steven Lemm, Motoaki Kawanabe,
-        Klaus-Robert Müller. Optimizing Spatial Filters for Robust EEG
-        Single-Trial Analysis. IEEE Signal Processing Magazine 25(1), 41-56,
-        2008.
-    [3] Grosse-Wentrup, Moritz, and Martin Buss. Multiclass common spatial
-        patterns and information theoretic feature extraction. IEEE
-        Transactions on Biomedical Engineering, Vol 55, no. 8, 2008.
+    .. [1] Zoltan J. Koles, Michael S. Lazar, Steven Z. Zhou. Spatial Patterns
+           Underlying Population Differences in the Background EEG. Brain
+           Topography 2(4), 275-284, 1990.
+    .. [2] Benjamin Blankertz, Ryota Tomioka, Steven Lemm, Motoaki Kawanabe,
+           Klaus-Robert Müller. Optimizing Spatial Filters for Robust EEG
+           Single-Trial Analysis. IEEE Signal Processing Magazine 25(1), 41-56,
+           2008.
+    .. [3] Grosse-Wentrup, Moritz, and Martin Buss. Multiclass common spatial
+           patterns and information theoretic feature extraction. IEEE
+           Transactions on Biomedical Engineering, Vol 55, no. 8, 2008.
     """
 
     def __init__(self, n_components=4, reg=None, log=None, cov_est="concat",
-                 transform_into='average_power'):
+                 transform_into='average_power', norm_trace=None):
         """Init of CSP."""
         # Init default CSP
         if not isinstance(n_components, int):
@@ -116,6 +126,15 @@ class CSP(TransformerMixin, BaseEstimator):
                 raise ValueError('log must be a None if transform_into == '
                                  '"csp_space".')
         self.log = log
+        if norm_trace is None:
+            norm_trace = True
+            warn("norm_trace defaults to True in 0.15, but will change to "
+                 "False in 0.16. Set it explicitly to avoid this warning.",
+                 DeprecationWarning)
+
+        if not isinstance(norm_trace, bool):
+            raise ValueError('norm_trace must be a bool.')
+        self.norm_trace = norm_trace
 
     def _check_Xy(self, X, y=None):
         """Aux. function to check input data."""
@@ -125,7 +144,7 @@ class CSP(TransformerMixin, BaseEstimator):
         if X.ndim < 3:
             raise ValueError('X must have at least 3 dimensions.')
 
-    def fit(self, X, y, epochs_data=None):
+    def fit(self, X, y):
         """Estimate the CSP decomposition on epochs.
 
         Parameters
@@ -140,7 +159,6 @@ class CSP(TransformerMixin, BaseEstimator):
         self : instance of CSP
             Returns the modified instance.
         """
-        X = _check_deprecate(epochs_data, X)
         if not isinstance(X, np.ndarray):
             raise ValueError("X should be of type ndarray (got %s)."
                              % type(X))
@@ -168,8 +186,17 @@ class CSP(TransformerMixin, BaseEstimator):
                 cov /= len(class_)
                 weight = len(class_)
 
-            # normalize by trace and stack
-            covs[class_idx] = cov / np.trace(cov)
+            covs[class_idx] = cov
+            if self.norm_trace:
+                # Append covariance matrix and weight. Prior to version 0.15,
+                # trace normalization was applied, but was breaking results for
+                # some usecases by chaging the apparent ranking of patterns.
+                # Trace normalization of the covariance matrix was removed
+                # without signigificant effect on patterns or performances.
+                # If the user interested in this feature, we suggest trace
+                # normalization of the epochs prior to the CSP.
+                covs[class_idx] /= np.trace(cov)
+
             sample_weights.append(weight)
 
         if n_classes == 2:
@@ -217,7 +244,7 @@ class CSP(TransformerMixin, BaseEstimator):
         X = np.asarray([np.dot(pick_filters, epoch) for epoch in X])
 
         # compute features (mean band power)
-        X = (X ** 2).mean(axis=-1)
+        X = (X ** 2).mean(axis=2)
 
         # To standardize features
         self.mean_ = X.mean(axis=0)
@@ -225,7 +252,7 @@ class CSP(TransformerMixin, BaseEstimator):
 
         return self
 
-    def transform(self, X, epochs_data=None):
+    def transform(self, X):
         """Estimate epochs sources given the CSP filters.
 
         Parameters
@@ -241,7 +268,6 @@ class CSP(TransformerMixin, BaseEstimator):
             If self.transform_into == 'csp_space' then returns the data in CSP
             space and shape is (n_epochs, n_sources, n_times)
         """
-        X = _check_deprecate(epochs_data, X)
         if not isinstance(X, np.ndarray):
             raise ValueError("X should be of type ndarray (got %s)." % type(X))
         if self.filters_ is None:
@@ -253,7 +279,7 @@ class CSP(TransformerMixin, BaseEstimator):
 
         # compute features (mean band power)
         if self.transform_into == 'average_power':
-            X = (X ** 2).mean(axis=-1)
+            X = (X ** 2).mean(axis=2)
             log = True if self.log is None else self.log
             if log:
                 X = np.log(X)
@@ -264,24 +290,24 @@ class CSP(TransformerMixin, BaseEstimator):
 
     def plot_patterns(self, info, components=None, ch_type=None, layout=None,
                       vmin=None, vmax=None, cmap='RdBu_r', sensors=True,
-                      colorbar=True, scale=None, scale_time=1, unit=None,
-                      res=64, size=1, cbar_fmt='%3.1f',
-                      name_format='CSP%01d', proj=False, show=True,
-                      show_names=False, title=None, mask=None,
+                      colorbar=True, scalings=None, units='a.u.', res=64,
+                      size=1, cbar_fmt='%3.1f', name_format='CSP%01d',
+                      show=True, show_names=False, title=None, mask=None,
                       mask_params=None, outlines='head', contours=6,
-                      image_interp='bilinear', average=None, head_pos=None):
-        """Plot topographic patterns of CSP components.
+                      image_interp='bilinear', average=None, head_pos=None,
+                      scale=None, unit=None):
+        """Plot topographic patterns of components.
 
-        The CSP patterns explain how the measured data was generated
-        from the neural sources (a.k.a. the forward model).
+        The patterns explain how the measured data was generated from the
+        neural sources (a.k.a. the forward model).
 
         Parameters
         ----------
         info : instance of Info
-            Info dictionary of the epochs used to fit CSP.
+            Info dictionary of the epochs used for fitting.
             If not possible, consider using ``create_info``.
         components : float | array of floats | None.
-           The CSP patterns to plot. If None, n_components will be shown.
+           The patterns to plot. If None, n_components will be shown.
         ch_type : 'mag' | 'grad' | 'planar1' | 'planar2' | 'eeg' | None
             The channel type to plot. For 'grad', the gradiometers are
             collected in pairs and the RMS for each pair is plotted.
@@ -321,12 +347,10 @@ class CSP(TransformerMixin, BaseEstimator):
             a circle will be used (via .add_artist). Defaults to True.
         colorbar : bool
             Plot a colorbar.
-        scale : dict | float | None
-            Scale the data for plotting. If None, defaults to 1e6 for eeg, 1e13
-            for grad and 1e15 for mag.
-        scale_time : float | None
-            Scale the time labels. Defaults to 1.
-        unit : dict | str | None
+        scalings : dict | float | None
+            The scalings of the channel types to be applied for plotting.
+            If None, defaults to ``dict(eeg=1e6, grad=1e13, mag=1e15)``.
+        units : dict | str | None
             The unit of the channel type used for colorbar label. If
             scale is None the unit is automatically determined.
         res : int
@@ -337,10 +361,6 @@ class CSP(TransformerMixin, BaseEstimator):
             String format for colorbar values.
         name_format : str
             String format for topomap values. Defaults to "CSP%01d"
-        proj : bool | 'interactive'
-            If true SSP projections are applied before display.
-            If 'interactive', a check box for reversible selection
-            of SSP projection vectors will be show.
         show : bool
             Show figure if True.
         show_names : bool | callable
@@ -372,9 +392,12 @@ class CSP(TransformerMixin, BaseEstimator):
             for advanced masking options, either directly or as a function that
             returns patches (required for multi-axis plots). If None, nothing
             will be drawn. Defaults to 'head'.
-        contours : int | False | None
-            The number of contour lines to draw.
-            If 0, no contours will be drawn.
+        contours : int | array of float
+            The number of contour lines to draw. If 0, no contours will be
+            drawn. When an integer, matplotlib ticker locator is used to find
+            suitable values for the contour thresholds (may sometimes be
+            inaccurate, use array for accuracy). If an array, the values
+            represent the levels for the contours. Defaults to 6.
         image_interp : str
             The image interpolation to be used.
             All matplotlib options are accepted.
@@ -394,7 +417,6 @@ class CSP(TransformerMixin, BaseEstimator):
         fig : instance of matplotlib.figure.Figure
            The figure.
         """
-
         from .. import EvokedArray
         if components is None:
             components = np.arange(self.n_components)
@@ -405,39 +427,37 @@ class CSP(TransformerMixin, BaseEstimator):
         # create an evoked
         patterns = EvokedArray(self.patterns_.T, info, tmin=0)
         # the call plot_topomap
-        return patterns.plot_topomap(times=components, ch_type=ch_type,
-                                     layout=layout, vmin=vmin, vmax=vmax,
-                                     cmap=cmap, colorbar=colorbar, res=res,
-                                     cbar_fmt=cbar_fmt, sensors=sensors,
-                                     scale=1, scale_time=1, unit='a.u.',
-                                     time_format=name_format, size=size,
-                                     show_names=show_names,
-                                     mask_params=mask_params,
-                                     mask=mask, outlines=outlines,
-                                     contours=contours,
-                                     image_interp=image_interp, show=show,
-                                     head_pos=head_pos)
+        return patterns.plot_topomap(
+            times=components, ch_type=ch_type, layout=layout,
+            vmin=vmin, vmax=vmax, cmap=cmap, colorbar=colorbar, res=res,
+            cbar_fmt=cbar_fmt, sensors=sensors,
+            scalings=scalings, units=units, scaling_time=1,
+            time_format=name_format, size=size, show_names=show_names,
+            title=title, mask_params=mask_params, mask=mask, outlines=outlines,
+            contours=contours, image_interp=image_interp, show=show,
+            average=average, head_pos=head_pos, unit=unit,
+            scale=scale)
 
     def plot_filters(self, info, components=None, ch_type=None, layout=None,
                      vmin=None, vmax=None, cmap='RdBu_r', sensors=True,
-                     colorbar=True, scale=None, scale_time=1, unit=None,
-                     res=64, size=1, cbar_fmt='%3.1f',
-                     name_format='CSP%01d', proj=False, show=True,
-                     show_names=False, title=None, mask=None,
+                     colorbar=True, scalings=None, units='a.u.', res=64,
+                     size=1, cbar_fmt='%3.1f', name_format='CSP%01d',
+                     show=True, show_names=False, title=None, mask=None,
                      mask_params=None, outlines='head', contours=6,
-                     image_interp='bilinear', average=None, head_pos=None):
-        """Plot topographic filters of CSP components.
+                     image_interp='bilinear', average=None, head_pos=None,
+                     scale=None, unit=None):
+        """Plot topographic filters of components.
 
-        The CSP filters are used to extract discriminant neural sources from
+        The filters are used to extract discriminant neural sources from
         the measured data (a.k.a. the backward model).
 
         Parameters
         ----------
         info : instance of Info
-            Info dictionary of the epochs used to fit CSP.
+            Info dictionary of the epochs used for fitting.
             If not possible, consider using ``create_info``.
         components : float | array of floats | None.
-           The CSP patterns to plot. If None, n_components will be shown.
+           The patterns to plot. If None, n_components will be shown.
         ch_type : 'mag' | 'grad' | 'planar1' | 'planar2' | 'eeg' | None
             The channel type to plot. For 'grad', the gradiometers are
             collected in pairs and the RMS for each pair is plotted.
@@ -477,12 +497,10 @@ class CSP(TransformerMixin, BaseEstimator):
             a circle will be used (via .add_artist). Defaults to True.
         colorbar : bool
             Plot a colorbar.
-        scale : dict | float | None
-            Scale the data for plotting. If None, defaults to 1e6 for eeg, 1e13
-            for grad and 1e15 for mag.
-        scale_time : float | None
-            Scale the time labels. Defaults to 1.
-        unit : dict | str | None
+        scalings : dict | float | None
+            The scalings of the channel types to be applied for plotting.
+            If None, defaults to ``dict(eeg=1e6, grad=1e13, mag=1e15)``.
+        units : dict | str | None
             The unit of the channel type used for colorbar label. If
             scale is None the unit is automatically determined.
         res : int
@@ -493,10 +511,6 @@ class CSP(TransformerMixin, BaseEstimator):
             String format for colorbar values.
         name_format : str
             String format for topomap values. Defaults to "CSP%01d"
-        proj : bool | 'interactive'
-            If true SSP projections are applied before display.
-            If 'interactive', a check box for reversible selection
-            of SSP projection vectors will be show.
         show : bool
             Show figure if True.
         show_names : bool | callable
@@ -528,9 +542,12 @@ class CSP(TransformerMixin, BaseEstimator):
             for advanced masking options, either directly or as a function that
             returns patches (required for multi-axis plots). If None, nothing
             will be drawn. Defaults to 'head'.
-        contours : int | False | None
-            The number of contour lines to draw.
-            If 0, no contours will be drawn.
+        contours : int | array of float
+            The number of contour lines to draw. If 0, no contours will be
+            drawn. When an integer, matplotlib ticker locator is used to find
+            suitable values for the contour thresholds (may sometimes be
+            inaccurate, use array for accuracy). If an array, the values
+            represent the levels for the contours. Defaults to 6.
         image_interp : str
             The image interpolation to be used.
             All matplotlib options are accepted.
@@ -550,7 +567,6 @@ class CSP(TransformerMixin, BaseEstimator):
         fig : instance of matplotlib.figure.Figure
            The figure.
         """
-
         from .. import EvokedArray
         if components is None:
             components = np.arange(self.n_components)
@@ -561,18 +577,15 @@ class CSP(TransformerMixin, BaseEstimator):
         # create an evoked
         filters = EvokedArray(self.filters_, info, tmin=0)
         # the call plot_topomap
-        return filters.plot_topomap(times=components, ch_type=ch_type,
-                                    layout=layout, vmin=vmin, vmax=vmax,
-                                    cmap=cmap, colorbar=colorbar, res=res,
-                                    cbar_fmt=cbar_fmt, sensors=sensors,
-                                    scale=1, scale_time=1, unit='a.u.',
-                                    time_format=name_format, size=size,
-                                    show_names=show_names,
-                                    mask_params=mask_params,
-                                    mask=mask, outlines=outlines,
-                                    contours=contours,
-                                    image_interp=image_interp, show=show,
-                                    head_pos=head_pos)
+        return filters.plot_topomap(
+            times=components, ch_type=ch_type, layout=layout, vmin=vmin,
+            vmax=vmax, cmap=cmap, colorbar=colorbar, res=res,
+            cbar_fmt=cbar_fmt, sensors=sensors, scalings=scalings, units=units,
+            scaling_time=1, time_format=name_format, size=size,
+            show_names=show_names, title=title, mask_params=mask_params,
+            mask=mask, outlines=outlines, contours=contours,
+            image_interp=image_interp, show=show, average=average,
+            head_pos=head_pos, unit=unit, scale=scale)
 
 
 def _ajd_pham(X, eps=1e-6, max_iter=15):
@@ -598,9 +611,9 @@ def _ajd_pham(X, eps=1e-6, max_iter=15):
 
     References
     ----------
-    [1] Pham, Dinh Tuan. "Joint approximate diagonalization of positive
-    definite Hermitian matrices." SIAM Journal on Matrix Analysis and
-    Applications 22, no. 4 (2001): 1136-1152.
+    .. [1] Pham, Dinh Tuan. "Joint approximate diagonalization of positive
+           definite Hermitian matrices." SIAM Journal on Matrix Analysis and
+           Applications 22, no. 4 (2001): 1136-1152.
 
     """
     # Adapted from http://github.com/alexandrebarachant/pyRiemann
@@ -655,13 +668,156 @@ def _ajd_pham(X, eps=1e-6, max_iter=15):
                 V[[ii, jj], :] = np.dot(tau, V[[ii, jj], :])
         if decr < epsilon:
             break
-    D = np.reshape(A, (n_times, n_m / n_times, n_times)).transpose(1, 0, 2)
+    D = np.reshape(A, (n_times, -1, n_times)).transpose(1, 0, 2)
     return V, D
 
 
-def _check_deprecate(epochs_data, X):
-    """Aux. function to CSP to deal with the change param name."""
-    if epochs_data is not None:
-        X = epochs_data
-        warn('epochs_data will be deprecated in mne 0.14. Use X instead')
-    return X
+class SPoC(CSP):
+    """Implementation of the SPoC spatial filtering.
+
+    Source Power Comodulation (SPoC) [1]_ allows to extract spatial filters and
+    patterns by using a target (continuous) variable in the decomposition
+    process in order to give preference to components whose power correlates
+    with the target variable.
+
+    SPoC can be seen as an extension of the CSP driven by a continuous
+    variable rather than a discrete variable. Typical applications include
+    extraction of motor patterns using EMG power or audio patterns using sound
+    envelope.
+
+    Parameters
+    ----------
+    n_components : int
+        The number of components to decompose M/EEG signals.
+    reg : float | str | None, defaults to None
+        if not None, allow regularization for covariance estimation
+        if float, shrinkage covariance is used (0 <= shrinkage <= 1).
+        if str, optimal shrinkage using Ledoit-Wolf Shrinkage ('ledoit_wolf')
+        or Oracle Approximating Shrinkage ('oas').
+    log : None | bool, defaults to None
+        If transform_into == 'average_power' and log is None or True, then
+        applies a log transform to standardize the features, else the features
+        are z-scored. If transform_into == 'csp_space', then log must be None.
+    transform_into : {'average_power', 'csp_space'}
+        If 'average_power' then self.transform will return the average power of
+        each spatial filter. If 'csp_space' self.transform will return the data
+        in CSP space. Defaults to 'average_power'.
+
+    Attributes
+    ----------
+    ``filters_`` : ndarray, shape (n_components, n_channels)
+        If fit, the SPoC spatial filters, else None.
+    ``patterns_`` : ndarray, shape (n_components, n_channels)
+        If fit, the SPoC spatial patterns, else None.
+    ``mean_`` : ndarray, shape (n_components,)
+        If fit, the mean squared power for each component.
+    ``std_`` : ndarray, shape (n_components,)
+        If fit, the std squared power for each component.
+
+    See Also
+    --------
+    mne.preprocessing.Xdawn, CSP
+
+    References
+    ----------
+    .. [1] Dahne, S., Meinecke, F. C., Haufe, S., Hohne, J., Tangermann, M.,
+           Muller, K. R., & Nikulin, V. V. (2014). SPoC: a novel framework for
+           relating the amplitude of neuronal oscillations to behaviorally
+           relevant parameters. NeuroImage, 86, 111-122.
+    """
+
+    def __init__(self, n_components=4, reg=None, log=None,
+                 transform_into='average_power'):
+        """Init of SPoC."""
+        super(SPoC, self).__init__(n_components=n_components, reg=reg, log=log,
+                                   cov_est="epoch", norm_trace=False,
+                                   transform_into=transform_into)
+        # Covariance estimation have to be done on the single epoch level,
+        # unlike CSP where covariance estimation can also be achieved through
+        # concatenation of all epochs from the same class.
+        delattr(self, 'cov_est')
+        delattr(self, 'norm_trace')
+
+    def fit(self, X, y):
+        """Estimate the SPoC decomposition on epochs.
+
+        Parameters
+        ----------
+        X : ndarray, shape (n_epochs, n_channels, n_times)
+            The data on which to estimate the SPoC.
+        y : array, shape (n_epochs,)
+            The class for each epoch.
+
+        Returns
+        -------
+        self : instance of SPoC
+            Returns the modified instance.
+        """
+        if not isinstance(X, np.ndarray):
+            raise ValueError("X should be of type ndarray (got %s)."
+                             % type(X))
+        self._check_Xy(X, y)
+
+        if len(np.unique(y)) < 2:
+            raise ValueError("y must have at least two distinct values.")
+
+        # The following code is direclty copied from pyRiemann
+
+        # Normalize target variable
+        target = y.astype(np.float64)
+        target -= target.mean()
+        target /= target.std()
+
+        n_epochs, n_channels = X.shape[:2]
+
+        # Estimate single trial covariance
+        covs = np.empty((n_epochs, n_channels, n_channels))
+        for ii, epoch in enumerate(X):
+            covs[ii] = _regularized_covariance(epoch, reg=self.reg)
+
+        C = covs.mean(0)
+        Cz = np.mean(covs * target[:, np.newaxis, np.newaxis], axis=0)
+
+        # solve eigenvalue decomposition
+        evals, evecs = linalg.eigh(Cz, C)
+        evals = evals.real
+        evecs = evecs.real
+        # sort vectors
+        ix = np.argsort(np.abs(evals))[::-1]
+
+        # sort eigenvectors
+        evecs = evecs[:, ix].T
+
+        # spatial patterns
+        self.patterns_ = linalg.pinv(evecs).T  # n_channels x n_channels
+        self.filters_ = evecs  # n_channels x n_channels
+
+        pick_filters = self.filters_[:self.n_components]
+        X = np.asarray([np.dot(pick_filters, epoch) for epoch in X])
+
+        # compute features (mean band power)
+        X = (X ** 2).mean(axis=-1)
+
+        # To standardize features
+        self.mean_ = X.mean(axis=0)
+        self.std_ = X.std(axis=0)
+
+        return self
+
+    def transform(self, X):
+        """Estimate epochs sources given the SPoC filters.
+
+        Parameters
+        ----------
+        X : array, shape (n_epochs, n_channels, n_times)
+            The data.
+
+        Returns
+        -------
+        X : ndarray
+            If self.transform_into == 'average_power' then returns the power of
+            CSP features averaged over time and shape (n_epochs, n_sources)
+            If self.transform_into == 'csp_space' then returns the data in CSP
+            space and shape is (n_epochs, n_sources, n_times)
+        """
+        return super(SPoC, self).transform(X)
diff --git a/mne/decoding/ems.py b/mne/decoding/ems.py
index 34b6c2b..4e66644 100644
--- a/mne/decoding/ems.py
+++ b/mne/decoding/ems.py
@@ -18,28 +18,29 @@ from .. import pick_types, pick_info
 class EMS(TransformerMixin, EstimatorMixin):
     """Transformer to compute event-matched spatial filters.
 
-    This version operates on the entire time course. The result is a spatial
-    filter at each time point and a corresponding time course. Intuitively,
-    the result gives the similarity between the filter at each time point and
-    the data vector (sensors) at that time point.
+    This version of EMS [1]_ operates on the entire time course. No time
+    window needs to be specified. The result is a spatial filter at each
+    time point and a corresponding time course. Intuitively, the result
+    gives the similarity between the filter at each time point and the
+    data vector (sensors) at that time point.
 
     .. note : EMS only works for binary classification.
 
-    References
-    ----------
-    [1] Aaron Schurger, Sebastien Marti, and Stanislas Dehaene, "Reducing
-        multi-sensor data to a single time course that reveals experimental
-        effects", BMC Neuroscience 2013, 14:122
-
     Attributes
     ----------
-    filters_ : ndarray, shape (n_channels, n_times)
+    ``filters_`` : ndarray, shape (n_channels, n_times)
         The set of spatial filters.
-    classes_ : ndarray, shape (n_classes,)
+    ``classes_`` : ndarray, shape (n_classes,)
         The target classes.
+
+    References
+    ----------
+    .. [1] Aaron Schurger, Sebastien Marti, and Stanislas Dehaene, "Reducing
+           multi-sensor data to a single time course that reveals experimental
+           effects", BMC Neuroscience 2013, 14:122
     """
 
-    def __repr__(self):
+    def __repr__(self):  # noqa: D105
         if hasattr(self, 'filters_'):
             return '<EMS: fitted with %i filters on %i classes.>' % (
                 len(self.filters_), len(self.classes_))
@@ -94,26 +95,22 @@ def compute_ems(epochs, conditions=None, picks=None, n_jobs=1, verbose=None,
                 cv=None):
     """Compute event-matched spatial filter on epochs.
 
-    This version operates on the entire time course. No time window needs to
-    be specified. The result is a spatial filter at each time point and a
-    corresponding time course. Intuitively, the result gives the similarity
-    between the filter at each time point and the data vector (sensors) at
-    that time point.
+    This version of EMS [1]_ operates on the entire time course. No time
+    window needs to be specified. The result is a spatial filter at each
+    time point and a corresponding time course. Intuitively, the result
+    gives the similarity between the filter at each time point and the
+    data vector (sensors) at that time point.
 
     .. note : EMS only works for binary classification.
+
     .. note : The present function applies a leave-one-out cross-validation,
               following Schurger et al's paper. However, we recommend using
               a stratified k-fold cross-validation. Indeed, leave-one-out tends
               to overfit and cannot be used to estimate the variance of the
               prediction within a given fold.
-    .. note : Because of the leave-one-out, thise function needs an equal
-              number of epochs in each of the two conditions.
 
-    References
-    ----------
-    [1] Aaron Schurger, Sebastien Marti, and Stanislas Dehaene, "Reducing
-        multi-sensor data to a single time course that reveals experimental
-        effects", BMC Neuroscience 2013, 14:122
+    .. note : Because of the leave-one-out, this function needs an equal
+              number of epochs in each of the two conditions.
 
     Parameters
     ----------
@@ -128,7 +125,8 @@ def compute_ems(epochs, conditions=None, picks=None, n_jobs=1, verbose=None,
     n_jobs : int, defaults to 1
         Number of jobs to run in parallel.
     verbose : bool, str, int, or None, defaults to self.verbose
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
     cv : cross-validation object | str | None, defaults to LeaveOneOut
         The cross-validation scheme.
 
@@ -140,6 +138,12 @@ def compute_ems(epochs, conditions=None, picks=None, n_jobs=1, verbose=None,
         The set of spatial filters.
     conditions : ndarray, shape (n_classes,)
         The conditions used. Values correspond to original event ids.
+
+    References
+    ----------
+    .. [1] Aaron Schurger, Sebastien Marti, and Stanislas Dehaene, "Reducing
+           multi-sensor data to a single time course that reveals experimental
+           effects", BMC Neuroscience 2013, 14:122
     """
     logger.info('...computing surrogate time series. This can take some time')
 
@@ -207,12 +211,12 @@ def compute_ems(epochs, conditions=None, picks=None, n_jobs=1, verbose=None,
 
 
 def _ems_diff(data0, data1):
-    """Aux. function to compute_ems that computes the default diff
-    objective function."""
+    """Compute the default diff objective function."""
     return np.mean(data0, axis=0) - np.mean(data1, axis=0)
 
 
 def _run_ems(objective_function, data, cond_idx, train, test):
+    """Run EMS."""
     d = objective_function(*(data[np.intersect1d(c, train)] for c in cond_idx))
     d /= np.sqrt(np.sum(d ** 2, axis=0))[None, :]
     # compute surrogates
diff --git a/mne/decoding/mixin.py b/mne/decoding/mixin.py
index 645110e..7b7bd78 100644
--- a/mne/decoding/mixin.py
+++ b/mne/decoding/mixin.py
@@ -2,10 +2,10 @@ from ..externals import six
 
 
 class TransformerMixin(object):
-    """Mixin class for all transformers in scikit-learn"""
+    """Mixin class for all transformers in scikit-learn."""
 
     def fit_transform(self, X, y=None, **fit_params):
-        """Fit to data, then transform it
+        """Fit to data, then transform it.
 
         Fits transformer to X and y with optional parameters fit_params
         and returns a transformed version of X.
@@ -37,6 +37,7 @@ class EstimatorMixin(object):
     """Mixin class for estimators."""
 
     def get_params(self):
+        """Get the estimator params."""
         pass
 
     def set_params(self, **params):
diff --git a/mne/decoding/receptive_field.py b/mne/decoding/receptive_field.py
new file mode 100644
index 0000000..a619fbc
--- /dev/null
+++ b/mne/decoding/receptive_field.py
@@ -0,0 +1,486 @@
+# -*- coding: utf-8 -*-
+# Authors: Chris Holdgraf <choldgraf at gmail.com>
+#          Eric Larson <larson.eric.d at gmail.com>
+
+# License: BSD (3-clause)
+
+import numbers
+
+import numpy as np
+from scipy import linalg
+
+from .base import get_coef, BaseEstimator, _check_estimator
+from .time_delaying_ridge import TimeDelayingRidge
+from ..fixes import is_regressor
+from ..externals.six import string_types
+
+
+class ReceptiveField(BaseEstimator):
+    """Fit a receptive field model.
+
+    This allows you to fit an encoding model (stimulus to brain) or a decoding
+    model (brain to stimulus) using time-lagged input features (for example, a
+    spectro- or spatio-temporal receptive field, or STRF).
+
+    Parameters
+    ----------
+    tmin : float
+        The starting lag, in seconds (or samples if ``sfreq`` == 1).
+    tmax : float
+        The ending lag, in seconds (or samples if ``sfreq`` == 1).
+        Must be >= tmin.
+    sfreq : float
+        The sampling frequency used to convert times into samples.
+    feature_names : array, shape (n_features,) | None
+        Names for input features to the model. If None, feature names will
+        be auto-generated from the shape of input data after running `fit`.
+    estimator : instance of sklearn estimator | float | None
+        The model used in fitting inputs and outputs. This can be any
+        scikit-learn-style model that contains a fit and predict method. If a
+        float is passed, it will be interpreted as the `alpha` parameter
+        to be passed to a Ridge regression model. If `None`, then a Ridge
+        regression model with an alpha of 0 will be used.
+    fit_intercept : bool | None
+        If True (default), the sample mean is removed before fitting.
+        If ``estimator`` is a :class:`sklearn.base.BaseEstimator`,
+        this must be None or match ``estimator.fit_intercept``.
+    scoring : ['r2', 'corrcoef']
+        Defines how predictions will be scored. Currently must be one of
+        'r2' (coefficient of determination) or 'corrcoef' (the correlation
+        coefficient).
+    patterns : bool
+        If True, inverse coefficients will be computed upon fitting using the
+        covariance matrix of the inputs, and the cross-covariance of the
+        inputs/outputs, according to [5]_. Defaults to False.
+
+    Attributes
+    ----------
+    ``coef_`` : array, shape ([n_outputs, ]n_features, n_delays)
+        The coefficients from the model fit, reshaped for easy visualization.
+        During :meth:`mne.decoding.ReceptiveField.fit`, if ``y`` has one
+        dimension (time), the ``n_outputs`` dimension here is omitted.
+    ``patterns_`` : array, shape ([n_outputs, ]n_features, n_delays)
+        If fit, the inverted coefficients from the model.
+    ``delays_``: array, shape (n_delays,), dtype int
+        The delays used to fit the model, in indices. To return the delays
+        in seconds, use ``self.delays_ / self.sfreq``
+    ``valid_samples_`` : slice
+        The rows to keep during model fitting after removing rows with
+        missing values due to time delaying. This can be used to get an
+        output equivalent to using :func:`numpy.convolve` or
+        :func:`numpy.correlate` with ``mode='valid'``.
+
+    See Also
+    --------
+    mne.decoding.TimeDelayingRidge
+
+    Notes
+    -----
+    For a causal system, the encoding model will have significant
+    non-zero values only at positive lags. In other words, lags point
+    backward in time relative to the input, so positive lags correspond
+    to previous input time samples, while negative lags correspond to
+    future input time samples.
+
+    References
+    ----------
+    .. [1] Theunissen, F. E. et al. Estimating spatio-temporal receptive
+           fields of auditory and visual neurons from their responses to
+           natural stimuli. Network 12, 289-316 (2001).
+
+    .. [2] Willmore, B. & Smyth, D. Methods for first-order kernel
+           estimation: simple-cell receptive fields from responses to
+           natural scenes. Network 14, 553-77 (2003).
+
+    .. [3] Crosse, M. J., Di Liberto, G. M., Bednar, A. & Lalor, E. C. (2016).
+           The Multivariate Temporal Response Function (mTRF) Toolbox:
+           A MATLAB Toolbox for Relating Neural Signals to Continuous Stimuli.
+           Frontiers in Human Neuroscience 10, 604.
+           doi:10.3389/fnhum.2016.00604
+
+    .. [4] Holdgraf, C. R. et al. Rapid tuning shifts in human auditory cortex
+           enhance speech intelligibility. Nature Communications,
+           7, 13654 (2016). doi:10.1038/ncomms13654
+
+    .. [5] Haufe, S., Meinecke, F., Goergen, K., Daehne, S., Haynes, J.-D.,
+           Blankertz, B., & Biessmann, F. (2014). On the interpretation of
+           weight vectors of linear models in multivariate neuroimaging.
+           NeuroImage, 87, 96-110. doi:10.1016/j.neuroimage.2013.10.067
+    """
+
+    def __init__(self, tmin, tmax, sfreq, feature_names=None, estimator=None,
+                 fit_intercept=None, scoring='r2',
+                 patterns=False):  # noqa: D102
+        self.feature_names = feature_names
+        self.sfreq = float(sfreq)
+        self.tmin = tmin
+        self.tmax = tmax
+        self.estimator = 0. if estimator is None else estimator
+        self.fit_intercept = fit_intercept
+        self.scoring = scoring
+        self.patterns = patterns
+
+    def __repr__(self):  # noqa: D105
+        s = "tmin, tmax : (%.3f, %.3f), " % (self.tmin, self.tmax)
+        estimator = self.estimator
+        if not isinstance(estimator, string_types):
+            estimator = type(self.estimator)
+        s += "estimator : %s, " % (estimator,)
+        if hasattr(self, 'coef_'):
+            feats = self.feature_names
+            if len(feats) == 1:
+                s += "feature: %s, " % feats[0]
+            else:
+                s += "features : [%s, ..., %s], " % (feats[0], feats[-1])
+            s += "fit: True"
+        else:
+            s += "fit: False"
+        if hasattr(self, 'scores_'):
+            s += "scored (%s)" % self.scoring
+        return "<ReceptiveField  |  %s>" % s
+
+    def _delay_and_reshape(self, X, y=None):
+        """Delay and reshape the variables."""
+        if not isinstance(self.estimator_, TimeDelayingRidge):
+            # X is now shape (n_times, n_epochs, n_feats, n_delays)
+            X = _delay_time_series(X, self.tmin, self.tmax, self.sfreq,
+                                   fill_mean=self.fit_intercept)
+            X = _reshape_for_est(X)
+            # Concat times + epochs
+            if y is not None:
+                y = y.reshape(-1, y.shape[-1], order='F')
+        return X, y
+
+    def fit(self, X, y):
+        """Fit a receptive field model.
+
+        Parameters
+        ----------
+        X : array, shape (n_times[, n_epochs], n_features)
+            The input features for the model.
+        y : array, shape (n_times[, n_epochs][, n_outputs])
+            The output features for the model.
+
+        Returns
+        -------
+        self : instance
+            The instance so you can chain operations.
+        """
+        if self.scoring not in _SCORERS.keys():
+            raise ValueError('scoring must be one of %s, got'
+                             '%s ' % (sorted(_SCORERS.keys()), self.scoring))
+        from sklearn.base import clone
+        X, y, _, self._y_dim = self._check_dimensions(X, y)
+
+        if self.tmin > self.tmax:
+            raise ValueError('tmin (%s) must be at most tmax (%s)'
+                             % (self.tmin, self.tmax))
+        # Initialize delays
+        self.delays_ = _times_to_delays(self.tmin, self.tmax, self.sfreq)
+
+        # Define the slice that we should use in the middle
+        self.valid_samples_ = _delays_to_slice(self.delays_)
+
+        if isinstance(self.estimator, numbers.Real):
+            if self.fit_intercept is None:
+                self.fit_intercept = True
+            estimator = TimeDelayingRidge(self.tmin, self.tmax, self.sfreq,
+                                          alpha=self.estimator,
+                                          fit_intercept=self.fit_intercept)
+        elif is_regressor(self.estimator):
+            estimator = clone(self.estimator)
+            if self.fit_intercept is not None and \
+                    estimator.fit_intercept != self.fit_intercept:
+                raise ValueError(
+                    'Estimator fit_intercept (%s) != initialization '
+                    'fit_intercept (%s), initialize ReceptiveField with the '
+                    'same fit_intercept value or use fit_intercept=None'
+                    % (estimator.fit_intercept, self.fit_itercept))
+            self.fit_intercept = estimator.fit_intercept
+        else:
+            raise ValueError('`estimator` must be a float or an instance'
+                             ' of `BaseEstimator`,'
+                             ' got type %s.' % type(self.estimator))
+        self.estimator_ = estimator
+        del estimator
+        _check_estimator(self.estimator_)
+
+        # Create input features
+        n_times, n_epochs, n_feats = X.shape
+        n_outputs = y.shape[-1]
+        n_delays = len(self.delays_)
+
+        # Update feature names if we have none
+        if self.feature_names is None:
+            self.feature_names = ['feature_%s' % ii for ii in range(n_feats)]
+        if len(self.feature_names) != n_feats:
+            raise ValueError('n_features in X does not match feature names '
+                             '(%s != %s)' % (n_feats, len(self.feature_names)))
+
+        # Create input features
+        X, y = self._delay_and_reshape(X, y)
+
+        self.estimator_.fit(X, y)
+        coef = get_coef(self.estimator_, 'coef_')  # (n_targets, n_features)
+        shape = [n_feats, n_delays]
+        if self._y_dim > 1:
+            shape.insert(0, -1)
+        self.coef_ = coef.reshape(shape)
+
+        # Inverse-transform model weights
+        if self.patterns:
+            if isinstance(self.estimator_, TimeDelayingRidge):
+                cov_ = self.estimator_.cov_ / float(n_times * n_epochs - 1)
+                y = y.reshape(-1, y.shape[-1], order='F')
+            else:
+                X = X - X.mean(0, keepdims=True)
+                cov_ = np.cov(X.T)
+            del X
+
+            # Inverse output covariance
+            if y.ndim == 2 and y.shape[1] != 1:
+                y = y - y.mean(0, keepdims=True)
+                inv_Y = linalg.pinv(np.cov(y.T))
+            else:
+                inv_Y = 1. / float(n_times * n_epochs - 1)
+            del y
+
+            # Inverse coef according to Haufe's method
+            # patterns has shape (n_feats * n_delays, n_outputs)
+            coef = np.reshape(self.coef_, (n_feats * n_delays, n_outputs))
+            patterns = cov_.dot(coef.dot(inv_Y))
+            self.patterns_ = patterns.reshape(shape)
+
+        return self
+
+    def predict(self, X):
+        """Generate predictions with a receptive field.
+
+        Parameters
+        ----------
+        X : array, shape (n_times[, n_epochs], n_channels)
+            The input features for the model.
+
+        Returns
+        -------
+        y_pred : array, shape (n_times[, n_epochs][, n_outputs])
+            The output predictions. "Note that valid samples (those
+            unaffected by edge artifacts during the time delaying step) can
+            be obtained using ``y_pred[rf.valid_samples_]``.
+        """
+        if not hasattr(self, 'delays_'):
+            raise ValueError('Estimator has not been fit yet.')
+        X, _, X_dim = self._check_dimensions(X, None, predict=True)[:3]
+        del _
+        # convert to sklearn and back
+        pred_shape = X.shape[:-1]
+        if self._y_dim > 1:
+            pred_shape = pred_shape + (self.coef_.shape[0],)
+        X, _ = self._delay_and_reshape(X)
+        y_pred = self.estimator_.predict(X)
+        y_pred = y_pred.reshape(pred_shape, order='F')
+        shape = list(y_pred.shape)
+        if X_dim <= 2:
+            shape.pop(1)  # epochs
+            extra = 0
+        else:
+            extra = 1
+        shape = shape[:self._y_dim + extra]
+        y_pred.shape = shape
+        return y_pred
+
+    def score(self, X, y):
+        """Score predictions generated with a receptive field.
+
+        This calls `self.predict`, then masks the output of this
+        and `y` with `self.mask_prediction_`. Finally, it passes
+        this to a `sklearn` scorer.
+
+        Parameters
+        ----------
+        X : array, shape (n_times[, n_epochs], n_channels)
+            The input features for the model.
+        y : array, shape (n_times[, n_epochs][, n_outputs])
+            Used for scikit-learn compatibility.
+
+        Returns
+        -------
+        scores : list of float, shape (n_outputs,)
+            The scores estimated by the model for each output (e.g. mean
+            R2 of ``predict(X)``).
+        """
+        # Create our scoring object
+        scorer_ = _SCORERS[self.scoring]
+
+        # Generate predictions, then reshape so we can mask time
+        X, y = self._check_dimensions(X, y, predict=True)[:2]
+        n_times, n_epochs, n_outputs = y.shape
+        y_pred = self.predict(X)
+        y_pred = y_pred[self.valid_samples_]
+        y = y[self.valid_samples_]
+
+        # Re-vectorize and call scorer
+        y = y.reshape([-1, n_outputs], order='F')
+        y_pred = y_pred.reshape([-1, n_outputs], order='F')
+        assert y.shape == y_pred.shape
+        scores = scorer_(y, y_pred, multioutput='raw_values')
+        return scores
+
+    def _check_dimensions(self, X, y, predict=False):
+        X_dim = X.ndim
+        y_dim = y.ndim if y is not None else 0
+        if X_dim == 2:
+            # Ensure we have a 3D input by adding singleton epochs dimension
+            X = X[:, np.newaxis, :]
+            if y is not None:
+                if y_dim == 1:
+                    y = y[:, np.newaxis, np.newaxis]  # epochs, outputs
+                elif y_dim == 2:
+                    y = y[:, np.newaxis, :]  # epochs
+                else:
+                    raise ValueError('y must be shape (n_times[, n_epochs]'
+                                     '[,n_outputs], got %s' % (y.shape,))
+        elif X.ndim == 3:
+            if y is not None:
+                if y.ndim == 2:
+                    y = y[:, :, np.newaxis]  # Add an outputs dim
+                elif y.ndim != 3:
+                    raise ValueError('If X has 3 dimensions, '
+                                     'y must have 2 or 3 dimensions')
+        else:
+            raise ValueError('X must be shape (n_times[, n_epochs],'
+                             ' n_features), got %s' % (X.shape,))
+        if y is not None:
+            if X.shape[0] != y.shape[0]:
+                raise ValueError('X any y do not have the same n_times\n'
+                                 '%s != %s' % (X.shape[0], y.shape[0]))
+            if X.shape[1] != y.shape[1]:
+                raise ValueError('X any y do not have the same n_epochs\n'
+                                 '%s != %s' % (X.shape[1], y.shape[1]))
+            if predict and y.shape[-1] != len(self.estimator_.coef_):
+                    raise ValueError('Number of outputs does not match'
+                                     ' estimator coefficients dimensions')
+        return X, y, X_dim, y_dim
+
+
+def _delay_time_series(X, tmin, tmax, sfreq, fill_mean=False):
+    """Return a time-lagged input time series.
+
+    Parameters
+    ----------
+    X : array, shape (n_times[, n_epochs], n_features)
+        The time series to delay. Must be 2D or 3D.
+    tmin : int | float
+        The starting lag.
+    tmax : int | float
+        The ending lag.
+        Must be >= tmin.
+    sfreq : int | float
+        The sampling frequency of the series. Defaults to 1.0.
+    fill_mean : bool
+        If True, the fill value will be the mean along the time dimension
+        of the feature, and each cropped and delayed segment of data
+        will be shifted to have the same mean value (ensuring that mean
+        subtraction works properly). If False, the fill value will be zero.
+
+    Returns
+    -------
+    delayed : array, shape(n_times[, n_epochs][, n_features], n_delays)
+        The delayed data. It has the same shape as X, with an extra dimension
+        appended to the end.
+
+    Examples
+    --------
+    >>> tmin, tmax = -0.1, 0.2
+    >>> sfreq = 10.
+    >>> x = np.arange(1, 6)
+    >>> x_del = _delay_time_series(x, tmin, tmax, sfreq)
+    >>> print(x_del)
+    [[ 2.  1.  0.  0.]
+     [ 3.  2.  1.  0.]
+     [ 4.  3.  2.  1.]
+     [ 5.  4.  3.  2.]
+     [ 0.  5.  4.  3.]]
+    """
+    _check_delayer_params(tmin, tmax, sfreq)
+    delays = _times_to_delays(tmin, tmax, sfreq)
+    # Iterate through indices and append
+    delayed = np.zeros(X.shape + (len(delays),))
+    if fill_mean:
+        mean_value = X.mean(axis=0)
+        if X.ndim == 3:
+            mean_value = np.mean(mean_value, axis=0)
+        delayed[:] = mean_value[:, np.newaxis]
+    for ii, ix_delay in enumerate(delays):
+        # Create zeros to populate w/ delays
+        if ix_delay < 0:
+            out = delayed[:ix_delay, ..., ii]
+            use_X = X[-ix_delay:]
+        elif ix_delay > 0:
+            out = delayed[ix_delay:, ..., ii]
+            use_X = X[:-ix_delay]
+        else:  # == 0
+            out = delayed[..., ii]
+            use_X = X
+        out[:] = use_X
+        if fill_mean:
+            out[:] += (mean_value - use_X.mean(axis=0))
+    return delayed
+
+
+def _times_to_delays(tmin, tmax, sfreq):
+    """Convert a tmin/tmax in seconds to delays."""
+    # Convert seconds to samples
+    delays = np.arange(int(np.round(tmin * sfreq)),
+                       int(np.round(tmax * sfreq) + 1))
+    return delays
+
+
+def _delays_to_slice(delays):
+    """Find the slice to be taken in order to remove missing values."""
+    # Negative values == cut off rows at the end
+    min_delay = None if delays[-1] <= 0 else delays[-1]
+    # Positive values == cut off rows at the end
+    max_delay = None if delays[0] >= 0 else delays[0]
+    return slice(min_delay, max_delay)
+
+
+def _check_delayer_params(tmin, tmax, sfreq):
+    """Check delayer input parameters. For future custom delay support."""
+    if not isinstance(sfreq, (int, float, np.int_)):
+        raise ValueError('`sfreq` must be an integer or float')
+    sfreq = float(sfreq)
+
+    if not all([isinstance(ii, (int, float, np.int_))
+                for ii in [tmin, tmax]]):
+        raise ValueError('tmin/tmax must be an integer or float')
+    if not tmin <= tmax:
+        raise ValueError('tmin must be <= tmax')
+
+
+def _reshape_for_est(X_del):
+    """Convert X_del to a sklearn-compatible shape."""
+    n_times, n_epochs, n_feats, n_delays = X_del.shape
+    X_del = X_del.reshape(n_times, n_epochs, -1)  # concatenate feats
+    X_del = X_del.reshape(n_times * n_epochs, -1, order='F')
+    return X_del
+
+
+# Create a correlation scikit-learn-style scorer
+def _corr_score(y_true, y, multioutput=None):
+    from scipy.stats import pearsonr
+    assert multioutput == 'raw_values'
+    for this_y in (y_true, y):
+        if this_y.ndim != 2:
+            raise ValueError('inputs must shape (samples, outputs), got %s'
+                             % (this_y.shape,))
+    return np.array([pearsonr(y_true[:, ii], y[:, ii])[0]
+                     for ii in range(y.shape[-1])])
+
+
+def _r2_score(y_true, y, multioutput=None):
+    from sklearn.metrics import r2_score
+    return r2_score(y_true, y, multioutput=multioutput)
+
+
+_SCORERS = {'r2': _r2_score, 'corrcoef': _corr_score}
diff --git a/mne/decoding/search_light.py b/mne/decoding/search_light.py
index 6c9e301..f5b28d2 100644
--- a/mne/decoding/search_light.py
+++ b/mne/decoding/search_light.py
@@ -9,11 +9,12 @@ from .base import BaseEstimator, _check_estimator
 from ..parallel import parallel_func
 
 
-class _SearchLight(BaseEstimator, TransformerMixin):
+class SlidingEstimator(BaseEstimator, TransformerMixin):
     """Search Light.
 
     Fit, predict and score a series of models to each subset of the dataset
-    along the last dimension.
+    along the last dimension. Each entry in the last dimension is referred
+    to as a task.
 
     Parameters
     ----------
@@ -21,19 +22,22 @@ class _SearchLight(BaseEstimator, TransformerMixin):
         The base estimator to iteratively fit on a subset of the dataset.
     scoring : callable, string, defaults to None
         Score function (or loss function) with signature
-        score_func(y, y_pred, **kwargs).
+        ``score_func(y, y_pred, **kwargs)``.
+        Note that the predict_method is automatically identified if scoring is
+        a string (e.g. scoring="roc_auc" calls predict_proba) but is not
+        automatically set if scoring is a callable (e.g.
+        scoring=sklearn.metrics.roc_auc_score).
     n_jobs : int, optional (default=1)
         The number of jobs to run in parallel for both `fit` and `predict`.
         If -1, then the number of jobs is set to the number of cores.
+
+    Attributes
+    ----------
+    ``estimators_`` : array-like, shape (n_tasks,)
+        List of fitted scikit-learn estimators (one per task).
     """
-    def __repr__(self):
-        repr_str = '<' + super(_SearchLight, self).__repr__()
-        if hasattr(self, 'estimators_'):
-            repr_str = repr_str[:-1]
-            repr_str += ', fitted with %i estimators' % len(self.estimators_)
-        return repr_str + '>'
 
-    def __init__(self, base_estimator, scoring=None, n_jobs=1):
+    def __init__(self, base_estimator, scoring=None, n_jobs=1):  # noqa: D102
         _check_estimator(base_estimator)
         self.base_estimator = base_estimator
         self.n_jobs = n_jobs
@@ -42,39 +46,27 @@ class _SearchLight(BaseEstimator, TransformerMixin):
         if not isinstance(self.n_jobs, int):
             raise ValueError('n_jobs must be int, got %s' % n_jobs)
 
-    def fit_transform(self, X, y):
-        """
-        Fit and transform a series of independent estimators to the dataset.
-
-        Parameters
-        ----------
-        X : array, shape (n_samples, nd_features, n_estimators)
-            The training input samples. For each data slice, a clone estimator
-            is fitted independently. The feature dimension can be
-            multidimensional e.g.
-            X.shape = (n_samples, n_features_1, n_features_2, n_estimators)
-        y : array, shape (n_samples,) | (n_samples, n_targets)
-            The target values.
-
-        Returns
-        -------
-        y_pred : array, shape (n_samples, n_estimators) | (n_samples, n_estimators, n_targets)  # noqa
-            The predicted values for each estimator.
-        """
-        return self.fit(X, y).transform(X)
+    def __repr__(self):  # noqa: D105
+        repr_str = '<' + super(SlidingEstimator, self).__repr__()
+        if hasattr(self, 'estimators_'):
+            repr_str = repr_str[:-1]
+            repr_str += ', fitted with %i estimators' % len(self.estimators_)
+        return repr_str + '>'
 
-    def fit(self, X, y):
+    def fit(self, X, y, **fit_params):
         """Fit a series of independent estimators to the dataset.
 
         Parameters
         ----------
-        X : array, shape (n_samples, nd_features, n_estimators)
+        X : array, shape (n_samples, nd_features, n_tasks)
             The training input samples. For each data slice, a clone estimator
             is fitted independently. The feature dimension can be
             multidimensional e.g.
-            X.shape = (n_samples, n_features_1, n_features_2, n_estimators)
+            X.shape = (n_samples, n_features_1, n_features_2, n_tasks)
         y : array, shape (n_samples,) | (n_samples, n_targets)
             The target values.
+        **fit_params : dict of string -> object
+            Parameters to pass to the fit method of the estimator.
 
         Returns
         -------
@@ -83,15 +75,47 @@ class _SearchLight(BaseEstimator, TransformerMixin):
         """
         self._check_Xy(X, y)
         self.estimators_ = list()
+        self.fit_params = fit_params
         # For fitting, the parallelization is across estimators.
         parallel, p_func, n_jobs = parallel_func(_sl_fit, self.n_jobs)
         n_jobs = min(n_jobs, X.shape[-1])
         estimators = parallel(
-            p_func(self.base_estimator, split, y)
+            p_func(self.base_estimator, split, y, **fit_params)
             for split in np.array_split(X, n_jobs, axis=-1))
-        self.estimators_ = np.concatenate(estimators, 0)
+
+        # Each parallel job can have a different number of training estimators
+        # We can't directly concatenate them because of sklearn's Bagging API
+        # (see scikit-learn #9720)
+        self.estimators_ = np.empty(X.shape[-1], dtype=object)
+        idx = 0
+        for job_estimators in estimators:
+            for est in job_estimators:
+                self.estimators_[idx] = est
+                idx += 1
         return self
 
+    def fit_transform(self, X, y, **fit_params):
+        """Fit and transform a series of independent estimators to the dataset.
+
+        Parameters
+        ----------
+        X : array, shape (n_samples, nd_features, n_tasks)
+            The training input samples. For each task, a clone estimator
+            is fitted independently. The feature dimension can be
+            multidimensional e.g.
+            X.shape = (n_samples, n_features_1, n_features_2, n_estimators)
+        y : array, shape (n_samples,) | (n_samples, n_targets)
+            The target values.
+        **fit_params : dict of string -> object
+            Parameters to pass to the fit method of the estimator.
+
+        Returns
+        -------
+        y_pred : array, shape (n_samples, n_tasks) | (n_samples, n_tasks, n_targets)
+            The predicted values for each estimator.
+        """  # noqa: E501
+        return self.fit(X, y, **fit_params).transform(X)
+
     def _transform(self, X, method):
         """Aux. function to make parallel predictions/transformation."""
         self._check_Xy(X)
@@ -108,71 +132,73 @@ class _SearchLight(BaseEstimator, TransformerMixin):
         y_pred = parallel(p_func(est, x, method)
                           for (est, x) in zip(est_splits, X_splits))
 
-        if n_jobs > 1:
-            y_pred = np.concatenate(y_pred, axis=1)
-        else:
-            y_pred = y_pred[0]
+        y_pred = np.concatenate(y_pred, axis=1)
         return y_pred
 
     def transform(self, X):
-        """Transform each data slice with a series of independent estimators.
+        """Transform each data slice/task with a series of independent estimators.
+
+        The number of tasks in X should match the number of tasks/estimators
+        given at fit time.
 
         Parameters
         ----------
-        X : array, shape (n_samples, nd_features, n_estimators)
-            The input samples. For each data slice, the corresponding estimator
-            makes a transformation of the data:
-            e.g. [estimators[ii].transform(X[..., ii])
-                  for ii in range(n_estimators)]
+        X : array, shape (n_samples, nd_features, n_tasks)
+            The input samples. For each data slice/task, the corresponding
+            estimator makes a transformation of the data, e.g.
+            ``[estimators[ii].transform(X[..., ii]) for ii in range(n_estimators)]``.
             The feature dimension can be multidimensional e.g.
-            X.shape = (n_samples, n_features_1, n_features_2, n_estimators)
+            X.shape = (n_samples, n_features_1, n_features_2, n_tasks)
 
         Returns
         -------
         Xt : array, shape (n_samples, n_estimators)
             The transformed values generated by each estimator.
-        """
+        """  # noqa: E501
         return self._transform(X, 'transform')
 
     def predict(self, X):
-        """Predict each data slice with a series of independent estimators.
+        """Predict each data slice/task with a series of independent estimators.
+
+        The number of tasks in X should match the number of tasks/estimators
+        given at fit time.
 
         Parameters
         ----------
-        X : array, shape (n_samples, nd_features, n_estimators)
+        X : array, shape (n_samples, nd_features, n_tasks)
             The input samples. For each data slice, the corresponding estimator
-            makes the sample predictions:
-            e.g. [estimators[ii].predict(X[..., ii])
-                  for ii in range(n_estimators)]
+            makes the sample predictions, e.g.:
+            ``[estimators[ii].predict(X[..., ii]) for ii in range(n_estimators)]``.
             The feature dimension can be multidimensional e.g.
-            X.shape = (n_samples, n_features_1, n_features_2, n_estimators)
+            X.shape = (n_samples, n_features_1, n_features_2, n_tasks)
 
         Returns
         -------
-        y_pred : array, shape (n_samples, n_estimators) | (n_samples, n_estimators, n_targets)  # noqa
+        y_pred : array, shape (n_samples, n_estimators) | (n_samples, n_tasks, n_targets)
             Predicted values for each estimator/data slice.
-        """
+        """  # noqa: E501
         return self._transform(X, 'predict')
 
     def predict_proba(self, X):
         """Predict each data slice with a series of independent estimators.
 
+        The number of tasks in X should match the number of tasks/estimators
+        given at fit time.
+
         Parameters
         ----------
-        X : array, shape (n_samples, nd_features, n_estimators)
+        X : array, shape (n_samples, nd_features, n_tasks)
             The input samples. For each data slice, the corresponding estimator
-            makes the sample probabilistic predictions:
-            e.g. [estimators[ii].predict_proba(X[..., ii])
-                  for ii in range(n_estimators)]
+            makes the sample probabilistic predictions, e.g.:
+            ``[estimators[ii].predict_proba(X[..., ii]) for ii in range(n_estimators)]``.
             The feature dimension can be multidimensional e.g.
-            X.shape = (n_samples, n_features_1, n_features_2, n_estimators)
-
+            X.shape = (n_samples, n_features_1, n_features_2, n_tasks)
 
         Returns
         -------
-        y_pred : array, shape (n_samples, n_estimators, n_classes)
-            Predicted probabilities for each estimator/data slice.
-        """
+        y_pred : array, shape (n_samples, n_tasks, n_classes)
+            Predicted probabilities for each estimator/data slice/task.
+        """  # noqa: E501
         return self._transform(X, 'predict_proba')
 
     def decision_function(self, X):
@@ -180,23 +206,22 @@ class _SearchLight(BaseEstimator, TransformerMixin):
 
         Parameters
         ----------
-        X : array, shape (n_samples, nd_features, n_estimators)
+        X : array, shape (n_samples, nd_features, n_tasks)
             The input samples. For each data slice, the corresponding estimator
-            outputs the distance to the hyperplane:
-            e.g. [estimators[ii].decision_function(X[..., ii])
-                  for ii in range(n_estimators)]
+            outputs the distance to the hyperplane, e.g.:
+            ``[estimators[ii].decision_function(X[..., ii]) for ii in range(n_estimators)]``.
             The feature dimension can be multidimensional e.g.
             X.shape = (n_samples, n_features_1, n_features_2, n_estimators)
 
         Returns
         -------
-        y_pred : array, shape (n_samples, n_estimators, n_classes * (n_classes-1) / 2)  # noqa
+        y_pred : array, shape (n_samples, n_estimators, n_classes * (n_classes-1) // 2)
             Predicted distances for each estimator/data slice.
 
         Notes
         -----
-        This requires base_estimator to have a `decision_function` method.
-        """
+        This requires base_estimator to have a ``decision_function`` method.
+        """  # noqa: E501
         return self._transform(X, 'decision_function')
 
     def _check_Xy(self, X, y=None):
@@ -208,16 +233,20 @@ class _SearchLight(BaseEstimator, TransformerMixin):
             raise ValueError('X must have at least 3 dimensions.')
 
     def score(self, X, y):
-        """Returns the score obtained for each estimators/data slice couple.
+        """Score each estimator on each task.
+
+        The number of tasks in X should match the number of tasks/estimators
+        given at fit time, i.e. we need
+        ``X.shape[-1] == len(self.estimators_)``.
 
         Parameters
         ----------
-        X : array, shape (n_samples, nd_features, n_estimators)
+        X : array, shape (n_samples, nd_features, n_tasks)
             The input samples. For each data slice, the corresponding estimator
-            scores the prediction: e.g. [estimators[ii].score(X[..., ii], y)
-                                         for ii in range(n_estimators)]
+            scores the prediction, e.g.:
+            ``[estimators[ii].score(X[..., ii], y) for ii in range(n_estimators)]``.
             The feature dimension can be multidimensional e.g.
-            X.shape = (n_samples, n_features_1, n_features_2, n_estimators)
+            X.shape = (n_samples, n_features_1, n_features_2, n_tasks)
 
         y : array, shape (n_samples,) | (n_samples, n_targets)
             The target values.
@@ -225,23 +254,17 @@ class _SearchLight(BaseEstimator, TransformerMixin):
         Returns
         -------
         score : array, shape (n_samples, n_estimators)
-            Score for each estimator / data slice couple.
-        """
-        from sklearn.metrics import make_scorer, get_scorer
+            Score for each estimator/task.
+        """  # noqa: E501
+        from sklearn.metrics.scorer import check_scoring
+
         self._check_Xy(X)
         if X.shape[-1] != len(self.estimators_):
             raise ValueError('The number of estimators does not match '
                              'X.shape[-1]')
 
-        # If scoring is None (default), the predictions are internally
-        # generated by estimator.score(). Else, we must first get the
-        # predictions based on the scorer.
-        if not isinstance(self.scoring, str):
-            scoring_ = (make_scorer(self.scoring) if self.scoring is
-                        not None else self.scoring)
-
-        elif self.scoring is not None:
-            scoring_ = get_scorer(self.scoring)
+        scoring = check_scoring(self.base_estimator, self.scoring)
+        y = _fix_auc(scoring, y)
 
         # For predictions/transforms the parallelization is across the data and
         # not across the estimators to avoid memory load.
@@ -249,18 +272,15 @@ class _SearchLight(BaseEstimator, TransformerMixin):
         n_jobs = min(n_jobs, X.shape[-1])
         X_splits = np.array_split(X, n_jobs, axis=-1)
         est_splits = np.array_split(self.estimators_, n_jobs)
-        score = parallel(p_func(est, scoring_, X, y)
+        score = parallel(p_func(est, scoring, x, y)
                          for (est, x) in zip(est_splits, X_splits))
 
-        if n_jobs > 1:
-            score = np.concatenate(score, axis=0)
-        else:
-            score = score[0]
+        score = np.concatenate(score, axis=0)
         return score
 
 
-def _sl_fit(estimator, X, y):
-    """Aux. function to fit _SearchLight in parallel.
+def _sl_fit(estimator, X, y, **fit_params):
+    """Aux. function to fit SlidingEstimator in parallel.
 
     Fit a clone estimator to each slice of data.
 
@@ -273,6 +293,8 @@ def _sl_fit(estimator, X, y):
         X.shape = (n_samples, n_features_1, n_features_2, n_estimators)
     y : array, shape (n_sample, )
         The target values.
+    fit_params : dict | None
+        Parameters to pass to the fit method of the estimator.
 
     Returns
     -------
@@ -283,13 +305,13 @@ def _sl_fit(estimator, X, y):
     estimators_ = list()
     for ii in range(X.shape[-1]):
         est = clone(estimator)
-        est.fit(X[..., ii], y)
+        est.fit(X[..., ii], y, **fit_params)
         estimators_.append(est)
     return estimators_
 
 
 def _sl_transform(estimators, X, method):
-    """Aux. function to transform _SearchLight in parallel.
+    """Aux. function to transform SlidingEstimator in parallel.
 
     Applies transform/predict/decision_function etc for each slice of data.
 
@@ -305,9 +327,9 @@ def _sl_transform(estimators, X, method):
 
     Returns
     -------
-    y_pred : array, shape (n_samples, n_estimators, n_classes * (n_classes-1) / 2)  # noqa
+    y_pred : array, shape (n_samples, n_estimators, n_classes * (n_classes-1) // 2)
         The transformations for each slice of data.
-    """
+    """  # noqa: E501
     for ii, est in enumerate(estimators):
         transform = getattr(est, method)
         _y_pred = transform(X[..., ii])
@@ -319,32 +341,24 @@ def _sl_transform(estimators, X, method):
 
 
 def _sl_init_pred(y_pred, X):
-    """Aux. function to _SearchLight to initialize y_pred."""
-    n_sample, n_iter = X.shape[0], X.shape[-1]
-    if y_pred.ndim > 1:
-        # for estimator that generate multidimensional y_pred,
-        # e.g. clf.predict_proba()
-        y_pred = np.zeros(np.r_[n_sample, n_iter, y_pred.shape[1:]],
-                          y_pred.dtype)
-    else:
-        # for estimator that generate unidimensional y_pred,
-        # e.g. clf.predict()
-        y_pred = np.zeros((n_sample, n_iter), y_pred.dtype)
+    """Aux. function to SlidingEstimator to initialize y_pred."""
+    n_sample, n_tasks = X.shape[0], X.shape[-1]
+    y_pred = np.zeros((n_sample, n_tasks) + y_pred.shape[1:], y_pred.dtype)
     return y_pred
 
 
 def _sl_score(estimators, scoring, X, y):
-    """Aux. function to score _SearchLight in parallel.
+    """Aux. function to score SlidingEstimator in parallel.
 
     Predict and score each slice of data.
 
     Parameters
     ----------
-    estimators : list of estimators
+    estimators : list, shape (n_tasks,)
         The fitted estimators.
-    X : array, shape (n_samples, nd_features, n_estimators)
+    X : array, shape (n_samples, nd_features, n_tasks)
         The target data. The feature dimension can be multidimensional e.g.
-        X.shape = (n_samples, n_features_1, n_features_2, n_estimators)
+        X.shape = (n_samples, n_features_1, n_features_2, n_tasks)
     scoring : callable, string or None
         If scoring is None (default), the predictions are internally
         generated by estimator.score(). Else, we must first get the
@@ -354,31 +368,19 @@ def _sl_score(estimators, scoring, X, y):
 
     Returns
     -------
-    score : array, shape (n_estimators,)
-        The score for each slice of data.
+    score : array, shape (n_tasks,)
+        The score for each task / slice of data.
     """
-    n_iter = X.shape[-1]
+    n_tasks = X.shape[-1]
+    score = np.zeros(n_tasks)
     for ii, est in enumerate(estimators):
-        if scoring is not None:
-            _score = scoring(est, X[..., ii], y)
-        else:
-            _score = est.score(X[..., ii], y)
-        # Initialize array of scores on the first score iteration
-        if ii == 0:
-            if isinstance(_score, np.ndarray):
-                dtype = _score.dtype
-                shape = _score.shape
-                np.r_[n_iter, _score.shape]
-            else:
-                dtype = type(_score)
-                shape = n_iter
-            score = np.zeros(shape, dtype)
-        score[ii] = _score
+        score[ii] = scoring(est, X[..., ii], y)
     return score
 
 
 def _check_method(estimator, method):
-    """Checks that an estimator has the method attribute.
+    """Check that an estimator has the method attribute.
+
     If method == 'transform'  and estimator does not have 'transform', use
     'predict' instead.
     """
@@ -389,11 +391,11 @@ def _check_method(estimator, method):
     return method
 
 
-class _GeneralizationLight(_SearchLight):
-    """Generalization Light
+class GeneralizingEstimator(SlidingEstimator):
+    """Generalization Light.
 
     Fit a search-light along the last dimension and use them to apply a
-    systematic cross-feature generalization.
+    systematic cross-tasks generalization.
 
     Parameters
     ----------
@@ -401,20 +403,25 @@ class _GeneralizationLight(_SearchLight):
         The base estimator to iteratively fit on a subset of the dataset.
     scoring : callable | string | None
         Score function (or loss function) with signature
-        score_func(y, y_pred, **kwargs).
+        ``score_func(y, y_pred, **kwargs)``.
+        Note that the predict_method is automatically identified if scoring is
+        a string (e.g. scoring="roc_auc" calls predict_proba) but is not
+        automatically set if scoring is a callable (e.g.
+        scoring=sklearn.metrics.roc_auc_score).
     n_jobs : int, optional (default=1)
         The number of jobs to run in parallel for both `fit` and `predict`.
         If -1, then the number of jobs is set to the number of cores.
     """
-    def __repr__(self):
-        repr_str = super(_GeneralizationLight, self).__repr__()
+
+    def __repr__(self):  # noqa: D105
+        repr_str = super(GeneralizingEstimator, self).__repr__()
         if hasattr(self, 'estimators_'):
             repr_str = repr_str[:-1]
             repr_str += ', fitted with %i estimators>' % len(self.estimators_)
         return repr_str
 
     def _transform(self, X, method):
-        """Aux. function to make parallel predictions/transformation"""
+        """Aux. function to make parallel predictions/transformation."""
         self._check_Xy(X)
         method = _check_method(self.base_estimator, method)
         parallel, p_func, n_jobs = parallel_func(_gl_transform, self.n_jobs)
@@ -457,14 +464,13 @@ class _GeneralizationLight(_SearchLight):
 
         Returns
         -------
-        y_pred : array, shape (n_samples, n_estimators, n_slices) | (n_samples, n_estimators, n_slices, n_targets) # noqa
+        y_pred : array, shape (n_samples, n_estimators, n_slices) | (n_samples, n_estimators, n_slices, n_targets)
             The predicted values for each estimator.
-        """
+        """  # noqa: E501
         return self._transform(X, 'predict')
 
     def predict_proba(self, X):
-        """Estimate probabilistic estimates of each data slice with all
-        possible estimators.
+        """Estimate probabilistic estimates of each data slice with all possible estimators.
 
         Parameters
         ----------
@@ -472,7 +478,7 @@ class _GeneralizationLight(_SearchLight):
             The training input samples. For each data slice, a fitted estimator
             predicts a slice of the data. The feature dimension can be
             multidimensional e.g.
-            X.shape = (n_samples, n_features_1, n_features_2, n_estimators)
+            ``X.shape = (n_samples, n_features_1, n_features_2, n_estimators)``.
 
         Returns
         -------
@@ -482,7 +488,7 @@ class _GeneralizationLight(_SearchLight):
         Notes
         -----
         This requires base_estimator to have a `predict_proba` method.
-        """
+        """  # noqa: E501
         return self._transform(X, 'predict_proba')
 
     def decision_function(self, X):
@@ -492,20 +498,20 @@ class _GeneralizationLight(_SearchLight):
         ----------
         X : array, shape (n_samples, nd_features, n_slices)
             The training input samples. Each estimator outputs the distance to
-            its hyperplane: e.g. [estimators[ii].decision_function(X[..., ii])
-                                  for ii in range(n_estimators)]
+            its hyperplane, e.g.:
+            ``[estimators[ii].decision_function(X[..., ii]) for ii in range(n_estimators)]``.
             The feature dimension can be multidimensional e.g.
-            X.shape = (n_samples, n_features_1, n_features_2, n_estimators)
+            ``X.shape = (n_samples, n_features_1, n_features_2, n_estimators)``.
 
         Returns
         -------
-        y_pred : array, shape (n_samples, n_estimators, n_slices, n_classes * (n_classes-1) / 2) # noqa
+        y_pred : array, shape (n_samples, n_estimators, n_slices, n_classes * (n_classes-1) // 2)
             The predicted values for each estimator.
 
         Notes
         -----
-        This requires base_estimator to have a `decision_function` method.
-        """
+        This requires base_estimator to have a ``decision_function`` method.
+        """  # noqa: E501
         return self._transform(X, 'decision_function')
 
     def score(self, X, y):
@@ -515,10 +521,10 @@ class _GeneralizationLight(_SearchLight):
         ----------
         X : array, shape (n_samples, nd_features, n_slices)
             The input samples. For each data slice, the corresponding estimator
-            scores the prediction: e.g. [estimators[ii].score(X[..., ii], y)
-                                         for ii in range(n_slices)]
+            scores the prediction, e.g.:
+            ``[estimators[ii].score(X[..., ii], y) for ii in range(n_slices)]``.
             The feature dimension can be multidimensional e.g.
-            X.shape = (n_samples, n_features_1, n_features_2, n_estimators)
+            ``X.shape = (n_samples, n_features_1, n_features_2, n_estimators)``.
         y : array, shape (n_samples,) | (n_samples, n_targets)
             The target values.
 
@@ -526,25 +532,27 @@ class _GeneralizationLight(_SearchLight):
         -------
         score : array, shape (n_samples, n_estimators, n_slices)
             Score for each estimator / data slice couple.
-        """
+        """  # noqa: E501
+        from sklearn.metrics.scorer import check_scoring
         self._check_Xy(X)
         # For predictions/transforms the parallelization is across the data and
         # not across the estimators to avoid memory load.
         parallel, p_func, n_jobs = parallel_func(_gl_score, self.n_jobs)
         n_jobs = min(n_jobs, X.shape[-1])
         X_splits = np.array_split(X, n_jobs, axis=-1)
-        score = parallel(p_func(self.estimators_, x, y) for x in X_splits)
+        scoring = check_scoring(self.base_estimator, self.scoring)
+        y = _fix_auc(scoring, y)
+        score = parallel(p_func(self.estimators_, scoring, x, y)
+                         for x in X_splits)
 
-        if n_jobs > 1:
-            score = np.concatenate(score, axis=1)
-        else:
-            score = score[0]
+        score = np.concatenate(score, axis=1)
         return score
 
 
 def _gl_transform(estimators, X, method):
-    """Transform the dataset by applying each estimator to all slices of
-    the data.
+    """Transform the dataset.
+
+    This will apply each estimator to all slices of the data.
 
     Parameters
     ----------
@@ -579,7 +587,7 @@ def _gl_transform(estimators, X, method):
 
 
 def _gl_init_pred(y_pred, X, n_train):
-    """Aux. function to _GeneralizationLight to initialize y_pred"""
+    """Aux. function to GeneralizingEstimator to initialize y_pred."""
     n_sample, n_iter = X.shape[0], X.shape[-1]
     if y_pred.ndim == 3:
         y_pred = np.zeros((n_sample, n_train, n_iter, y_pred.shape[-1]),
@@ -589,14 +597,19 @@ def _gl_init_pred(y_pred, X, n_train):
     return y_pred
 
 
-def _gl_score(estimators, X, y):
-    """Aux. function to score _GeneralizationLight in parallel.
+def _gl_score(estimators, scoring, X, y):
+    """Score GeneralizingEstimator in parallel.
+
     Predict and score each slice of data.
 
     Parameters
     ----------
     estimators : list of estimators
         The fitted estimators.
+    scoring : callable, string or None
+        If scoring is None (default), the predictions are internally
+        generated by estimator.score(). Else, we must first get the
+        predictions to pass them to ad-hoc scorer.
     X : array, shape (n_samples, nd_features, n_slices)
         The target data. The feature dimension can be multidimensional e.g.
         X.shape = (n_samples, n_features_1, n_features_2, n_estimators)
@@ -610,20 +623,30 @@ def _gl_score(estimators, X, y):
     """
     # FIXME: The level parallization may be a bit high, and might be memory
     # consuming. Perhaps need to lower it down to the loop across X slices.
-    n_iter = X.shape[-1]
-    n_est = len(estimators)
+    score_shape = [len(estimators), X.shape[-1]]
     for ii, est in enumerate(estimators):
         for jj in range(X.shape[-1]):
-            _score = est.score(X[..., jj], y)
-
+            _score = scoring(est, X[..., jj], y)
             # Initialize array of predictions on the first score iteration
             if (ii == 0) & (jj == 0):
-                if isinstance(_score, np.ndarray):
-                    dtype = _score.dtype
-                    shape = np.r_[n_est, n_iter, _score.shape]
-                else:
-                    dtype = type(_score)
-                    shape = [n_est, n_iter]
-                score = np.zeros(shape, dtype)
+                dtype = type(_score)
+                score = np.zeros(score_shape, dtype)
             score[ii, jj, ...] = _score
     return score
+
+
+def _fix_auc(scoring, y):
+    from sklearn.preprocessing import LabelEncoder
+    # This fixes sklearn's inability to compute roc_auc when y not in [0, 1]
+    # scikit-learn/scikit-learn#6874
+    if scoring is not None:
+        if (
+            hasattr(scoring, '_score_func') and
+            hasattr(scoring._score_func, '__name__') and
+            scoring._score_func.__name__ == 'roc_auc_score'
+        ):
+            if np.ndim(y) != 1 or len(set(y)) != 2:
+                raise ValueError('roc_auc scoring can only be computed for '
+                                 'two-class problems.')
+            y = LabelEncoder().fit_transform(y)
+    return y
diff --git a/mne/decoding/tests/test_base.py b/mne/decoding/tests/test_base.py
new file mode 100644
index 0000000..647340e
--- /dev/null
+++ b/mne/decoding/tests/test_base.py
@@ -0,0 +1,230 @@
+# Author: Jean-Remi King, <jeanremi.king at gmail.com>
+#         Marijn van Vliet, <w.m.vanvliet at gmail.com>
+#
+# License: BSD (3-clause)
+
+import numpy as np
+from numpy.testing import assert_array_equal, assert_array_almost_equal
+from nose.tools import assert_true, assert_equal, assert_raises
+from mne.fixes import is_regressor, is_classifier
+from mne.utils import requires_version
+from mne.decoding.base import (_get_inverse_funcs, LinearModel, get_coef,
+                               cross_val_multiscore)
+from mne.decoding.search_light import SlidingEstimator
+from mne.decoding import Scaler
+
+
+def _make_data(n_samples=1000, n_features=5, n_targets=3):
+    """Generate some testing data.
+
+    Parameters
+    ----------
+    n_samples : int
+        The number of samples.
+    n_features : int
+        The number of features.
+    n_targets : int
+        The number of targets.
+
+    Returns
+    -------
+    X : ndarray, shape (n_samples, n_features)
+        The measured data.
+    Y : ndarray, shape (n_samples, n_targets)
+        The latent variables generating the data.
+    A : ndarray, shape (n_features, n_targets)
+        The forward model, mapping the latent variables (=Y) to the measured
+        data (=X).
+    """
+
+    # Define Y latent factors
+    np.random.seed(0)
+    cov_Y = np.eye(n_targets) * 10 + np.random.rand(n_targets, n_targets)
+    cov_Y = (cov_Y + cov_Y.T) / 2.
+    mean_Y = np.random.rand(n_targets)
+    Y = np.random.multivariate_normal(mean_Y, cov_Y, size=n_samples)
+
+    # The Forward model
+    A = np.random.randn(n_features, n_targets)
+
+    X = Y.dot(A.T)
+    X += np.random.randn(n_samples, n_features)  # add noise
+    X += np.random.rand(n_features)  # Put an offset
+
+    return X, Y, A
+
+
+ at requires_version('sklearn', '0.17')
+def test_get_coef():
+    """Test getting linear coefficients (filters/patterns) from estimators."""
+    from sklearn.base import TransformerMixin, BaseEstimator
+    from sklearn.pipeline import make_pipeline
+    from sklearn.preprocessing import StandardScaler
+    from sklearn.linear_model import Ridge, LinearRegression
+
+    lm = LinearModel()
+    assert_true(is_classifier(lm))
+
+    lm = LinearModel(Ridge())
+    assert_true(is_regressor(lm))
+
+    # Define a classifier, an invertible transformer and an non-invertible one.
+
+    class Clf(BaseEstimator):
+        def fit(self, X, y):
+            return self
+
+    class NoInv(TransformerMixin):
+        def fit(self, X, y):
+            return self
+
+        def transform(self, X):
+            return X
+
+    class Inv(NoInv):
+        def inverse_transform(self, X):
+            return X
+
+    X, y, A = _make_data(n_samples=2000, n_features=3, n_targets=1)
+
+    # I. Test inverse function
+
+    # Check that we retrieve the right number of inverse functions even if
+    # there are nested pipelines
+    good_estimators = [
+        (1, make_pipeline(Inv(), Clf())),
+        (2, make_pipeline(Inv(), Inv(), Clf())),
+        (3, make_pipeline(Inv(), make_pipeline(Inv(), Inv()), Clf())),
+    ]
+
+    for expected_n, est in good_estimators:
+        est.fit(X, y)
+        assert_true(expected_n == len(_get_inverse_funcs(est)))
+
+    bad_estimators = [
+        Clf(),  # no preprocessing
+        Inv(),  # final estimator isn't classifier
+        make_pipeline(NoInv(), Clf()),  # first step isn't invertible
+        make_pipeline(Inv(), make_pipeline(
+            Inv(), NoInv()), Clf()),  # nested step isn't invertible
+    ]
+    for est in bad_estimators:
+        est.fit(X, y)
+        invs = _get_inverse_funcs(est)
+        assert_equal(invs, list())
+
+    # II. Test get coef for simple estimator and pipelines
+    for clf in (lm, make_pipeline(StandardScaler(), lm)):
+        clf.fit(X, y)
+        # Retrieve final linear model
+        filters = get_coef(clf, 'filters_', False)
+        if hasattr(clf, 'steps'):
+            coefs = clf.steps[-1][-1].model.coef_
+        else:
+            coefs = clf.model.coef_
+        assert_array_equal(filters, coefs[0])
+        patterns = get_coef(clf, 'patterns_', False)
+        assert_true(filters[0] != patterns[0])
+        n_chans = X.shape[1]
+        assert_array_equal(filters.shape, patterns.shape, [n_chans, n_chans])
+
+    # Inverse transform linear model
+    filters_inv = get_coef(clf, 'filters_', True)
+    assert_true(filters[0] != filters_inv[0])
+    patterns_inv = get_coef(clf, 'patterns_', True)
+    assert_true(patterns[0] != patterns_inv[0])
+
+    # Check with search_light and combination of preprocessing ending with sl:
+    slider = SlidingEstimator(make_pipeline(StandardScaler(), lm))
+    X = np.transpose([X, -X], [1, 2, 0])  # invert X across 2 time samples
+    clfs = (make_pipeline(Scaler(None, scalings='mean'), slider), slider)
+    for clf in clfs:
+        clf.fit(X, y)
+        for inverse in (True, False):
+            patterns = get_coef(clf, 'patterns_', inverse)
+            filters = get_coef(clf, 'filters_', inverse)
+            assert_array_equal(filters.shape, patterns.shape,
+                               X.shape[1:])
+            # the two time samples get inverted patterns
+            assert_equal(patterns[0, 0], -patterns[0, 1])
+    for t in [0, 1]:
+        assert_array_equal(get_coef(clf.estimators_[t], 'filters_', False),
+                           filters[:, t])
+
+    # Check patterns with more than 1 regressor
+    for n_features in [1, 5]:
+        for n_targets in [1, 3]:
+            X, Y, A = _make_data(n_samples=5000, n_features=5, n_targets=3)
+            lm = LinearModel(LinearRegression()).fit(X, Y)
+            assert_array_equal(lm.filters_.shape, lm.patterns_.shape)
+            assert_array_equal(lm.filters_.shape, [3, 5])
+            assert_array_almost_equal(A, lm.patterns_.T, decimal=2)
+            lm = LinearModel(Ridge(alpha=1)).fit(X, Y)
+            assert_array_almost_equal(A, lm.patterns_.T, decimal=2)
+
+
+ at requires_version('sklearn', '0.15')
+def test_linearmodel():
+    """Test LinearModel class for computing filters and patterns."""
+    from sklearn.linear_model import LinearRegression
+    np.random.seed(42)
+    clf = LinearModel()
+    n, n_features = 20, 3
+    X = np.random.rand(n, n_features)
+    y = np.arange(n) % 2
+    clf.fit(X, y)
+    assert_equal(clf.filters_.shape, (n_features,))
+    assert_equal(clf.patterns_.shape, (n_features,))
+    assert_raises(ValueError, clf.fit, np.random.rand(n, n_features, 99), y)
+
+    # check multi-target fit
+    n_targets = 5
+    clf = LinearModel(LinearRegression())
+    Y = np.random.rand(n, n_targets)
+    clf.fit(X, Y)
+    assert_equal(clf.filters_.shape, (n_targets, n_features))
+    assert_equal(clf.patterns_.shape, (n_targets, n_features))
+    assert_raises(ValueError, clf.fit, X, np.random.rand(n, n_features, 99))
+
+
+ at requires_version('sklearn', '0.18')
+def test_cross_val_multiscore():
+    """Test cross_val_multiscore for computing scores on decoding over time."""
+    from sklearn.model_selection import KFold, cross_val_score
+    from sklearn.linear_model import LogisticRegression
+
+    # compare to cross-val-score
+    X = np.random.rand(20, 3)
+    y = np.arange(20) % 2
+    clf = LogisticRegression()
+    cv = KFold(2, random_state=0)
+    assert_array_equal(cross_val_score(clf, X, y, cv=cv),
+                       cross_val_multiscore(clf, X, y, cv=cv))
+
+    # Test with search light
+    X = np.random.rand(20, 4, 3)
+    y = np.arange(20) % 2
+    clf = SlidingEstimator(LogisticRegression(), scoring='accuracy')
+    scores_acc = cross_val_multiscore(clf, X, y, cv=cv)
+    assert_array_equal(np.shape(scores_acc), [2, 3])
+
+    # check values
+    scores_acc_manual = list()
+    for train, test in cv.split(X, y):
+        clf.fit(X[train], y[train])
+        scores_acc_manual.append(clf.score(X[test], y[test]))
+    assert_array_equal(scores_acc, scores_acc_manual)
+
+    # check scoring metric
+    # raise an error if scoring is defined at cross-val-score level and
+    # search light, because search light does not return a 1-dimensional
+    # prediction.
+    assert_raises(ValueError, cross_val_multiscore, clf, X, y, cv=cv,
+                  scoring='roc_auc')
+    clf = SlidingEstimator(LogisticRegression(), scoring='roc_auc')
+    scores_auc = cross_val_multiscore(clf, X, y, cv=cv, n_jobs=1)
+    scores_auc_manual = list()
+    for train, test in cv.split(X, y):
+        clf.fit(X[train], y[train])
+        scores_auc_manual.append(clf.score(X[test], y[test]))
+    assert_array_equal(scores_auc, scores_auc_manual)
diff --git a/mne/decoding/tests/test_csp.py b/mne/decoding/tests/test_csp.py
index d0ca998..759b7e5 100644
--- a/mne/decoding/tests/test_csp.py
+++ b/mne/decoding/tests/test_csp.py
@@ -1,17 +1,20 @@
 # Author: Alexandre Gramfort <alexandre.gramfort at telecom-paristech.fr>
 #         Romain Trachel <trachelr at gmail.com>
+#         Alexandre Barachant <alexandre.barachant at gmail.com>
+#         Jean-Remi King <jeanremi.king at gmail.com>
 #
 # License: BSD (3-clause)
 
 import os.path as op
 
-from nose.tools import assert_true, assert_raises, assert_equal
+from nose.tools import assert_true, assert_raises, assert_equal, assert_greater
+import pytest
 import numpy as np
 from numpy.testing import assert_array_almost_equal, assert_array_equal
 
 from mne import io, Epochs, read_events, pick_types
-from mne.decoding.csp import CSP, _ajd_pham
-from mne.utils import requires_sklearn, slow_test
+from mne.decoding.csp import CSP, _ajd_pham, SPoC
+from mne.utils import requires_sklearn
 
 data_dir = op.join(op.dirname(__file__), '..', '..', 'io', 'tests', 'data')
 raw_fname = op.join(data_dir, 'test_raw.fif')
@@ -23,39 +26,61 @@ event_id = dict(aud_l=1, vis_l=3)
 start, stop = 0, 8
 
 
- at slow_test
+def simulate_data(target, n_trials=100, n_channels=10, random_state=42):
+    """Simulate data according to an instantaneous mixin model.
+
+    Data are simulated in the statistical source space, where one source is
+    modulated according to a target variable, before being mixed with a
+    random mixing matrix.
+    """
+    rs = np.random.RandomState(random_state)
+
+    # generate a orthogonal mixin matrix
+    mixing_mat = np.linalg.svd(rs.randn(n_channels, n_channels))[0]
+
+    S = rs.randn(n_trials, n_channels, 50)
+    S[:, 0] *= np.atleast_2d(np.sqrt(target)).T
+    S[:, 1:] *= 0.01  # less noise
+
+    X = np.dot(mixing_mat, S).transpose((1, 0, 2))
+
+    return X, mixing_mat
+
+
+ at pytest.mark.slowtest
 def test_csp():
     """Test Common Spatial Patterns algorithm on epochs
     """
-    raw = io.read_raw_fif(raw_fname, preload=False, add_eeg_ref=False)
+    raw = io.read_raw_fif(raw_fname, preload=False)
     events = read_events(event_name)
     picks = pick_types(raw.info, meg=True, stim=False, ecg=False,
                        eog=False, exclude='bads')
     picks = picks[2:12:3]  # subselect channels -> disable proj!
     raw.add_proj([], remove_existing=True)
     epochs = Epochs(raw, events, event_id, tmin, tmax, picks=picks,
-                    baseline=(None, 0), preload=True, proj=False,
-                    add_eeg_ref=False)
+                    baseline=(None, 0), preload=True, proj=False)
     epochs_data = epochs.get_data()
     n_channels = epochs_data.shape[1]
     y = epochs.events[:, -1]
 
     # Init
-    assert_raises(ValueError, CSP, n_components='foo')
+    assert_raises(ValueError, CSP, n_components='foo', norm_trace=False)
     for reg in ['foo', -0.1, 1.1]:
-        assert_raises(ValueError, CSP, reg=reg)
+        assert_raises(ValueError, CSP, reg=reg, norm_trace=False)
     for reg in ['oas', 'ledoit_wolf', 0, 0.5, 1.]:
-        CSP(reg=reg)
+        CSP(reg=reg, norm_trace=False)
     for cov_est in ['foo', None]:
-        assert_raises(ValueError, CSP, cov_est=cov_est)
+        assert_raises(ValueError, CSP, cov_est=cov_est, norm_trace=False)
+    assert_raises(ValueError, CSP, norm_trace='foo')
     for cov_est in ['concat', 'epoch']:
-        CSP(cov_est=cov_est)
+        CSP(cov_est=cov_est, norm_trace=False)
 
     n_components = 3
-    csp = CSP(n_components=n_components)
-
     # Fit
-    csp.fit(epochs_data, epochs.events[:, -1])
+    for norm_trace in [True, False]:
+        csp = CSP(n_components=n_components, norm_trace=norm_trace)
+        csp.fit(epochs_data, epochs.events[:, -1])
+
     assert_equal(len(csp.mean_), n_components)
     assert_equal(len(csp.std_), n_components)
 
@@ -80,26 +105,16 @@ def test_csp():
     for plot in (csp.plot_patterns, csp.plot_filters):
         plot(epochs.info, components=components, res=12, show=False, cmap=cmap)
 
-    # Test covariance estimation methods (results should be roughly equal)
-    np.random.seed(0)
-    csp_epochs = CSP(cov_est="epoch")
-    csp_epochs.fit(epochs_data, y)
-    for attr in ('filters_', 'patterns_'):
-        corr = np.corrcoef(getattr(csp, attr).ravel(),
-                           getattr(csp_epochs, attr).ravel())[0, 1]
-        assert_true(corr >= 0.94)
-
     # Test with more than 2 classes
     epochs = Epochs(raw, events, tmin=tmin, tmax=tmax, picks=picks,
                     event_id=dict(aud_l=1, aud_r=2, vis_l=3, vis_r=4),
-                    baseline=(None, 0), proj=False, preload=True,
-                    add_eeg_ref=False)
+                    baseline=(None, 0), proj=False, preload=True)
     epochs_data = epochs.get_data()
     n_channels = epochs_data.shape[1]
 
     n_channels = epochs_data.shape[1]
     for cov_est in ['concat', 'epoch']:
-        csp = CSP(n_components=n_components, cov_est=cov_est)
+        csp = CSP(n_components=n_components, cov_est=cov_est, norm_trace=False)
         csp.fit(epochs_data, epochs.events[:, 2]).transform(epochs_data)
         assert_equal(len(csp._classes), 4)
         assert_array_equal(csp.filters_.shape, [n_channels, n_channels])
@@ -111,7 +126,7 @@ def test_csp():
     feature_shape = [len(epochs_data), n_components]
     X_trans = dict()
     for log in (None, True, False):
-        csp = CSP(n_components=n_components, log=log)
+        csp = CSP(n_components=n_components, log=log, norm_trace=False)
         assert_true(csp.log is log)
         Xt = csp.fit_transform(epochs_data, epochs.events[:, 2])
         assert_array_equal(Xt.shape, feature_shape)
@@ -124,35 +139,55 @@ def test_csp():
     assert_raises(ValueError, CSP, transform_into='average_power', log='foo')
 
     # Test csp space transform
-    csp = CSP(transform_into='csp_space')
+    csp = CSP(transform_into='csp_space', norm_trace=False)
     assert_true(csp.transform_into == 'csp_space')
     for log in ('foo', True, False):
-        assert_raises(ValueError, CSP, transform_into='csp_space', log=log)
+        assert_raises(ValueError, CSP, transform_into='csp_space', log=log,
+                      norm_trace=False)
     n_components = 2
-    csp = CSP(n_components=n_components, transform_into='csp_space')
+    csp = CSP(n_components=n_components, transform_into='csp_space',
+              norm_trace=False)
     Xt = csp.fit(epochs_data, epochs.events[:, 2]).transform(epochs_data)
     feature_shape = [len(epochs_data), n_components, epochs_data.shape[2]]
     assert_array_equal(Xt.shape, feature_shape)
 
+    # Check mixing matrix on simulated data
+    y = np.array([100] * 50 + [1] * 50)
+    X, A = simulate_data(y)
+
+    for cov_est in ['concat', 'epoch']:
+        # fit csp
+        csp = CSP(n_components=1, cov_est=cov_est, norm_trace=False)
+        csp.fit(X, y)
+
+        # check the first pattern match the mixing matrix
+        # the sign might change
+        corr = np.abs(np.corrcoef(csp.patterns_[0, :].T, A[:, 0])[0, 1])
+        assert_greater(np.abs(corr), 0.99)
+
+        # check output
+        out = csp.transform(X)
+        corr = np.abs(np.corrcoef(out[:, 0], y)[0, 1])
+        assert_greater(np.abs(corr), 0.95)
+
 
 @requires_sklearn
 def test_regularized_csp():
-    """Test Common Spatial Patterns algorithm using regularized covariance
-    """
-    raw = io.read_raw_fif(raw_fname, preload=False, add_eeg_ref=False)
+    """Test Common Spatial Patterns algorithm using regularized covariance."""
+    raw = io.read_raw_fif(raw_fname)
     events = read_events(event_name)
     picks = pick_types(raw.info, meg=True, stim=False, ecg=False,
                        eog=False, exclude='bads')
     picks = picks[1:13:3]
     epochs = Epochs(raw, events, event_id, tmin, tmax, picks=picks,
-                    baseline=(None, 0), preload=True, add_eeg_ref=False)
+                    baseline=(None, 0), preload=True)
     epochs_data = epochs.get_data()
     n_channels = epochs_data.shape[1]
 
     n_components = 3
     reg_cov = [None, 0.05, 'ledoit_wolf', 'oas']
     for reg in reg_cov:
-        csp = CSP(n_components=n_components, reg=reg)
+        csp = CSP(n_components=n_components, reg=reg, norm_trace=False)
         csp.fit(epochs_data, epochs.events[:, -1])
         y = epochs.events[:, -1]
         X = csp.fit_transform(epochs_data, y)
@@ -178,7 +213,7 @@ def test_csp_pipeline():
     """
     from sklearn.svm import SVC
     from sklearn.pipeline import Pipeline
-    csp = CSP(reg=1)
+    csp = CSP(reg=1, norm_trace=False)
     svc = SVC()
     pipe = Pipeline([("CSP", csp), ("SVC", svc)])
     pipe.set_params(CSP__reg=0.2)
@@ -204,3 +239,43 @@ def test_ajd():
                 [0.694689013234610, 0.775690358505945, -1.162043086446043],
                 [-0.592603135588066, -0.598996925696260, 1.009550086271192]]
     assert_array_almost_equal(V, V_matlab)
+
+
+def test_spoc():
+    X = np.random.randn(10, 10, 20)
+    y = np.random.randn(10)
+
+    spoc = SPoC(n_components=4)
+    spoc.fit(X, y)
+    Xt = spoc.transform(X)
+    assert_array_equal(Xt.shape, [10, 4])
+    spoc = SPoC(n_components=4, transform_into='csp_space')
+    spoc.fit(X, y)
+    Xt = spoc.transform(X)
+    assert_array_equal(Xt.shape, [10, 4, 20])
+    assert_array_equal(spoc.filters_.shape, [10, 10])
+    assert_array_equal(spoc.patterns_.shape, [10, 10])
+
+    # check y
+    assert_raises(ValueError, spoc.fit, X, y * 0)
+
+    # Check that doesn't take CSP-spcific input
+    assert_raises(TypeError, SPoC, cov_est='epoch')
+
+    # Check mixing matrix on simulated data
+    rs = np.random.RandomState(42)
+    y = rs.rand(100) * 50 + 1
+    X, A = simulate_data(y)
+
+    # fit spoc
+    spoc = SPoC(n_components=1)
+    spoc.fit(X, y)
+
+    # check the first patterns match the mixing matrix
+    corr = np.abs(np.corrcoef(spoc.patterns_[0, :].T, A[:, 0])[0, 1])
+    assert_greater(np.abs(corr), 0.99)
+
+    # check output
+    out = spoc.transform(X)
+    corr = np.abs(np.corrcoef(out[:, 0], y)[0, 1])
+    assert_greater(np.abs(corr), 0.85)
diff --git a/mne/decoding/tests/test_ems.py b/mne/decoding/tests/test_ems.py
index 7f5b767..7fe9837 100644
--- a/mne/decoding/tests/test_ems.py
+++ b/mne/decoding/tests/test_ems.py
@@ -8,7 +8,7 @@ from numpy.testing import assert_array_almost_equal
 from nose.tools import assert_equal, assert_raises
 
 from mne import io, Epochs, read_events, pick_types
-from mne.utils import requires_sklearn_0_15, check_version
+from mne.utils import requires_version, check_version, run_tests_if_main
 from mne.decoding import compute_ems, EMS
 
 data_dir = op.join(op.dirname(__file__), '..', '..', 'io', 'tests', 'data')
@@ -21,10 +21,10 @@ tmin, tmax = -0.2, 0.5
 event_id = dict(aud_l=1, vis_l=3)
 
 
- at requires_sklearn_0_15
+ at requires_version('sklearn', '0.15')
 def test_ems():
-    """Test event-matched spatial filters"""
-    raw = io.read_raw_fif(raw_fname, preload=False, add_eeg_ref=False)
+    """Test event-matched spatial filters."""
+    raw = io.read_raw_fif(raw_fname, preload=False)
 
     # create unequal number of events
     events = read_events(event_name)
@@ -33,9 +33,9 @@ def test_ems():
                        eog=False, exclude='bads')
     picks = picks[1:13:3]
     epochs = Epochs(raw, events, event_id, tmin, tmax, picks=picks,
-                    baseline=(None, 0), preload=True, add_eeg_ref=False)
+                    baseline=(None, 0), preload=True)
     assert_raises(ValueError, compute_ems, epochs, ['aud_l', 'vis_l'])
-    epochs = epochs.equalize_event_counts(epochs.event_id, copy=False)[0]
+    epochs.equalize_event_counts(epochs.event_id)
 
     assert_raises(KeyError, compute_ems, epochs, ['blah', 'hahah'])
     surrogates, filters, conditions = compute_ems(epochs)
@@ -44,8 +44,8 @@ def test_ems():
     events = read_events(event_name)
     event_id2 = dict(aud_l=1, aud_r=2, vis_l=3)
     epochs = Epochs(raw, events, event_id2, tmin, tmax, picks=picks,
-                    baseline=(None, 0), preload=True, add_eeg_ref=False)
-    epochs = epochs.equalize_event_counts(epochs.event_id, copy=False)[0]
+                    baseline=(None, 0), preload=True)
+    epochs.equalize_event_counts(epochs.event_id)
 
     n_expected = sum([len(epochs[k]) for k in ['aud_l', 'vis_l']])
 
@@ -86,3 +86,6 @@ def test_ems():
     assert_equal(ems.__repr__(), '<EMS: fitted with 4 filters on 2 classes.>')
     assert_array_almost_equal(filters, np.mean(coefs, axis=0))
     assert_array_almost_equal(surrogates, np.vstack(Xt))
+
+
+run_tests_if_main()
diff --git a/mne/decoding/tests/test_receptive_field.py b/mne/decoding/tests/test_receptive_field.py
new file mode 100644
index 0000000..e070a82
--- /dev/null
+++ b/mne/decoding/tests/test_receptive_field.py
@@ -0,0 +1,528 @@
+# Authors: Chris Holdgraf <choldgraf at gmail.com>
+#
+# License: BSD (3-clause)
+import warnings
+import os.path as op
+
+from nose.tools import assert_raises, assert_true, assert_equal
+import numpy as np
+
+from numpy.testing import assert_array_equal, assert_allclose
+
+from mne import io, pick_types
+from mne.utils import requires_version, run_tests_if_main
+from mne.decoding import ReceptiveField, TimeDelayingRidge
+from mne.decoding.receptive_field import (_delay_time_series, _SCORERS,
+                                          _times_to_delays, _delays_to_slice)
+from mne.decoding.time_delaying_ridge import (_compute_reg_neighbors,
+                                              _compute_corrs)
+
+
+data_dir = op.join(op.dirname(__file__), '..', '..', 'io', 'tests', 'data')
+raw_fname = op.join(data_dir, 'test_raw.fif')
+event_name = op.join(data_dir, 'test-eve.fif')
+
+rng = np.random.RandomState(1337)
+
+tmin, tmax = -0.1, 0.5
+event_id = dict(aud_l=1, vis_l=3)
+
+warnings.simplefilter('always')
+
+# Loading raw data
+raw = io.read_raw_fif(raw_fname, preload=True)
+picks = pick_types(raw.info, meg=True, stim=False, ecg=False,
+                   eog=False, exclude='bads')
+picks = picks[:2]
+
+
+def test_compute_reg_neighbors():
+    """Test fast calculation of laplacian regularizer."""
+    for reg_type in (
+            ('ridge', 'ridge'),
+            ('ridge', 'laplacian'),
+            ('laplacian', 'ridge'),
+            ('laplacian', 'laplacian')):
+        for n_ch_x, n_delays in (
+                (1, 1), (1, 2), (2, 1), (1, 3), (3, 1), (1, 4), (4, 1),
+                (2, 2), (2, 3), (3, 2), (3, 3),
+                (2, 4), (4, 2), (3, 4), (4, 3), (4, 4),
+                (5, 4), (4, 5), (5, 5),
+                (20, 9), (9, 20)):
+            for normed in (True, False):
+                reg_direct = _compute_reg_neighbors(
+                    n_ch_x, n_delays, reg_type, 'direct', normed=normed)
+                reg_csgraph = _compute_reg_neighbors(
+                    n_ch_x, n_delays, reg_type, 'csgraph', normed=normed)
+                assert_allclose(
+                    reg_direct, reg_csgraph, atol=1e-7,
+                    err_msg='%s: %s' % (reg_type, (n_ch_x, n_delays)))
+
+
+ at requires_version('sklearn', '0.17')
+def test_rank_deficiency():
+    """Test signals that are rank deficient."""
+    # See GH#4253
+    from sklearn.linear_model import Ridge
+    N = 256
+    fs = 1.
+    tmin, tmax = -50, 100
+    reg = 0.1
+    rng = np.random.RandomState(0)
+    eeg = rng.randn(N, 1)
+    eeg *= 100
+    eeg = np.fft.rfft(eeg, axis=0)
+    eeg[N // 4:] = 0  # rank-deficient lowpass
+    eeg = np.fft.irfft(eeg, axis=0)
+    win = np.hanning(N // 8)
+    win /= win.mean()
+    y = np.apply_along_axis(np.convolve, 0, eeg, win, mode='same')
+    y += rng.randn(*y.shape) * 100
+
+    for est in (Ridge(reg), reg):
+        rf = ReceptiveField(tmin, tmax, fs, estimator=est, patterns=True)
+        rf.fit(eeg, y)
+        pred = rf.predict(eeg)
+        assert_equal(y.shape, pred.shape)
+        corr = np.corrcoef(y.ravel(), pred.ravel())[0, 1]
+        assert_true(corr > 0.995, msg=corr)
+
+
+def test_time_delay():
+    """Test that time-delaying w/ times and samples works properly."""
+    # Explicit delays + sfreq
+    X = np.random.RandomState(0).randn(1000, 2)
+    assert (X == 0).sum() == 0  # need this for later
+    test_tlims = [
+        ((1, 2), 1),
+        ((1, 1), 1),
+        ((0, 2), 1),
+        ((0, 1), 1),
+        ((0, 0), 1),
+        ((-1, 2), 1),
+        ((-1, 1), 1),
+        ((-1, 0), 1),
+        ((-1, -1), 1),
+        ((-2, 2), 1),
+        ((-2, 1), 1),
+        ((-2, 0), 1),
+        ((-2, -1), 1),
+        ((-2, -1), 1),
+        ((0, .2), 10),
+        ((-.1, .1), 10)]
+    for (tmin, tmax), isfreq in test_tlims:
+        # sfreq must be int/float
+        assert_raises(ValueError, _delay_time_series, X, tmin, tmax,
+                      sfreq=[1])
+        # Delays must be int/float
+        assert_raises(ValueError, _delay_time_series, X,
+                      np.complex(tmin), tmax, 1)
+        # Make sure swapaxes works
+        start, stop = int(round(tmin * isfreq)), int(round(tmax * isfreq)) + 1
+        n_delays = stop - start
+        X_delayed = _delay_time_series(X, tmin, tmax, isfreq)
+        assert_equal(X_delayed.shape, (1000, 2, n_delays))
+        # Make sure delay slice is correct
+        delays = _times_to_delays(tmin, tmax, isfreq)
+        assert_array_equal(delays, np.arange(start, stop))
+        keep = _delays_to_slice(delays)
+        expected = np.where((X_delayed != 0).all(-1).all(-1))[0]
+        got = np.arange(len(X_delayed))[keep]
+        assert_array_equal(got, expected)
+        assert_true(X_delayed[keep].shape[-1] > 0)
+        assert_true((X_delayed[keep] == 0).sum() == 0)
+
+        del_zero = int(round(-tmin * isfreq))
+        for ii in range(-2, 3):
+            idx = del_zero + ii
+            err_msg = '[%s,%s] (%s): %s %s' % (tmin, tmax, isfreq, ii, idx)
+            if 0 <= idx < X_delayed.shape[-1]:
+                if ii == 0:
+                    assert_array_equal(X_delayed[:, :, idx], X,
+                                       err_msg=err_msg)
+                elif ii < 0:  # negative delay
+                    assert_array_equal(X_delayed[:ii, :, idx], X[-ii:, :],
+                                       err_msg=err_msg)
+                    assert_array_equal(X_delayed[ii:, :, idx], 0.)
+                else:
+                    assert_array_equal(X_delayed[ii:, :, idx], X[:-ii, :],
+                                       err_msg=err_msg)
+                    assert_array_equal(X_delayed[:ii, :, idx], 0.)
+
+
+ at requires_version('sklearn', '0.17')
+def test_receptive_field():
+    """Test model prep and fitting."""
+    from sklearn.linear_model import Ridge
+    # Make sure estimator pulling works
+    mod = Ridge()
+
+    # Test the receptive field model
+    # Define parameters for the model and simulate inputs + weights
+    tmin, tmax = -10., 0
+    n_feats = 3
+    X = rng.randn(10000, n_feats)
+    w = rng.randn(int((tmax - tmin) + 1) * n_feats)
+
+    # Delay inputs and cut off first 4 values since they'll be cut in the fit
+    X_del = np.concatenate(
+        _delay_time_series(X, tmin, tmax, 1.).transpose(2, 0, 1), axis=1)
+    y = np.dot(X_del, w)
+
+    # Fit the model and test values
+    feature_names = ['feature_%i' % ii for ii in [0, 1, 2]]
+    rf = ReceptiveField(tmin, tmax, 1, feature_names, estimator=mod,
+                        patterns=True)
+    rf.fit(X, y)
+    assert_array_equal(rf.delays_, np.arange(tmin, tmax + 1))
+
+    y_pred = rf.predict(X)
+    assert_allclose(y[rf.valid_samples_], y_pred[rf.valid_samples_], atol=1e-2)
+    scores = rf.score(X, y)
+    assert_true(scores > .99)
+    assert_allclose(rf.coef_.T.ravel(), w, atol=1e-2)
+    # Make sure different input shapes work
+    rf.fit(X[:, np.newaxis:, ], y[:, np.newaxis])
+    rf.fit(X, y[:, np.newaxis])
+    assert_raises(ValueError, rf.fit, X[..., np.newaxis], y)
+    assert_raises(ValueError, rf.fit, X[:, 0], y)
+    assert_raises(ValueError, rf.fit, X[..., np.newaxis],
+                  np.tile(y[..., np.newaxis], [2, 1, 1]))
+    # stim features must match length of input data
+    assert_raises(ValueError, rf.fit, X[:, :1], y)
+    # auto-naming features
+    rf = ReceptiveField(tmin, tmax, 1, estimator=mod)
+    rf.fit(X, y)
+    assert_equal(rf.feature_names, ['feature_%s' % ii for ii in [0, 1, 2]])
+    # X/y same n timepoints
+    assert_raises(ValueError, rf.fit, X, y[:-2])
+    # Float becomes ridge
+    rf = ReceptiveField(tmin, tmax, 1, ['one', 'two', 'three'],
+                        estimator=0, patterns=True)
+    str(rf)  # repr works before fit
+    rf.fit(X, y)
+    assert_true(isinstance(rf.estimator_, TimeDelayingRidge))
+    str(rf)  # repr works after fit
+    rf = ReceptiveField(tmin, tmax, 1, ['one'], estimator=0, patterns=True)
+    rf.fit(X[:, [0]], y)
+    str(rf)  # repr with one feature
+    # Should only accept estimators or floats
+    rf = ReceptiveField(tmin, tmax, 1, estimator='foo', patterns=True)
+    assert_raises(ValueError, rf.fit, X, y)
+    rf = ReceptiveField(tmin, tmax, 1, estimator=np.array([1, 2, 3]))
+    assert_raises(ValueError, rf.fit, X, y)
+    # tmin must be <= tmax
+    rf = ReceptiveField(5, 4, 1, patterns=True)
+    assert_raises(ValueError, rf.fit, X, y)
+    # scorers
+    for key, val in _SCORERS.items():
+        rf = ReceptiveField(tmin, tmax, 1, ['one'],
+                            estimator=0, scoring=key, patterns=True)
+        rf.fit(X[:, [0]], y)
+        y_pred = rf.predict(X[:, [0]]).T.ravel()[:, np.newaxis]
+        assert_allclose(val(y[:, np.newaxis], y_pred,
+                            multioutput='raw_values'),
+                        rf.score(X[:, [0]], y), rtol=1e-2)
+    # Need 2D input
+    assert_raises(ValueError, _SCORERS['corrcoef'], y.ravel(), y_pred,
+                  multioutput='raw_values')
+    # Need correct scorers
+    rf = ReceptiveField(tmin, tmax, 1., scoring='foo')
+    assert_raises(ValueError, rf.fit, X, y)
+
+
+def test_time_delaying_fast_calc():
+    """Test time delaying and fast calculations."""
+    X = np.array([[1, 2, 3], [5, 7, 11]]).T
+    # all negative
+    smin, smax = 1, 2
+    X_del = _delay_time_series(X, smin, smax, 1.)
+    # (n_times, n_features, n_delays) -> (n_times, n_features * n_delays)
+    X_del.shape = (X.shape[0], -1)
+    expected = np.array([[0, 1, 2], [0, 0, 1], [0, 5, 7], [0, 0, 5]]).T
+    assert_allclose(X_del, expected)
+    Xt_X = np.dot(X_del.T, X_del)
+    expected = [[5, 2, 19, 10], [2, 1, 7, 5], [19, 7, 74, 35], [10, 5, 35, 25]]
+    assert_allclose(Xt_X, expected)
+    x_xt = _compute_corrs(X, np.zeros((X.shape[0], 1)), smin, smax + 1)[0]
+    assert_allclose(x_xt, expected)
+    # all positive
+    smin, smax = -2, -1
+    X_del = _delay_time_series(X, smin, smax, 1.)
+    X_del.shape = (X.shape[0], -1)
+    expected = np.array([[3, 0, 0], [2, 3, 0], [11, 0, 0], [7, 11, 0]]).T
+    assert_allclose(X_del, expected)
+    Xt_X = np.dot(X_del.T, X_del)
+    expected = [[9, 6, 33, 21], [6, 13, 22, 47],
+                [33, 22, 121, 77], [21, 47, 77, 170]]
+    assert_allclose(Xt_X, expected)
+    x_xt = _compute_corrs(X, np.zeros((X.shape[0], 1)), smin, smax + 1)[0]
+    assert_allclose(x_xt, expected)
+    # both sides
+    smin, smax = -1, 1
+    X_del = _delay_time_series(X, smin, smax, 1.)
+    X_del.shape = (X.shape[0], -1)
+    expected = np.array([[2, 3, 0], [1, 2, 3], [0, 1, 2],
+                         [7, 11, 0], [5, 7, 11], [0, 5, 7]]).T
+    assert_allclose(X_del, expected)
+    Xt_X = np.dot(X_del.T, X_del)
+    expected = [[13, 8, 3, 47, 31, 15],
+                [8, 14, 8, 29, 52, 31],
+                [3, 8, 5, 11, 29, 19],
+                [47, 29, 11, 170, 112, 55],
+                [31, 52, 29, 112, 195, 112],
+                [15, 31, 19, 55, 112, 74]]
+    assert_allclose(Xt_X, expected)
+    x_xt = _compute_corrs(X, np.zeros((X.shape[0], 1)), smin, smax + 1)[0]
+    assert_allclose(x_xt, expected)
+
+    # slightly harder to get the non-Toeplitz correction correct
+    X = np.array([[1, 2, 3, 5]]).T
+    smin, smax = 0, 3
+    X_del = _delay_time_series(X, smin, smax, 1.)
+    X_del.shape = (X.shape[0], -1)
+    expected = np.array([[1, 2, 3, 5], [0, 1, 2, 3],
+                         [0, 0, 1, 2], [0, 0, 0, 1]]).T
+    assert_allclose(X_del, expected)
+    Xt_X = np.dot(X_del.T, X_del)
+    expected = [[39, 23, 13, 5], [23, 14, 8, 3], [13, 8, 5, 2], [5, 3, 2, 1]]
+    assert_allclose(Xt_X, expected)
+    x_xt = _compute_corrs(X, np.zeros((X.shape[0], 1)), smin, smax + 1)[0]
+    assert_allclose(x_xt, expected)
+
+    # even worse
+    X = np.array([[1, 2, 3], [5, 7, 11]]).T
+    smin, smax = 0, 2
+    X_del = _delay_time_series(X, smin, smax, 1.)
+    X_del.shape = (X.shape[0], -1)
+    expected = np.array([[1, 2, 3], [0, 1, 2], [0, 0, 1],
+                         [5, 7, 11], [0, 5, 7], [0, 0, 5]]).T
+    assert_allclose(X_del, expected)
+    Xt_X = np.dot(X_del.T, X_del)
+    expected = np.array([[14, 8, 3, 52, 31, 15],
+                         [8, 5, 2, 29, 19, 10],
+                         [3, 2, 1, 11, 7, 5],
+                         [52, 29, 11, 195, 112, 55],
+                         [31, 19, 7, 112, 74, 35],
+                         [15, 10, 5, 55, 35, 25]])
+    assert_allclose(Xt_X, expected)
+    x_xt = _compute_corrs(X, np.zeros((X.shape[0], 1)), smin, smax + 1)[0]
+    assert_allclose(x_xt, expected)
+
+    # And a bunch of random ones for good measure
+    rng = np.random.RandomState(0)
+    X = rng.randn(25, 3)
+    y = np.empty((25, 2))
+    vals = (0, -1, 1, -2, 2, -11, 11)
+    for smax in vals:
+        for smin in vals:
+            if smin > smax:
+                continue
+            for ii in range(X.shape[1]):
+                kernel = rng.randn(smax - smin + 1)
+                kernel -= np.mean(kernel)
+                y[:, ii % y.shape[-1]] = np.convolve(X[:, ii], kernel, 'same')
+            x_xt, x_yt, n_ch_x = _compute_corrs(X, y, smin, smax + 1)
+            X_del = _delay_time_series(X, smin, smax, 1., fill_mean=False)
+            x_yt_true = np.einsum('tfd,to->ofd', X_del, y)
+            x_yt_true = np.reshape(x_yt_true, (x_yt_true.shape[0], -1)).T
+            assert_allclose(x_yt, x_yt_true, atol=1e-7, err_msg=(smin, smax))
+            X_del.shape = (X.shape[0], -1)
+            x_xt_true = np.dot(X_del.T, X_del).T
+            assert_allclose(x_xt, x_xt_true, atol=1e-7, err_msg=(smin, smax))
+
+
+ at requires_version('sklearn', '0.17')
+def test_receptive_field_1d():
+    """Test that the fast solving works like Ridge."""
+    from sklearn.linear_model import Ridge
+    rng = np.random.RandomState(0)
+    x = rng.randn(500, 1)
+    for delay in range(-2, 3):
+        y = np.zeros(500)
+        slims = [(-2, 4)]
+        if delay == 0:
+            y[:] = x[:, 0]
+        elif delay < 0:
+            y[:delay] = x[-delay:, 0]
+            slims += [(-4, -1)]
+        else:
+            y[delay:] = x[:-delay, 0]
+            slims += [(1, 2)]
+        for ndim in (1, 2):
+            y.shape = (y.shape[0],) + (1,) * (ndim - 1)
+            for slim in slims:
+                lap = TimeDelayingRidge(slim[0], slim[1], 1., 0.1, 'laplacian',
+                                        fit_intercept=False)
+                for estimator in (Ridge(alpha=0.), Ridge(alpha=0.1), 0., 0.1,
+                                  lap):
+                    for offset in (-100, 0, 100):
+                        model = ReceptiveField(slim[0], slim[1], 1.,
+                                               estimator=estimator)
+                        use_x = x + offset
+                        model.fit(use_x, y)
+                        if estimator is lap:
+                            continue  # these checks are too stringent
+                        assert_allclose(model.estimator_.intercept_, -offset,
+                                        atol=1e-1)
+                        assert_array_equal(model.delays_,
+                                           np.arange(slim[0], slim[1] + 1))
+                        expected = (model.delays_ == delay).astype(float)
+                        expected = expected[np.newaxis]  # features
+                        if y.ndim == 2:
+                            expected = expected[np.newaxis]  # outputs
+                        assert_equal(model.coef_.ndim, ndim + 1)
+                        assert_allclose(model.coef_, expected, atol=1e-3)
+                        start = model.valid_samples_.start or 0
+                        stop = len(use_x) - (model.valid_samples_.stop or 0)
+                        assert stop - start >= 495
+                        assert_allclose(
+                            model.predict(use_x)[model.valid_samples_],
+                            y[model.valid_samples_], atol=1e-2)
+                        score = np.mean(model.score(use_x, y))
+                        assert_true(score > 0.9999, msg=score)
+
+
+ at requires_version('sklearn', '0.17')
+def test_receptive_field_nd():
+    """Test multidimensional support."""
+    from sklearn.linear_model import Ridge
+    # multidimensional
+    x = rng.randn(1000, 3)
+    y = np.zeros((1000, 2))
+    slim = [0, 5]
+    # This is a weird assignment, but it's just a way to distribute some
+    # unique values at various delays, and "expected" explains how they
+    # should appear in the resulting RF
+    for ii in range(1, 5):
+        y[ii:, ii % 2] += (-1) ** ii * ii * x[:-ii, ii % 3]
+    y -= np.mean(y, axis=0)
+    x -= np.mean(x, axis=0)
+    x_off = x + 1e3
+    expected = [
+        [[0, 0, 0, 0, 0, 0],
+         [0, 0, 0, 0, 4, 0],
+         [0, 0, 2, 0, 0, 0]],
+        [[0, 0, 0, -3, 0, 0],
+         [0, -1, 0, 0, 0, 0],
+         [0, 0, 0, 0, 0, 0]],
+    ]
+    tdr = TimeDelayingRidge(slim[0], slim[1], 1., 0.1, 'laplacian')
+    for estimator in (Ridge(alpha=0.), 0., 0.01, tdr):
+        model = ReceptiveField(slim[0], slim[1], 1.,
+                               estimator=estimator)
+        model.fit(x, y)
+        assert_array_equal(model.delays_,
+                           np.arange(slim[0], slim[1] + 1))
+        assert_allclose(model.coef_, expected, atol=1e-1)
+    tdr = TimeDelayingRidge(slim[0], slim[1], 1., 0.01, reg_type='foo')
+    model = ReceptiveField(slim[0], slim[1], 1., estimator=tdr)
+    assert_raises(ValueError, model.fit, x, y)
+    tdr = TimeDelayingRidge(slim[0], slim[1], 1., 0.01, reg_type=['laplacian'])
+    model = ReceptiveField(slim[0], slim[1], 1., estimator=tdr)
+    assert_raises(ValueError, model.fit, x, y)
+
+    # Now check the intercept_
+    tdr = TimeDelayingRidge(slim[0], slim[1], 1., 0.)
+    tdr_no = TimeDelayingRidge(slim[0], slim[1], 1., 0., fit_intercept=False)
+    for estimator in (Ridge(alpha=0.), tdr,
+                      Ridge(alpha=0., fit_intercept=False), tdr_no):
+        # first with no intercept in the data
+        model = ReceptiveField(slim[0], slim[1], 1., estimator=estimator)
+        model.fit(x, y)
+        assert_allclose(model.estimator_.intercept_, 0., atol=1e-7,
+                        err_msg=repr(estimator))
+        assert_allclose(model.coef_, expected, atol=1e-3,
+                        err_msg=repr(estimator))
+        y_pred = model.predict(x)
+        assert_allclose(y_pred[model.valid_samples_],
+                        y[model.valid_samples_],
+                        atol=1e-2, err_msg=repr(estimator))
+        score = np.mean(model.score(x, y))
+        assert score > 0.9999
+
+        # now with an intercept in the data
+        model.fit(x_off, y)
+        if estimator.fit_intercept:
+            val = [-6000, 4000]
+            itol = 0.5
+            ctol = 5e-4
+        else:
+            val = itol = 0.
+            ctol = 2.
+        assert_allclose(model.estimator_.intercept_, val, atol=itol,
+                        err_msg=repr(estimator))
+        assert_allclose(model.coef_, expected, atol=ctol, rtol=ctol,
+                        err_msg=repr(estimator))
+        if estimator.fit_intercept:
+            ptol = 1e-2
+            stol = 0.999999
+        else:
+            ptol = 10
+            stol = 0.6
+        y_pred = model.predict(x_off)[model.valid_samples_]
+        assert_allclose(y_pred, y[model.valid_samples_],
+                        atol=ptol, err_msg=repr(estimator))
+        score = np.mean(model.score(x_off, y))
+        assert score > stol, estimator
+        model = ReceptiveField(slim[0], slim[1], 1., fit_intercept=False)
+        model.fit(x_off, y)
+        assert_allclose(model.estimator_.intercept_, 0., atol=1e-7)
+        score = np.mean(model.score(x_off, y))
+        assert_true(score > 0.6, msg=score)
+
+
+ at requires_version('sklearn', '0.17')
+def test_inverse_coef():
+    """Test inverse coefficients computation."""
+    from sklearn.linear_model import Ridge
+
+    rng = np.random.RandomState(0)
+    tmin, tmax = 0., 10.
+    n_feats, n_targets, n_samples = 64, 2, 10000
+    n_delays = int((tmax - tmin) + 1)
+
+    def make_data(n_feats, n_targets, n_samples, tmin, tmax):
+        X = rng.randn(n_samples, n_feats)
+        w = rng.randn(int((tmax - tmin) + 1) * n_feats, n_targets)
+        # Delay inputs
+        X_del = np.concatenate(
+            _delay_time_series(X, tmin, tmax, 1.).transpose(2, 0, 1), axis=1)
+        y = np.dot(X_del, w)
+        return X, y
+
+    # Check coefficient dims, for all estimator types
+    X, y = make_data(n_feats, n_targets, n_samples, tmin, tmax)
+    tdr = TimeDelayingRidge(tmin, tmax, 1., 0.1, 'laplacian')
+    for estimator in (0., 0.01, Ridge(alpha=0.), tdr):
+        rf = ReceptiveField(tmin, tmax, 1., estimator=estimator,
+                            patterns=True)
+        rf.fit(X, y)
+        inv_rf = ReceptiveField(tmin, tmax, 1., estimator=estimator,
+                                patterns=True)
+        inv_rf.fit(y, X)
+
+        assert_array_equal(rf.coef_.shape, rf.patterns_.shape,
+                           (n_targets, n_feats, n_delays))
+        assert_array_equal(inv_rf.coef_.shape, inv_rf.patterns_.shape,
+                           (n_feats, n_targets, n_delays))
+
+        # we should have np.dot(patterns.T,coef) ~ np.eye(n)
+        c0 = rf.coef_.reshape(n_targets, n_feats * n_delays)
+        c1 = rf.patterns_.reshape(n_targets, n_feats * n_delays)
+        assert_allclose(np.dot(c0, c1.T), np.eye(c0.shape[0]), atol=0.1)
+
+    # Check that warnings are issued when no regularization is applied
+    n_feats, n_targets, n_samples = 5, 60, 50
+    X, y = make_data(n_feats, n_targets, n_samples, tmin, tmax)
+    for estimator in (0., Ridge(alpha=0.)):
+        rf = ReceptiveField(tmin, tmax, 1., estimator=estimator, patterns=True)
+        with warnings.catch_warnings(record=True) as w:
+            rf.fit(y, X)
+            assert_equal(len(w), 1)
+            assert_true(any(x in str(w[0].message).lower()
+                            for x in ('singular', 'scipy.linalg.solve')),
+                        msg=str(w[0].message))
+
+
+run_tests_if_main()
diff --git a/mne/decoding/tests/test_search_light.py b/mne/decoding/tests/test_search_light.py
index 4905c7f..8c2fc90 100644
--- a/mne/decoding/tests/test_search_light.py
+++ b/mne/decoding/tests/test_search_light.py
@@ -6,9 +6,9 @@
 import numpy as np
 from numpy.testing import assert_array_equal
 from nose.tools import assert_raises, assert_true, assert_equal
-from ...utils import requires_sklearn_0_15
-from ..search_light import _SearchLight, _GeneralizationLight
-from .. import Vectorizer
+from mne.utils import requires_version
+from mne.decoding.search_light import SlidingEstimator, GeneralizingEstimator
+from mne.decoding.transformer import Vectorizer
 
 
 def make_data():
@@ -22,25 +22,27 @@ def make_data():
     return X, y
 
 
- at requires_sklearn_0_15
-def test_SearchLight():
-    """Test _SearchLight"""
+ at requires_version('sklearn', '0.17')
+def test_search_light():
+    """Test SlidingEstimator"""
     from sklearn.linear_model import Ridge, LogisticRegression
     from sklearn.pipeline import make_pipeline
-    from sklearn.metrics import roc_auc_score
+    from sklearn.metrics import roc_auc_score, make_scorer
+    from sklearn.ensemble import BaggingClassifier
 
     X, y = make_data()
     n_epochs, _, n_time = X.shape
     # init
-    assert_raises(ValueError, _SearchLight, 'foo')
-    sl = _SearchLight(Ridge())
-    sl = _SearchLight(LogisticRegression())
+    assert_raises(ValueError, SlidingEstimator, 'foo')
+    sl = SlidingEstimator(Ridge())
+    sl = SlidingEstimator(LogisticRegression())
     # fit
-    assert_equal(sl.__repr__()[:14], '<_SearchLight(')
+    assert_equal(sl.__repr__()[:18], '<SlidingEstimator(')
     sl.fit(X, y)
     assert_equal(sl.__repr__()[-28:], ', fitted with 10 estimators>')
     assert_raises(ValueError, sl.fit, X[1:], y)
     assert_raises(ValueError, sl.fit, X[:, :, 0], y)
+    sl.fit(X, y, sample_weight=np.ones_like(y))
 
     # transforms
     assert_raises(ValueError, sl.predict, X[:, :, :2])
@@ -57,33 +59,58 @@ def test_SearchLight():
     assert_true(np.sum(np.abs(score)) != 0)
     assert_true(score.dtype == float)
 
-    # change score method
-    sl1 = _SearchLight(LogisticRegression(), scoring=roc_auc_score)
-    sl1.fit(X, y)
-    score1 = sl1.score(X, y)
-    assert_array_equal(score1.shape, [n_time])
-    assert_true(score1.dtype == float)
-
-    X_2d = X.reshape(X.shape[0], X.shape[1] * X.shape[2])
-    lg_score = LogisticRegression().fit(X_2d, y).predict_proba(X_2d)[:, 1]
-    assert_equal(score1[0], roc_auc_score(y, lg_score))
-
-    sl2 = _SearchLight(LogisticRegression(), scoring='roc_auc')
-    sl2.fit(X, y)
-    assert_array_equal(score1, sl2.score(X, y))
+    sl = SlidingEstimator(LogisticRegression())
+    assert_equal(sl.scoring, None)
 
-    sl = _SearchLight(LogisticRegression(), scoring='foo')
+    # Scoring method
+    for scoring in ['foo', 999]:
+        sl = SlidingEstimator(LogisticRegression(), scoring=scoring)
+        sl.fit(X, y)
+        assert_raises((ValueError, TypeError), sl.score, X, y)
+
+    # Check sklearn's roc_auc fix: scikit-learn/scikit-learn#6874
+    # -- 3 class problem
+    sl = SlidingEstimator(LogisticRegression(random_state=0),
+                          scoring='roc_auc')
+    y = np.arange(len(X)) % 3
     sl.fit(X, y)
     assert_raises(ValueError, sl.score, X, y)
+    # -- 2 class problem not in [0, 1]
+    y = np.arange(len(X)) % 2 + 1
+    sl.fit(X, y)
+    score = sl.score(X, y)
+    assert_array_equal(score, [roc_auc_score(y - 1, _y_pred - 1)
+                               for _y_pred in sl.decision_function(X).T])
+    y = np.arange(len(X)) % 2
 
-    sl = _SearchLight(LogisticRegression())
-    assert_equal(sl.scoring, None)
+    # Cannot pass a metric as a scoring parameter
+    sl1 = SlidingEstimator(LogisticRegression(), scoring=roc_auc_score)
+    sl1.fit(X, y)
+    assert_raises(ValueError, sl1.score, X, y)
+
+    # Now use string as scoring
+    sl1 = SlidingEstimator(LogisticRegression(), scoring='roc_auc')
+    sl1.fit(X, y)
+    rng = np.random.RandomState(0)
+    X = rng.randn(*X.shape)  # randomize X to avoid AUCs in [0, 1]
+    score_sl = sl1.score(X, y)
+    assert_array_equal(score_sl.shape, [n_time])
+    assert_true(score_sl.dtype == float)
+
+    # Check that scoring was applied adequately
+    scoring = make_scorer(roc_auc_score, needs_threshold=True)
+    score_manual = [scoring(est, x, y) for est, x in zip(
+                    sl1.estimators_, X.transpose(2, 0, 1))]
+    assert_array_equal(score_manual, score_sl)
 
     # n_jobs
-    sl = _SearchLight(LogisticRegression(), n_jobs=2)
-    sl.fit(X, y)
+    sl = SlidingEstimator(LogisticRegression(random_state=0), n_jobs=1,
+                          scoring='roc_auc')
+    score_1job = sl.fit(X, y).score(X, y)
+    sl.n_jobs = 2
+    score_njobs = sl.fit(X, y).score(X, y)
+    assert_array_equal(score_1job, score_njobs)
     sl.predict(X)
-    sl.score(X, y)
 
     # n_jobs > n_estimators
     sl.fit(X[..., [0]], y)
@@ -96,7 +123,7 @@ def test_SearchLight():
         def transform(self, X):
             return super(_LogRegTransformer, self).predict_proba(X)[..., 1]
 
-    pipe = make_pipeline(_SearchLight(_LogRegTransformer()),
+    pipe = make_pipeline(SlidingEstimator(_LogRegTransformer()),
                          LogisticRegression())
     pipe.fit(X, y)
     pipe.predict(X)
@@ -106,25 +133,36 @@ def test_SearchLight():
     y = np.arange(10) % 2
     y_preds = list()
     for n_jobs in [1, 2]:
-        pipe = _SearchLight(make_pipeline(Vectorizer(), LogisticRegression()),
-                            n_jobs=n_jobs)
+        pipe = SlidingEstimator(
+            make_pipeline(Vectorizer(), LogisticRegression()), n_jobs=n_jobs)
         y_preds.append(pipe.fit(X, y).predict(X))
         features_shape = pipe.estimators_[0].steps[0][1].features_shape_
         assert_array_equal(features_shape, [3, 4])
     assert_array_equal(y_preds[0], y_preds[1])
 
+    # Bagging classifiers
+    X = np.random.rand(10, 3, 4)
+    for n_jobs in (1, 2):
+        pipe = SlidingEstimator(BaggingClassifier(None, 2), n_jobs=n_jobs)
+        pipe.fit(X, y)
+        pipe.score(X, y)
+        assert_true(isinstance(pipe.estimators_[0], BaggingClassifier))
+
 
- at requires_sklearn_0_15
-def test_GeneralizationLight():
-    """Test _GeneralizationLight"""
+ at requires_version('sklearn', '0.17')
+def test_generalization_light():
+    """Test GeneralizingEstimator"""
     from sklearn.pipeline import make_pipeline
     from sklearn.linear_model import LogisticRegression
+    from sklearn.metrics import roc_auc_score
+
     X, y = make_data()
     n_epochs, _, n_time = X.shape
     # fit
-    gl = _GeneralizationLight(LogisticRegression())
-    assert_equal(gl.__repr__()[:22], '<_GeneralizationLight(')
+    gl = GeneralizingEstimator(LogisticRegression())
+    assert_equal(repr(gl)[:23], '<GeneralizingEstimator(')
     gl.fit(X, y)
+    gl.fit(X, y, sample_weight=np.ones_like(y))
 
     assert_equal(gl.__repr__()[-28:], ', fitted with 10 estimators>')
     # transforms
@@ -145,8 +183,33 @@ def test_GeneralizationLight():
     assert_true(np.sum(np.abs(score)) != 0)
     assert_true(score.dtype == float)
 
+    gl = GeneralizingEstimator(LogisticRegression(), scoring='roc_auc')
+    gl.fit(X, y)
+    score = gl.score(X, y)
+    auc = roc_auc_score(y, gl.estimators_[0].predict_proba(X[..., 0])[..., 1])
+    assert_equal(score[0, 0], auc)
+
+    for scoring in ['foo', 999]:
+        gl = GeneralizingEstimator(LogisticRegression(), scoring=scoring)
+        gl.fit(X, y)
+        assert_raises((ValueError, TypeError), gl.score, X, y)
+
+    # Check sklearn's roc_auc fix: scikit-learn/scikit-learn#6874
+    # -- 3 class problem
+    gl = GeneralizingEstimator(LogisticRegression(), scoring='roc_auc')
+    y = np.arange(len(X)) % 3
+    gl.fit(X, y)
+    assert_raises(ValueError, gl.score, X, y)
+    # -- 2 class problem not in [0, 1]
+    y = np.arange(len(X)) % 2 + 1
+    gl.fit(X, y)
+    score = gl.score(X, y)
+    manual_score = [[roc_auc_score(y - 1, _y_pred) for _y_pred in _y_preds]
+                    for _y_preds in gl.decision_function(X).transpose(1, 2, 0)]
+    assert_array_equal(score, manual_score)
+
     # n_jobs
-    gl = _GeneralizationLight(LogisticRegression(), n_jobs=2)
+    gl = GeneralizingEstimator(LogisticRegression(), n_jobs=2)
     gl.fit(X, y)
     y_pred = gl.predict(X)
     assert_array_equal(y_pred.shape, [n_epochs, n_time, n_time])
@@ -162,7 +225,7 @@ def test_GeneralizationLight():
     y = np.arange(10) % 2
     y_preds = list()
     for n_jobs in [1, 2]:
-        pipe = _GeneralizationLight(
+        pipe = GeneralizingEstimator(
             make_pipeline(Vectorizer(), LogisticRegression()), n_jobs=n_jobs)
         y_preds.append(pipe.fit(X, y).predict(X))
         features_shape = pipe.estimators_[0].steps[0][1].features_shape_
diff --git a/mne/decoding/tests/test_time_frequency.py b/mne/decoding/tests/test_time_frequency.py
index 1c19b53..037c97d 100644
--- a/mne/decoding/tests/test_time_frequency.py
+++ b/mne/decoding/tests/test_time_frequency.py
@@ -15,10 +15,10 @@ def test_timefrequency():
     from sklearn.base import clone
     # Init
     n_freqs = 3
-    frequencies = np.linspace(20, 30, n_freqs)
-    tf = TimeFrequency(frequencies, sfreq=100)
+    freqs = np.linspace(20, 30, n_freqs)
+    tf = TimeFrequency(freqs, sfreq=100)
     for output in ['avg_power', 'foo', None]:
-        assert_raises(ValueError, TimeFrequency, frequencies, output=output)
+        assert_raises(ValueError, TimeFrequency, freqs, output=output)
     tf = clone(tf)
 
     # Fit
@@ -27,7 +27,7 @@ def test_timefrequency():
     tf.fit(X, None)
 
     # Transform
-    tf = TimeFrequency(frequencies, sfreq=100)
+    tf = TimeFrequency(freqs, sfreq=100)
     tf.fit_transform(X, None)
     # 3-D X
     Xt = tf.transform(X)
@@ -36,6 +36,6 @@ def test_timefrequency():
     Xt = tf.transform(X[:, 0, :])
     assert_array_equal(Xt.shape, [n_epochs, n_freqs, n_times])
     # 3-D with decim
-    tf = TimeFrequency(frequencies, sfreq=100, decim=2)
+    tf = TimeFrequency(freqs, sfreq=100, decim=2)
     Xt = tf.transform(X)
     assert_array_equal(Xt.shape, [n_epochs, n_chans, n_freqs, n_times // 2])
diff --git a/mne/decoding/tests/test_time_gen.py b/mne/decoding/tests/test_time_gen.py
index 5c72648b..f85f3fe 100644
--- a/mne/decoding/tests/test_time_gen.py
+++ b/mne/decoding/tests/test_time_gen.py
@@ -7,12 +7,14 @@ import copy
 import os.path as op
 
 from nose.tools import assert_equal, assert_true, assert_raises
+import pytest
 import numpy as np
 from numpy.testing import assert_array_equal
 
 from mne import io, Epochs, read_events, pick_types
-from mne.utils import (requires_sklearn, requires_sklearn_0_15, slow_test,
-                       run_tests_if_main, check_version, use_log_level)
+from mne.fixes import is_classifier
+from mne.utils import (requires_version, run_tests_if_main, check_version,
+                       use_log_level)
 from mne.decoding import GeneralizationAcrossTime, TimeDecoding
 
 
@@ -28,7 +30,7 @@ warnings.simplefilter('always')
 
 
 def make_epochs():
-    raw = io.read_raw_fif(raw_fname, preload=False, add_eeg_ref=False)
+    raw = io.read_raw_fif(raw_fname)
     events = read_events(event_name)
     picks = pick_types(raw.info, meg='mag', stim=False, ecg=False,
                        eog=False, exclude='bads')
@@ -38,18 +40,15 @@ def make_epochs():
     # Test on time generalization within one condition
     with warnings.catch_warnings(record=True):
         epochs = Epochs(raw, events, event_id, tmin, tmax, picks=picks,
-                        baseline=(None, 0), preload=True, decim=decim,
-                        add_eeg_ref=False)
+                        baseline=(None, 0), preload=True, decim=decim)
     return epochs
 
 
- at slow_test
- at requires_sklearn_0_15
+ at pytest.mark.slowtest
+ at requires_version('sklearn', '0.17')
 def test_generalization_across_time():
-    """Test time generalization decoding
-    """
+    """Test time generalization decoding."""
     from sklearn.svm import SVC
-    from sklearn.base import is_classifier
     # KernelRidge is used for testing 1) regression analyses 2) n-dimensional
     # predictions.
     from sklearn.kernel_ridge import KernelRidge
@@ -61,8 +60,8 @@ def test_generalization_across_time():
     if check_version('sklearn', '0.18'):
         from sklearn.model_selection import (KFold, StratifiedKFold,
                                              ShuffleSplit, LeaveOneGroupOut)
-        cv_shuffle = ShuffleSplit()
         cv = LeaveOneGroupOut()
+        cv_shuffle = ShuffleSplit()
         # XXX we cannot pass any other parameters than X and y to cv.split
         # so we have to build it before hand
         cv_lolo = [(train, test) for train, test in cv.split(
@@ -81,7 +80,8 @@ def test_generalization_across_time():
         # therefore the scoring metrics cannot be automatically assigned.
         scorer_regress = mean_squared_error
     # Test default running
-    gat = GeneralizationAcrossTime(picks='foo')
+    with warnings.catch_warnings(record=True):  # dep
+        gat = GeneralizationAcrossTime(picks='foo')
     assert_equal("<GAT | no fit, no prediction, no score>", "%s" % gat)
     assert_raises(ValueError, gat.fit, epochs)
     with warnings.catch_warnings(record=True):
@@ -98,7 +98,8 @@ def test_generalization_across_time():
                  "prediction, no score>", '%s' % gat)
     assert_equal(gat.ch_names, epochs.ch_names)
     # test different predict function:
-    gat = GeneralizationAcrossTime(predict_method='decision_function')
+    with warnings.catch_warnings(record=True):  # dep
+        gat = GeneralizationAcrossTime(predict_method='decision_function')
     gat.fit(epochs)
     # With classifier, the default cv is StratifiedKFold
     assert_true(gat.cv_.__class__ == StratifiedKFold)
@@ -192,7 +193,8 @@ def test_generalization_across_time():
                         for ww in w))
 
     # Test longer time window
-    gat = GeneralizationAcrossTime(train_times={'length': .100})
+    with warnings.catch_warnings(record=True):  # dep
+        gat = GeneralizationAcrossTime(train_times={'length': .100})
     with warnings.catch_warnings(record=True):
         gat2 = gat.fit(epochs)
     assert_true(gat is gat2)  # return self
@@ -204,7 +206,8 @@ def test_generalization_across_time():
     assert_equal(len(scores[0]), len(scores))  # shape check
     assert_equal(len(gat.test_times_['slices'][0][0]), 2)
     # Decim training steps
-    gat = GeneralizationAcrossTime(train_times={'step': .100})
+    with warnings.catch_warnings(record=True):  # dep
+        gat = GeneralizationAcrossTime(train_times={'step': .100})
     with warnings.catch_warnings(record=True):
         gat.fit(epochs)
     gat.score(epochs)
@@ -214,7 +217,8 @@ def test_generalization_across_time():
     # Test start stop training & test cv without n_fold params
     y_4classes = np.hstack((epochs.events[:7, 2], epochs.events[7:, 2] + 1))
     train_times = dict(start=0.090, stop=0.250)
-    gat = GeneralizationAcrossTime(cv=cv_lolo, train_times=train_times)
+    with warnings.catch_warnings(record=True):  # dep
+        gat = GeneralizationAcrossTime(cv=cv_lolo, train_times=train_times)
     # predict without fit
     assert_raises(RuntimeError, gat.predict, epochs)
     with warnings.catch_warnings(record=True):
@@ -225,7 +229,8 @@ def test_generalization_across_time():
     assert_equal(gat.train_times_['times'][-1], epochs.times[9])
 
     # Test score without passing epochs & Test diagonal decoding
-    gat = GeneralizationAcrossTime(test_times='diagonal')
+    with warnings.catch_warnings(record=True):  # dep
+        gat = GeneralizationAcrossTime(test_times='diagonal')
     with warnings.catch_warnings(record=True):  # not vectorizing
         gat.fit(epochs)
     assert_raises(RuntimeError, gat.score)
@@ -237,7 +242,8 @@ def test_generalization_across_time():
     assert_array_equal([tim for ttime in gat.test_times_['times']
                         for tim in ttime], gat.train_times_['times'])
     # Test generalization across conditions
-    gat = GeneralizationAcrossTime(predict_mode='mean-prediction', cv=2)
+    with warnings.catch_warnings(record=True):  # dep
+        gat = GeneralizationAcrossTime(predict_mode='mean-prediction', cv=2)
     with warnings.catch_warnings(record=True):
         gat.fit(epochs[0:6])
     with warnings.catch_warnings(record=True):
@@ -298,8 +304,9 @@ def test_generalization_across_time():
     # The first estimator is tested once, the second estimator is tested on
     # two successive time samples.
     test_times = dict(slices=[[[0, 1]], [[0], [1]]])
-    gat = GeneralizationAcrossTime(train_times=train_times,
-                                   test_times=test_times)
+    with warnings.catch_warnings(record=True):  # dep
+        gat = GeneralizationAcrossTime(train_times=train_times,
+                                       test_times=test_times)
     gat.fit(epochs)
     with warnings.catch_warnings(record=True):  # not vectorizing
         gat.score(epochs)
@@ -310,7 +317,8 @@ def test_generalization_across_time():
     assert_raises(ValueError, gat.predict, epochs)
 
     svc = SVC(C=1, kernel='linear', probability=True)
-    gat = GeneralizationAcrossTime(clf=svc, predict_mode='mean-prediction')
+    with warnings.catch_warnings(record=True):  # dep
+        gat = GeneralizationAcrossTime(clf=svc, predict_mode='mean-prediction')
     with warnings.catch_warnings(record=True):
         gat.fit(epochs)
 
@@ -323,9 +331,22 @@ def test_generalization_across_time():
     assert_true(0.0 <= np.min(scores) <= 1.0)
     assert_true(0.0 <= np.max(scores) <= 1.0)
 
+    # Test that error if cv is not partition
+    with warnings.catch_warnings(record=True):  # dep
+        gat = GeneralizationAcrossTime(cv=cv_shuffle,
+                                       predict_mode='cross-validation')
+    gat.fit(epochs)
+    assert_raises(ValueError, gat.predict, epochs)
+    with warnings.catch_warnings(record=True):  # dep
+        gat = GeneralizationAcrossTime(cv=cv_shuffle,
+                                       predict_mode='mean-prediction')
+    gat.fit(epochs)
+    gat.predict(epochs)
+
     # Test that gets error if train on one dataset, test on another, and don't
     # specify appropriate cv:
-    gat = GeneralizationAcrossTime(cv=cv_shuffle)
+    with warnings.catch_warnings(record=True):  # dep
+        gat = GeneralizationAcrossTime()
     gat.fit(epochs)
     with warnings.catch_warnings(record=True):
         gat.fit(epochs)
@@ -342,13 +363,15 @@ def test_generalization_across_time():
         assert_true(any('do not have any test epochs' in str(ww.message)
                         for ww in w))
     # --- empty train fold(s) should raise when gat.fit()
-    gat = GeneralizationAcrossTime(cv=[([0], [1]), ([], [0])])
+    with warnings.catch_warnings(record=True):  # dep
+        gat = GeneralizationAcrossTime(cv=[([0], [1]), ([], [0])])
     assert_raises(ValueError, gat.fit, epochs[:2])
 
     # Check that still works with classifier that output y_pred with
     # shape = (n_trials, 1) instead of (n_trials,)
     if check_version('sklearn', '0.17'):  # no is_regressor before v0.17
-        gat = GeneralizationAcrossTime(clf=KernelRidge(), cv=2)
+        with warnings.catch_warnings(record=True):  # dep
+            gat = GeneralizationAcrossTime(clf=KernelRidge(), cv=2)
         epochs.crop(None, epochs.times[2])
         gat.fit(epochs)
         # With regression the default cv is KFold and not StratifiedKFold
@@ -406,17 +429,17 @@ def test_generalization_across_time():
                         assert_equal(scorer_name, scorer.__name__)
 
 
- at requires_sklearn
+ at requires_version('sklearn', '0.17')
 def test_decoding_time():
-    """Test TimeDecoding
-    """
+    """Test TimeDecoding."""
     from sklearn.svm import SVR
     if check_version('sklearn', '0.18'):
         from sklearn.model_selection import KFold
     else:
         from sklearn.cross_validation import KFold
     epochs = make_epochs()
-    tg = TimeDecoding()
+    with warnings.catch_warnings(record=True):  # dep
+        tg = TimeDecoding()
     assert_equal("<TimeDecoding | no fit, no prediction, no score>", '%s' % tg)
     assert_true(hasattr(tg, 'times'))
     assert_true(not hasattr(tg, 'train_times'))
@@ -444,16 +467,19 @@ def test_decoding_time():
     clf = SVR()
     cv = KFold(len(epochs))
     y = np.random.rand(len(epochs))
-    tg = TimeDecoding(clf=clf, cv=cv)
+    with warnings.catch_warnings(record=True):  # dep
+        tg = TimeDecoding(clf=clf, cv=cv)
     tg.fit(epochs, y=y)
 
     # Test scorer parameter to accept string
     epochs.crop(epochs.times[0], epochs.times[2])
-    td_1 = TimeDecoding(scorer='accuracy')
+    with warnings.catch_warnings(record=True):  # dep
+        td_1 = TimeDecoding(scorer='accuracy')
     td_1.fit(epochs)
     score_1 = td_1.score(epochs)
 
-    td_2 = TimeDecoding()
+    with warnings.catch_warnings(record=True):  # dep
+        td_2 = TimeDecoding()
     td_2.fit(epochs)
     score_2 = td_2.score(epochs)
     assert_array_equal(score_1, score_2)
diff --git a/mne/decoding/tests/test_transformer.py b/mne/decoding/tests/test_transformer.py
index 443d7cf..665c37a 100644
--- a/mne/decoding/tests/test_transformer.py
+++ b/mne/decoding/tests/test_transformer.py
@@ -7,15 +7,15 @@ import warnings
 import os.path as op
 import numpy as np
 
-from nose.tools import assert_true, assert_raises
-from numpy.testing import (assert_array_equal, assert_equal,
-                           assert_array_almost_equal)
+from nose.tools import assert_true, assert_raises, assert_equal
+from numpy.testing import (assert_array_equal, assert_array_almost_equal,
+                           assert_allclose)
 
 from mne import io, read_events, Epochs, pick_types
-from mne.decoding import Scaler, FilterEstimator
-from mne.decoding import (PSDEstimator, EpochsVectorizer, Vectorizer,
+from mne.decoding import (Scaler, FilterEstimator, PSDEstimator, Vectorizer,
                           UnsupervisedSpatialFilter, TemporalFilter)
-from mne.utils import requires_sklearn_0_15
+from mne.defaults import DEFAULTS
+from mne.utils import requires_version, run_tests_if_main, check_version
 
 warnings.simplefilter('always')  # enable b/c these tests throw warnings
 
@@ -30,45 +30,73 @@ event_name = op.join(data_dir, 'test-eve.fif')
 
 def test_scaler():
     """Test methods of Scaler."""
-    raw = io.read_raw_fif(raw_fname, preload=False, add_eeg_ref=False)
+    raw = io.read_raw_fif(raw_fname)
     events = read_events(event_name)
     picks = pick_types(raw.info, meg=True, stim=False, ecg=False,
                        eog=False, exclude='bads')
     picks = picks[1:13:3]
 
     epochs = Epochs(raw, events, event_id, tmin, tmax, picks=picks,
-                    baseline=(None, 0), preload=True, add_eeg_ref=False)
+                    baseline=(None, 0), preload=True)
     epochs_data = epochs.get_data()
-    scaler = Scaler(epochs.info)
     y = epochs.events[:, -1]
 
-    # np invalid divide value warnings
-    with warnings.catch_warnings(record=True):
+    methods = (None, dict(mag=5, grad=10, eeg=20), 'mean', 'median')
+    infos = (epochs.info, epochs.info, None, None)
+    epochs_data_t = epochs_data.transpose([1, 0, 2])
+    for method, info in zip(methods, infos):
+        if method == 'median' and not check_version('sklearn', '0.17'):
+            assert_raises(ValueError, Scaler, info, method)
+            continue
+        if method == 'mean' and not check_version('sklearn', ''):
+            assert_raises(ImportError, Scaler, info, method)
+            continue
+        scaler = Scaler(info, method)
         X = scaler.fit_transform(epochs_data, y)
-        assert_true(X.shape == epochs_data.shape)
-        X2 = scaler.fit(epochs_data, y).transform(epochs_data)
+        assert_equal(X.shape, epochs_data.shape)
+        if method is None or isinstance(method, dict):
+            sd = DEFAULTS['scalings'] if method is None else method
+            stds = np.zeros(len(picks))
+            for key in ('mag', 'grad'):
+                stds[pick_types(epochs.info, meg=key)] = 1. / sd[key]
+            stds[pick_types(epochs.info, meg=False, eeg=True)] = 1. / sd['eeg']
+            means = np.zeros(len(epochs.ch_names))
+        elif method == 'mean':
+            stds = np.array([np.std(ch_data) for ch_data in epochs_data_t])
+            means = np.array([np.mean(ch_data) for ch_data in epochs_data_t])
+        else:  # median
+            percs = np.array([np.percentile(ch_data, [25, 50, 75])
+                              for ch_data in epochs_data_t])
+            stds = percs[:, 2] - percs[:, 0]
+            means = percs[:, 1]
+        assert_allclose(X * stds[:, np.newaxis] + means[:, np.newaxis],
+                        epochs_data, rtol=1e-12, atol=1e-20, err_msg=method)
 
-    assert_array_equal(X2, X)
+        X2 = scaler.fit(epochs_data, y).transform(epochs_data)
+        assert_array_equal(X, X2)
 
-    # Test inverse_transform
-    with warnings.catch_warnings(record=True):  # invalid value in mult
-        Xi = scaler.inverse_transform(X, y)
-    assert_array_equal(epochs_data, Xi)
+        # inverse_transform
+        Xi = scaler.inverse_transform(X)
+        assert_array_almost_equal(epochs_data, Xi)
 
     # Test init exception
     assert_raises(ValueError, scaler.fit, epochs, y)
-    assert_raises(ValueError, scaler.transform, epochs, y)
+    assert_raises(ValueError, scaler.transform, epochs)
+    epochs_bad = Epochs(raw, events, event_id, 0, 0.01,
+                        picks=np.arange(len(raw.ch_names)))  # non-data chs
+    scaler = Scaler(epochs_bad.info, None)
+    assert_raises(ValueError, scaler.fit, epochs_bad.get_data(), y)
 
 
 def test_filterestimator():
     """Test methods of FilterEstimator."""
-    raw = io.read_raw_fif(raw_fname, preload=False, add_eeg_ref=False)
+    raw = io.read_raw_fif(raw_fname)
     events = read_events(event_name)
     picks = pick_types(raw.info, meg=True, stim=False, ecg=False,
                        eog=False, exclude='bads')
     picks = picks[1:13:3]
     epochs = Epochs(raw, events, event_id, tmin, tmax, picks=picks,
-                    baseline=(None, 0), preload=True, add_eeg_ref=False)
+                    baseline=(None, 0), preload=True)
     epochs_data = epochs.get_data()
 
     # Add tests for different combinations of l_freq and h_freq
@@ -106,13 +134,13 @@ def test_filterestimator():
 
 def test_psdestimator():
     """Test methods of PSDEstimator."""
-    raw = io.read_raw_fif(raw_fname, preload=False, add_eeg_ref=False)
+    raw = io.read_raw_fif(raw_fname)
     events = read_events(event_name)
     picks = pick_types(raw.info, meg=True, stim=False, ecg=False,
                        eog=False, exclude='bads')
     picks = picks[1:13:3]
     epochs = Epochs(raw, events, event_id, tmin, tmax, picks=picks,
-                    baseline=(None, 0), preload=True, add_eeg_ref=False)
+                    baseline=(None, 0), preload=True)
     epochs_data = epochs.get_data()
     psd = PSDEstimator(2 * np.pi, 0, np.inf)
     y = epochs.events[:, -1]
@@ -126,48 +154,6 @@ def test_psdestimator():
     assert_raises(ValueError, psd.transform, epochs, y)
 
 
-def test_epochs_vectorizer():
-    """Test methods of EpochsVectorizer."""
-    raw = io.read_raw_fif(raw_fname, preload=False, add_eeg_ref=False)
-    events = read_events(event_name)
-    picks = pick_types(raw.info, meg=True, stim=False, ecg=False,
-                       eog=False, exclude='bads')
-    picks = picks[1:13:3]
-    with warnings.catch_warnings(record=True):
-        epochs = Epochs(raw, events, event_id, tmin, tmax, picks=picks,
-                        baseline=(None, 0), preload=True, add_eeg_ref=False)
-    epochs_data = epochs.get_data()
-    with warnings.catch_warnings(record=True):  # deprecation
-        vector = EpochsVectorizer(epochs.info)
-    y = epochs.events[:, -1]
-    X = vector.fit_transform(epochs_data, y)
-
-    # Check data dimensions
-    assert_true(X.shape[0] == epochs_data.shape[0])
-    assert_true(X.shape[1] == epochs_data.shape[1] * epochs_data.shape[2])
-
-    assert_array_equal(vector.fit(epochs_data, y).transform(epochs_data), X)
-
-    # Check if data is preserved
-    n_times = epochs_data.shape[2]
-    assert_array_equal(epochs_data[0, 0, 0:n_times], X[0, 0:n_times])
-
-    # Check inverse transform
-    Xi = vector.inverse_transform(X, y)
-    assert_true(Xi.shape[0] == epochs_data.shape[0])
-    assert_true(Xi.shape[1] == epochs_data.shape[1])
-    assert_array_equal(epochs_data[0, 0, 0:n_times], Xi[0, 0, 0:n_times])
-
-    # check if inverse transform works with different number of epochs
-    Xi = vector.inverse_transform(epochs_data[0], y)
-    assert_true(Xi.shape[1] == epochs_data.shape[1])
-    assert_true(Xi.shape[2] == epochs_data.shape[2])
-
-    # Test init exception
-    assert_raises(ValueError, vector.fit, epochs, y)
-    assert_raises(ValueError, vector.transform, epochs, y)
-
-
 def test_vectorizer():
     """Test Vectorizer."""
     data = np.random.rand(150, 18, 6)
@@ -193,19 +179,18 @@ def test_vectorizer():
                   np.random.rand(102, 12, 12))
 
 
- at requires_sklearn_0_15
+ at requires_version('sklearn', '0.15')
 def test_unsupervised_spatial_filter():
     """Test unsupervised spatial filter."""
     from sklearn.decomposition import PCA
     from sklearn.kernel_ridge import KernelRidge
-    raw = io.read_raw_fif(raw_fname, preload=False, add_eeg_ref=False)
+    raw = io.read_raw_fif(raw_fname)
     events = read_events(event_name)
     picks = pick_types(raw.info, meg=True, stim=False, ecg=False,
                        eog=False, exclude='bads')
     picks = picks[1:13:3]
     epochs = Epochs(raw, events, event_id, tmin, tmax, picks=picks,
-                    preload=True, baseline=None, verbose=False,
-                    add_eeg_ref=False)
+                    preload=True, baseline=None, verbose=False)
 
     # Test estimator
     assert_raises(ValueError, UnsupervisedSpatialFilter, KernelRidge(2))
@@ -221,8 +206,8 @@ def test_unsupervised_spatial_filter():
     assert_equal(usf.transform(X).ndim, 3)
     # test fit_transform
     assert_array_almost_equal(usf.transform(X), usf1.fit_transform(X))
-    # assert shape
     assert_equal(usf.transform(X).shape[1], n_components)
+    assert_array_almost_equal(usf.inverse_transform(usf.transform(X)), X)
 
     # Test with average param
     usf = UnsupervisedSpatialFilter(PCA(4), average=True)
@@ -238,12 +223,12 @@ def test_temporal_filter():
     values = (('10hz', None, 100., 'auto'), (5., '10hz', 100., 'auto'),
               (10., 20., 5., 'auto'), (None, None, 100., '5hz'))
     for low, high, sf, ltrans in values:
-        filt = TemporalFilter(low, high, sf, ltrans)
+        filt = TemporalFilter(low, high, sf, ltrans, fir_design='firwin')
         assert_raises(ValueError, filt.fit_transform, X)
 
     # Add tests for different combinations of l_freq and h_freq
     for low, high in ((5., 15.), (None, 15.), (5., None)):
-        filt = TemporalFilter(low, high, sfreq=100.)
+        filt = TemporalFilter(low, high, sfreq=100., fir_design='firwin')
         Xt = filt.fit_transform(X)
         assert_array_equal(filt.fit_transform(X), Xt)
         assert_true(X.shape == Xt.shape)
@@ -255,5 +240,8 @@ def test_temporal_filter():
     # Test with 2 dimensional data array
     X = np.random.rand(101, 500)
     filt = TemporalFilter(l_freq=25., h_freq=50., sfreq=1000.,
-                          filter_length=150)
+                          filter_length=150, fir_design='firwin2')
     assert_equal(filt.fit_transform(X).shape, X.shape)
+
+
+run_tests_if_main()
diff --git a/mne/decoding/time_delaying_ridge.py b/mne/decoding/time_delaying_ridge.py
new file mode 100644
index 0000000..e236b00
--- /dev/null
+++ b/mne/decoding/time_delaying_ridge.py
@@ -0,0 +1,343 @@
+# -*- coding: utf-8 -*-
+"""TimeDelayingRidge class."""
+# Authors: Eric Larson <larson.eric.d at gmail.com>
+#          Ross Maddox <ross.maddox at rochester.edu>
+#
+# License: BSD (3-clause)
+
+import warnings
+
+import numpy as np
+from scipy import linalg
+
+from .base import BaseEstimator
+from ..filter import next_fast_len
+from ..externals.six import string_types
+
+
+def _compute_corrs(X, y, smin, smax):
+    """Compute auto- and cross-correlations."""
+    if X.ndim == 2:
+        assert y.ndim == 2
+        X = X[:, np.newaxis, :]
+        y = y[:, np.newaxis, :]
+    assert X.shape[:2] == y.shape[:2]
+    len_trf = smax - smin
+    len_x, n_epochs, n_ch_x = X.shape
+    len_y, n_epcohs, n_ch_y = y.shape
+    assert len_x == len_y
+
+    n_fft = next_fast_len(X.shape[0] + max(smax, 0) - min(smin, 0) - 1)
+
+    x_xt = np.zeros([n_ch_x * len_trf] * 2)
+    x_y = np.zeros((len_trf, n_ch_x, n_ch_y), order='F')
+    for ei in range(n_epochs):
+        this_X = X[:, ei, :]
+        X_fft = np.fft.rfft(this_X, n_fft, axis=0)
+        y_fft = np.fft.rfft(y[:, ei, :], n_fft, axis=0)
+
+        # compute the autocorrelations
+        for ch0 in range(n_ch_x):
+            other_sl = slice(ch0, n_ch_x)
+            ac_temp = np.fft.irfft(X_fft[:, ch0][:, np.newaxis] *
+                                   X_fft[:, other_sl].conj(), n_fft, axis=0)
+            n_other = ac_temp.shape[1]
+            row = ac_temp[:len_trf]  # zero and positive lags
+            col = ac_temp[-1:-len_trf:-1]  # negative lags
+            # Our autocorrelation structure is a Toeplitz matrix, but
+            # it's faster to create the Toeplitz ourselves.
+            x_xt_temp = np.zeros((len_trf, len_trf, n_other))
+            for ii in range(len_trf):
+                x_xt_temp[ii, ii:] = row[:len_trf - ii]
+                x_xt_temp[ii + 1:, ii] = col[:len_trf - ii - 1]
+            row_adjust = np.zeros((len_trf, n_other))
+            col_adjust = np.zeros((len_trf, n_other))
+
+            # However, we need to adjust for coeffs that are cut off by
+            # the mode="same"-like behavior of the algorithm,
+            # i.e. the non-zero delays should not have the same AC value
+            # as the zero-delay ones (because they actually have fewer
+            # coefficents).
+            #
+            # These adjustments also follow a Toeplitz structure, but it's
+            # computationally more efficient to manually accumulate and
+            # subtract from each row and col, rather than accumulate a single
+            # adjustment matrix using Toeplitz repetitions then subtract
+
+            # Adjust positive lags where the tail gets cut off
+            for idx in range(1, smax):
+                ii = idx - smin
+                end_sl = slice(X.shape[0] - idx, -smax - min(ii, 0), -1)
+                c = (this_X[-idx, other_sl][np.newaxis] *
+                     this_X[end_sl, ch0][:, np.newaxis])
+                r = this_X[-idx, ch0] * this_X[end_sl, other_sl]
+                if ii <= 0:
+                    col_adjust += c
+                    row_adjust += r
+                    if ii == 0:
+                        x_xt_temp[0, :] = row - row_adjust
+                        x_xt_temp[1:, 0] = col - col_adjust[1:]
+                else:
+                    col_adjust[:-ii] += c
+                    row_adjust[:-ii] += r
+                    x_xt_temp[ii, ii:] = row[:-ii] - row_adjust[:-ii]
+                    x_xt_temp[ii + 1:, ii] = col[:-ii] - col_adjust[1:-ii]
+
+            # Adjust negative lags where the head gets cut off
+            x_xt_temp = x_xt_temp[::-1][:, ::-1]
+            row_adjust.fill(0.)
+            col_adjust.fill(0.)
+            for idx in range(0, -smin):
+                ii = idx + smax
+                start_sl = slice(idx, -smin + min(ii, 0))
+                c = (this_X[idx, other_sl][np.newaxis] *
+                     this_X[start_sl, ch0][:, np.newaxis])
+                r = this_X[idx, ch0] * this_X[start_sl, other_sl]
+                if ii <= 0:
+                    col_adjust += c
+                    row_adjust += r
+                    if ii == 0:
+                        x_xt_temp[0, :] -= row_adjust
+                        x_xt_temp[1:, 0] -= col_adjust[1:]
+                else:
+                    col_adjust[:-ii] += c
+                    row_adjust[:-ii] += r
+                    x_xt_temp[ii, ii:] -= row_adjust[:-ii]
+                    x_xt_temp[ii + 1:, ii] -= col_adjust[1:-ii]
+
+            x_xt_temp = x_xt_temp[::-1][:, ::-1]
+            for oi in range(n_other):
+                ch1 = oi + ch0
+                # Store the result
+                this_result = x_xt_temp[:, :, oi]
+                x_xt[ch0 * len_trf:(ch0 + 1) * len_trf,
+                     ch1 * len_trf:(ch1 + 1) * len_trf] += this_result
+                if ch0 != ch1:
+                    x_xt[ch1 * len_trf:(ch1 + 1) * len_trf,
+                         ch0 * len_trf:(ch0 + 1) * len_trf] += this_result.T
+
+            # compute the crosscorrelations
+            cc_temp = np.fft.irfft(
+                y_fft * X_fft[:, ch0][:, np.newaxis].conj(), n_fft, axis=0)
+            if smin < 0 and smax >= 0:
+                x_y[:-smin, ch0] += cc_temp[smin:]
+                x_y[len_trf - smax:, ch0] += cc_temp[:smax]
+            else:
+                x_y[:, ch0] += cc_temp[smin:smax]
+
+    x_y = np.reshape(x_y, (n_ch_x * len_trf, n_ch_y), order='F')
+    return x_xt, x_y, n_ch_x
+
+
+def _compute_reg_neighbors(n_ch_x, n_delays, reg_type, method='direct',
+                           normed=False):
+    """Compute regularization parameter from neighbors."""
+    from scipy.sparse.csgraph import laplacian
+    known_types = ('ridge', 'laplacian')
+    if isinstance(reg_type, string_types):
+        reg_type = (reg_type,) * 2
+    if len(reg_type) != 2:
+        raise ValueError('reg_type must have two elements, got %s'
+                         % (len(reg_type),))
+    for r in reg_type:
+        if r not in known_types:
+            raise ValueError('reg_type entries must be one of %s, got %s'
+                             % (known_types, r))
+    reg_time = (reg_type[0] == 'laplacian' and n_delays > 1)
+    reg_chs = (reg_type[1] == 'laplacian' and n_ch_x > 1)
+    if not reg_time and not reg_chs:
+        return np.eye(n_ch_x * n_delays)
+    # regularize time
+    if reg_time:
+        reg = np.eye(n_delays)
+        stride = n_delays + 1
+        reg.flat[1::stride] += -1
+        reg.flat[n_delays::stride] += -1
+        reg.flat[n_delays + 1:-n_delays - 1:stride] += 1
+        args = [reg] * n_ch_x
+        reg = linalg.block_diag(*args)
+    else:
+        reg = np.zeros((n_delays * n_ch_x,) * 2)
+
+    # regularize features
+    if reg_chs:
+        block = n_delays * n_delays
+        row_offset = block * n_ch_x
+        stride = n_delays * n_ch_x + 1
+        reg.flat[n_delays:-row_offset:stride] += -1
+        reg.flat[n_delays + row_offset::stride] += 1
+        reg.flat[row_offset:-n_delays:stride] += -1
+        reg.flat[:-(n_delays + row_offset):stride] += 1
+    assert np.array_equal(reg[::-1, ::-1], reg)
+
+    if method == 'direct':
+        if normed:
+            norm = np.sqrt(np.diag(reg))
+            reg /= norm
+            reg /= norm[:, np.newaxis]
+        return reg
+    else:
+        # Use csgraph. Note that our -1's above are really the neighbors!
+        # If we ever want to allow arbitrary adjacency matrices, this is how
+        # we'd want to do it.
+        reg = laplacian(-reg, normed=normed)
+    return reg
+
+
+def _fit_corrs(x_xt, x_y, n_ch_x, reg_type, alpha, n_ch_in):
+    """Fit the model using correlation matrices."""
+    # do the regularized solving
+    n_ch_out = x_y.shape[1]
+    assert x_y.shape[0] % n_ch_x == 0
+    n_delays = x_y.shape[0] // n_ch_x
+    reg = _compute_reg_neighbors(n_ch_x, n_delays, reg_type)
+    mat = x_xt + alpha * reg
+    # From sklearn
+    try:
+        # Note: we must use overwrite_a=False in order to be able to
+        #       use the fall-back solution below in case a LinAlgError
+        #       is raised
+        w = linalg.solve(mat, x_y, sym_pos=True, overwrite_a=False)
+    except np.linalg.LinAlgError:
+        warnings.warn('Singular matrix in solving dual problem. Using '
+                      'least-squares solution instead.')
+        w = linalg.lstsq(mat, x_y, lapack_driver='gelsy')[0]
+    w = w.T.reshape([n_ch_out, n_ch_in, n_delays])
+    return w
+
+
+class TimeDelayingRidge(BaseEstimator):
+    """Ridge regression of data with time delays.
+
+    Parameters
+    ----------
+    tmin : int | float
+        The starting lag, in seconds (or samples if ``sfreq`` == 1).
+        Negative values correspond to times in the past.
+    tmax : int | float
+        The ending lag, in seconds (or samples if ``sfreq`` == 1).
+        Positive values correspond to times in the future.
+        Must be >= tmin.
+    sfreq : float
+        The sampling frequency used to convert times into samples.
+    alpha : float
+        The ridge (or laplacian) regularization factor.
+    reg_type : str | list
+        Can be "ridge" (default) or "laplacian".
+        Can also be a 2-element list specifying how to regularize in time
+        and across adjacent features.
+    fit_intercept : bool
+        If True (default), the sample mean is removed before fitting.
+
+    Notes
+    -----
+    This class is meant to be used with :class:`mne.decoding.ReceptiveField`
+    by only implicitly doing the time delaying. For reasonable receptive
+    field and input signal sizes, it should be more CPU and memory
+    efficient by using frequency-domain methods (FFTs) to compute the
+    auto- and cross-correlations.
+
+    See Also
+    --------
+    mne.decoding.ReceptiveField
+    """
+
+    _estimator_type = "regressor"
+
+    def __init__(self, tmin, tmax, sfreq, alpha=0., reg_type='ridge',
+                 fit_intercept=True):  # noqa: D102
+        if tmin > tmax:
+            raise ValueError('tmin must be <= tmax, got %s and %s'
+                             % (tmin, tmax))
+        self.tmin = float(tmin)
+        self.tmax = float(tmax)
+        self.sfreq = float(sfreq)
+        self.alpha = float(alpha)
+        self.reg_type = reg_type
+        self.fit_intercept = fit_intercept
+
+    @property
+    def _smin(self):
+        return int(round(self.tmin * self.sfreq))
+
+    @property
+    def _smax(self):
+        return int(round(self.tmax * self.sfreq)) + 1
+
+    def fit(self, X, y):
+        """Estimate the coefficients of the linear model.
+
+        Parameters
+        ----------
+        X : array, shape (n_samples[, n_epochs], n_features)
+            The training input samples to estimate the linear coefficients.
+        y : array, shape (n_samples[, n_epochs],  n_outputs)
+            The target values.
+
+        Returns
+        -------
+        self : instance of TimeDelayingRidge
+            Returns the modified instance.
+        """
+        if X.ndim == 3:
+            assert y.ndim == 3
+            assert X.shape[:2] == y.shape[:2]
+        else:
+            assert X.ndim == 2 and y.ndim == 2
+            assert X.shape[0] == y.shape[0]
+        # These are split into two functions because it's possible that we
+        # might want to allow people to do them separately (e.g., to test
+        # different regularization parameters).
+        if self.fit_intercept:
+            # We could do this in the Fourier domain, too, but it should
+            # be a bit cleaner numerically to do it here.
+            X_offset = np.mean(X, axis=0)
+            y_offset = np.mean(y, axis=0)
+            if X.ndim == 3:
+                X_offset = X_offset.mean(axis=0)
+                y_offset = np.mean(y_offset, axis=0)
+            X = X - X_offset
+            y = y - y_offset
+        else:
+            X_offset = y_offset = 0.
+        self.cov_, x_y_, n_ch_x = _compute_corrs(X, y, self._smin, self._smax)
+        self.coef_ = _fit_corrs(self.cov_, x_y_, n_ch_x,
+                                self.reg_type, self.alpha, n_ch_x)
+        # This is the sklearn formula from LinearModel (will be 0. for no fit)
+        if self.fit_intercept:
+            self.intercept_ = y_offset - np.dot(X_offset, self.coef_.sum(-1).T)
+        else:
+            self.intercept_ = 0.
+        return self
+
+    def predict(self, X):
+        """Predict the output.
+
+        Parameters
+        ----------
+        X : array, shape (n_samples[, n_epochs], n_features)
+            The data.
+
+        Returns
+        -------
+        X : ndarray
+            The predicted response.
+        """
+        if X.ndim == 2:
+            X = X[:, np.newaxis, :]
+            singleton = True
+        else:
+            singleton = False
+        out = np.zeros(X.shape[:2] + (self.coef_.shape[0],))
+        smin = self._smin
+        offset = max(smin, 0)
+        for ei in range(X.shape[1]):
+            for oi in range(self.coef_.shape[0]):
+                for fi in range(self.coef_.shape[1]):
+                    temp = np.convolve(X[:, ei, fi], self.coef_[oi, fi])
+                    temp = temp[max(-smin, 0):][:len(out) - offset]
+                    out[offset:len(temp) + offset, ei, oi] += temp
+        out += self.intercept_
+        if singleton:
+            out = out[:, 0, :]
+        return out
diff --git a/mne/decoding/time_frequency.py b/mne/decoding/time_frequency.py
index c913130..e30de5f 100644
--- a/mne/decoding/time_frequency.py
+++ b/mne/decoding/time_frequency.py
@@ -6,6 +6,7 @@ import numpy as np
 from .mixin import TransformerMixin
 from .base import BaseEstimator
 from ..time_frequency.tfr import _compute_tfr, _check_tfr_param
+from ..utils import warn
 
 
 class TimeFrequency(TransformerMixin, BaseEstimator):
@@ -15,7 +16,7 @@ class TimeFrequency(TransformerMixin, BaseEstimator):
 
     Parameters
     ----------
-    frequencies : array-like of floats, shape (n_freqs,)
+    freqs : array-like of floats, shape (n_freqs,)
         The frequencies.
     sfreq : float | int, defaults to 1.0
         Sampling frequency of the data.
@@ -38,20 +39,20 @@ class TimeFrequency(TransformerMixin, BaseEstimator):
         decomposition.
         If `int`, returns tfr[..., ::decim].
         If `slice`, returns tfr[..., decim].
-        .. note:
-            Decimation may create aliasing artifacts, yet decimation
-            is done after the convolutions.
-    output : str, defaults to 'complex'
 
+        .. note:: Decimation may create aliasing artifacts, yet decimation
+                  is done after the convolutions.
+
+    output : str, defaults to 'complex'
         * 'complex' : single trial complex.
         * 'power' : single trial power.
         * 'phase' : single trial phase.
-
     n_jobs : int, defaults to 1
         The number of epochs to process at the same time. The parallelization
         is implemented across channels.
     verbose : bool, str, int, or None, defaults to None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     See Also
     --------
@@ -59,14 +60,15 @@ class TimeFrequency(TransformerMixin, BaseEstimator):
     mne.time_frequency.tfr_multitaper
     """
 
-    def __init__(self, frequencies, sfreq=1.0, method='morlet', n_cycles=7.0,
+    def __init__(self, freqs, sfreq=1.0, method='morlet', n_cycles=7.0,
                  time_bandwidth=None, use_fft=True, decim=1, output='complex',
-                 n_jobs=1, verbose=None):
+                 n_jobs=1, frequencies=None, verbose=None):  # noqa: D102
         """Init TimeFrequency transformer."""
-        frequencies, sfreq, _, n_cycles, time_bandwidth, decim = \
-            _check_tfr_param(frequencies, sfreq, method, True, n_cycles,
-                             time_bandwidth, use_fft, decim, output)
-        self.frequencies = frequencies
+        freqs, sfreq, _, n_cycles, time_bandwidth, decim = \
+            _check_tfr_param(freqs, sfreq, method, True, n_cycles,
+                             time_bandwidth, use_fft, decim, output,
+                             frequencies)
+        self.freqs = freqs
         self.sfreq = sfreq
         self.method = method
         self.n_cycles = n_cycles
@@ -81,9 +83,15 @@ class TimeFrequency(TransformerMixin, BaseEstimator):
         self.n_jobs = n_jobs
         self.verbose = verbose
 
+    @property
+    def frequencies(self):
+        """Deprecated and will be removed in 0.16. use freqs."""
+        warn('frequencies is deprecated and will be removed in 0.16, use'
+             'freqs instead', DeprecationWarning)
+        return self.freqs
+
     def fit_transform(self, X, y=None):
-        """
-        Time-frequency transform of times series along the last axis.
+        """Time-frequency transform of times series along the last axis.
 
         Parameters
         ----------
@@ -95,14 +103,14 @@ class TimeFrequency(TransformerMixin, BaseEstimator):
 
         Returns
         -------
-        Xt : array, shape (n_samples, n_channels, n_frequencies, n_times)
+        Xt : array, shape (n_samples, n_channels, n_freqs, n_times)
             The time-frequency transform of the data, where n_channels can be
             zero- or 1-dimensional.
         """
         return self.fit(X, y).transform(X)
 
-    def fit(self, X, y=None):
-        """ Does nothing, for scikit-learn compatibility purposes.
+    def fit(self, X, y=None):  # noqa: D401
+        """Do nothing (for scikit-learn compatibility purposes).
 
         Parameters
         ----------
@@ -129,7 +137,7 @@ class TimeFrequency(TransformerMixin, BaseEstimator):
 
         Returns
         -------
-        Xt : array, shape (n_samples, n_channels, n_frequencies, n_times)
+        Xt : array, shape (n_samples, n_channels, n_freqs, n_times)
             The time-frequency transform of the data, where n_channels can be
             zero- or 1-dimensional.
 
@@ -140,7 +148,7 @@ class TimeFrequency(TransformerMixin, BaseEstimator):
             X = X[:, np.newaxis, :]
 
         # Compute time-frequency
-        Xt = _compute_tfr(X, self.frequencies, self.sfreq, self.method,
+        Xt = _compute_tfr(X, self.freqs, self.sfreq, self.method,
                           self.n_cycles, True, self.time_bandwidth,
                           self.use_fft, self.decim, self.output, self.n_jobs,
                           self.verbose)
diff --git a/mne/decoding/time_gen.py b/mne/decoding/time_gen.py
index ce9c8ad..eb9ce6f 100644
--- a/mne/decoding/time_gen.py
+++ b/mne/decoding/time_gen.py
@@ -12,11 +12,14 @@ from .base import _set_cv
 from ..io.pick import _pick_data_channels
 from ..viz.decoding import plot_gat_matrix, plot_gat_times
 from ..parallel import parallel_func, check_n_jobs
-from ..utils import warn, check_version
+from ..utils import warn, deprecated
+from ..fixes import is_classifier, is_regressor
 
 
 class _DecodingTime(dict):
-    """A dictionary to configure the training times that has the following keys:
+    """Dictionary to configure the training times.
+
+    It has the following keys:
 
     'slices' : ndarray, shape (n_clfs,)
         Array of time slices (in indices) used for each classifier.
@@ -32,9 +35,11 @@ class _DecodingTime(dict):
         seconds). Defaults to one time sample.
     'length' : float
         Duration of each classifier (in seconds). Defaults to one time sample.
-    If None, empty dict. """
 
-    def __repr__(self):
+    If None, empty dict.
+    """
+
+    def __repr__(self):  # noqa: D105
         s = ""
         if "start" in self:
             s += "start: %0.3f (s)" % (self["start"])
@@ -62,13 +67,12 @@ class _DecodingTime(dict):
 
 
 class _GeneralizationAcrossTime(object):
-    """Generic object to train and test a series of classifiers at and across
-    different time samples.
-    """  # noqa
+    """Object to train and test classifiers at and acrosstime samples."""
+
     def __init__(self, picks=None, cv=5, clf=None, train_times=None,
                  test_times=None, predict_method='predict',
                  predict_mode='cross-validation', scorer=None,
-                 score_mode='mean-fold-wise', n_jobs=1):
+                 score_mode='mean-fold-wise', n_jobs=1):  # noqa: D102
 
         from sklearn.preprocessing import StandardScaler
         from sklearn.linear_model import LogisticRegression
@@ -166,7 +170,7 @@ class _GeneralizationAcrossTime(object):
         return self
 
     def predict(self, epochs):
-        """Classifiers' predictions on each specified testing time slice.
+        """Get predictions of classifiers on each specified testing time slice.
 
         .. note::
             This function sets the ``y_pred_`` and ``test_times_`` attributes.
@@ -184,8 +188,7 @@ class _GeneralizationAcrossTime(object):
             time. Note that the number of testing times per training time need
             not be regular; else
             ``np.shape(y_pred_) = (n_train_time, n_test_time, n_epochs)``.
-        """  # noqa
-
+        """  # noqa: E501
         # Check that classifier has predict_method (e.g. predict_proba is not
         # always available):
         if not hasattr(self.clf, self.predict_method):
@@ -212,6 +215,13 @@ class _GeneralizationAcrossTime(object):
                     'When predict_mode = "cross-validation", the training '
                     'and predicting cv schemes must be identical.')
 
+            # Check that cv is a partition: i.e. that each tested sample may
+            # have more than one prediction, such as with ShuffleSplit.
+            test_idx = [ii for _, test in self._cv_splits for ii in test]
+            if sum([sum(np.array(test_idx) == idx) > 1 for idx in test_idx]):
+                raise ValueError('cv must be a partition if predict_mode is '
+                                 '"cross-validation".')
+
         # Clean attributes
         for att in ['y_pred_', 'test_times_', 'scores_', 'scorer_', 'y_true_']:
             if hasattr(self, att):
@@ -309,7 +319,7 @@ class _GeneralizationAcrossTime(object):
         return self.y_pred_
 
     def score(self, epochs=None, y=None):
-        """Score Epochs
+        """Score Epochs.
 
         Estimate scores across trials by comparing the prediction estimated for
         each trial to its true value.
@@ -346,13 +356,7 @@ class _GeneralizationAcrossTime(object):
             n_test_time, n_folds).
         """
         import sklearn.metrics
-        from sklearn.base import is_classifier
         from sklearn.metrics import accuracy_score, mean_squared_error
-        if check_version('sklearn', '0.17'):
-            from sklearn.base import is_regressor
-        else:
-            def is_regressor(clf):
-                return False
 
         # Run predictions if not already done
         if epochs is not None:
@@ -443,7 +447,7 @@ _warn_once = dict()
 
 def _predict_slices(X, train_times, estimators, cv_splits, predict_mode,
                     predict_method, n_orig_epochs, test_epochs):
-    """Aux function of GeneralizationAcrossTime
+    """Aux function of GeneralizationAcrossTime.
 
     Run classifiers predictions loop across time samples.
 
@@ -478,7 +482,6 @@ def _predict_slices(X, train_times, estimators, cv_splits, predict_mode,
     test_epochs : list of slices
         List of slices to select the tested epoched in the cv.
     """
-
     # Check inputs
     n_epochs, _, n_times = X.shape
     n_train = len(estimators)
@@ -595,9 +598,7 @@ def _init_ypred(n_train, n_test, n_orig_epochs, n_dim):
 
 
 def _score_slices(y_true, list_y_pred, scorer, score_mode, cv):
-    """Aux function of GeneralizationAcrossTime that loops across chunks of
-    testing slices.
-    """
+    """Loop across chunks of testing slices."""
     scores_list = list()
     for y_pred in list_y_pred:
         scores = list()
@@ -620,7 +621,7 @@ def _score_slices(y_true, list_y_pred, scorer, score_mode, cv):
 
 
 def _check_epochs_input(epochs, y, picks=None):
-    """Aux function of GeneralizationAcrossTime
+    """Aux function of GeneralizationAcrossTime.
 
     Format MNE data into scikit-learn X and y.
 
@@ -667,7 +668,7 @@ def _check_epochs_input(epochs, y, picks=None):
 
 
 def _fit_slices(clf, x_chunk, y, slices, cv_splits):
-    """Aux function of GeneralizationAcrossTime
+    """Aux function of GeneralizationAcrossTime.
 
     Fit each classifier.
 
@@ -717,7 +718,7 @@ def _fit_slices(clf, x_chunk, y, slices, cv_splits):
 
 
 def _sliding_window(times, window, sfreq):
-    """Aux function of GeneralizationAcrossTime
+    """Aux function of GeneralizationAcrossTime.
 
     Define the slices on which to train each classifier. The user either define
     the time slices manually in window['slices'] or s/he passes optional params
@@ -791,13 +792,13 @@ def _sliding_window(times, window, sfreq):
 
 
 def _set_window_time(slices, times):
-    """Aux function to define time as the last training time point"""
+    """Aux function to define time as the last training time point."""
     t_idx_ = [t[-1] for t in slices]
     return times[t_idx_]
 
 
 def _predict(X, estimators, vectorize_times, predict_method):
-    """Aux function of GeneralizationAcrossTime
+    """Aux function of GeneralizationAcrossTime.
 
     Predict each classifier. If multiple classifiers are passed, average
     prediction across all classifiers to result in a single prediction per
@@ -823,7 +824,6 @@ def _predict(X, estimators, vectorize_times, predict_method):
         Classifier's prediction for each trial.
     """
     from scipy import stats
-    from sklearn.base import is_classifier
     # Initialize results:
 
     orig_shape = X.shape
@@ -866,8 +866,10 @@ def _predict(X, estimators, vectorize_times, predict_method):
     return y_pred
 
 
+ at deprecated('GeneralizationAcrossTime is deprecated and will be removed in '
+            ' 0.15, use GeneralizingEstimator instead.')
 class GeneralizationAcrossTime(_GeneralizationAcrossTime):
-    """Generalize across time and conditions
+    """Generalize across time and conditions.
 
     Creates an estimator object used to 1) fit a series of classifiers on
     multidimensional time-resolved data, and 2) test the ability of each
@@ -1015,18 +1017,19 @@ class GeneralizationAcrossTime(_GeneralizationAcrossTime):
        DOI: 10.1371/journal.pone.0085791
 
     .. versionadded:: 0.9.0
-    """  # noqa
+    """  # noqa: E501
+
     def __init__(self, picks=None, cv=5, clf=None, train_times=None,
                  test_times=None, predict_method='predict',
                  predict_mode='cross-validation', scorer=None,
-                 score_mode='mean-fold-wise', n_jobs=1):
+                 score_mode='mean-fold-wise', n_jobs=1):  # noqa: D102
         super(GeneralizationAcrossTime, self).__init__(
             picks=picks, cv=cv, clf=clf, train_times=train_times,
             test_times=test_times, predict_method=predict_method,
             predict_mode=predict_mode, scorer=scorer, score_mode=score_mode,
             n_jobs=n_jobs)
 
-    def __repr__(self):
+    def __repr__(self):  # noqa: D105
         s = ''
         if hasattr(self, "estimators_"):
             s += "fitted, start : %0.3f (s), stop : %0.3f (s)" % (
@@ -1054,7 +1057,7 @@ class GeneralizationAcrossTime(_GeneralizationAcrossTime):
     def plot(self, title=None, vmin=None, vmax=None, tlim=None, ax=None,
              cmap='RdBu_r', show=True, colorbar=True,
              xlabel=True, ylabel=True):
-        """Plotting function of GeneralizationAcrossTime object
+        """Plot GeneralizationAcrossTime object.
 
         Plot the score of each classifier at each tested time window.
 
@@ -1094,7 +1097,7 @@ class GeneralizationAcrossTime(_GeneralizationAcrossTime):
                       ymax=None, ax=None, show=True, color=None,
                       xlabel=True, ylabel=True, legend=True, chance=True,
                       label='Classif. score'):
-        """Plotting function of GeneralizationAcrossTime object
+        """Plot GeneralizationAcrossTime object.
 
         Plot each classifier score trained and tested at identical time
         windows.
@@ -1144,7 +1147,7 @@ class GeneralizationAcrossTime(_GeneralizationAcrossTime):
                    ymin=None, ymax=None, ax=None, show=True, color=None,
                    xlabel=True, ylabel=True, legend=True, chance=True,
                    label='Classif. score'):
-        """Plotting function of GeneralizationAcrossTime object
+        """Plot GeneralizationAcrossTime object.
 
         Plot the scores of the classifier trained at specific training time(s).
 
@@ -1196,9 +1199,12 @@ class GeneralizationAcrossTime(_GeneralizationAcrossTime):
                               legend=legend, chance=chance, label=label)
 
 
+ at deprecated('TimeDecoding is deprecated and will be removed in '
+            ' 0.15, use SlidingEstimator instead.')
 class TimeDecoding(_GeneralizationAcrossTime):
-    """Train and test a series of classifiers at each time point to obtain a
-    score across time.
+    """Train and test a series of classifiers at each time point.
+
+    This will result in a score across time.
 
     Parameters
     ----------
@@ -1316,11 +1322,12 @@ class TimeDecoding(_GeneralizationAcrossTime):
     The function is equivalent to the diagonal of GeneralizationAcrossTime()
 
     .. versionadded:: 0.10
-    """  # noqa
+    """  # noqa: E501
 
     def __init__(self, picks=None, cv=5, clf=None, times=None,
                  predict_method='predict', predict_mode='cross-validation',
-                 scorer=None, score_mode='mean-fold-wise', n_jobs=1):
+                 scorer=None, score_mode='mean-fold-wise',
+                 n_jobs=1):  # noqa: D102
         super(TimeDecoding, self).__init__(picks=picks, cv=cv, clf=clf,
                                            train_times=times,
                                            test_times='diagonal',
@@ -1331,7 +1338,7 @@ class TimeDecoding(_GeneralizationAcrossTime):
                                            n_jobs=n_jobs)
         self._clean_times()
 
-    def __repr__(self):
+    def __repr__(self):  # noqa: D105
         s = ''
         if hasattr(self, "estimators_"):
             s += "fitted, start : %0.3f (s), stop : %0.3f (s)" % (
@@ -1404,14 +1411,14 @@ class TimeDecoding(_GeneralizationAcrossTime):
         -------
         y_pred : list of lists of arrays of floats, shape (n_times, n_epochs, n_prediction_dims)
             The single-trial predictions at each time sample.
-        """  # noqa
+        """  # noqa: E501
         self._prep_times()
         super(TimeDecoding, self).predict(epochs)
         self._clean_times()
         return self.y_pred_
 
     def score(self, epochs=None, y=None):
-        """Score Epochs
+        """Score Epochs.
 
         Estimate scores across trials by comparing the prediction estimated for
         each trial to its true value.
@@ -1457,7 +1464,7 @@ class TimeDecoding(_GeneralizationAcrossTime):
     def plot(self, title=None, xmin=None, xmax=None, ymin=None, ymax=None,
              ax=None, show=True, color=None, xlabel=True, ylabel=True,
              legend=True, chance=True, label='Classif. score'):
-        """Plotting function
+        """Plot the decoding results.
 
         Predict each classifier. If multiple classifiers are passed, average
         prediction across all classifiers to result in a single prediction per
@@ -1509,7 +1516,7 @@ class TimeDecoding(_GeneralizationAcrossTime):
         return fig
 
     def _prep_times(self):
-        """Auxiliary function to allow compatibility with GAT"""
+        """Auxiliary function to allow compatibility with GAT."""
         self.test_times = 'diagonal'
         if hasattr(self, 'times'):
             self.train_times = self.times
@@ -1526,7 +1533,7 @@ class TimeDecoding(_GeneralizationAcrossTime):
             self.y_pred_ = [[y_pred] for y_pred in self.y_pred_]
 
     def _clean_times(self):
-        """Auxiliary function to allow compatibility with GAT"""
+        """Auxiliary function to allow compatibility with GAT."""
         if hasattr(self, 'train_times'):
             self.times = self.train_times
         if hasattr(self, 'train_times_'):
@@ -1550,7 +1557,6 @@ def _chunk_data(X, slices):
     samples that are required by each job. The indices of the training times
     must be adjusted accordingly.
     """
-
     # from object array to list
     slices = [sl for sl in slices if len(sl)]
     selected_times = np.hstack([np.ravel(sl) for sl in slices])
diff --git a/mne/decoding/transformer.py b/mne/decoding/transformer.py
index 220a12a..14012a9 100644
--- a/mne/decoding/transformer.py
+++ b/mne/decoding/transformer.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
 # Authors: Mainak Jas <mainak at neuro.hut.fi>
 #          Alexandre Gramfort <alexandre.gramfort at telecom-paristech.fr>
 #          Romain Trachel <trachelr at gmail.com>
@@ -10,44 +11,127 @@ from .mixin import TransformerMixin
 from .base import BaseEstimator
 
 from .. import pick_types
-from ..filter import (low_pass_filter, high_pass_filter, band_pass_filter,
-                      band_stop_filter, filter_data, _triage_filter_params)
-from ..time_frequency.psd import _psd_multitaper
-from ..externals import six
-from ..utils import _check_type_picks, deprecated
+from ..filter import filter_data, _triage_filter_params
+from ..time_frequency.psd import psd_array_multitaper
+from ..externals.six import string_types
+from ..utils import _check_type_picks, check_version, warn
+from ..io.pick import pick_info, _pick_data_channels, _picks_by_type
+from ..cov import _check_scalings_user
 
 
-class Scaler(TransformerMixin):
-    """Standardizes data across channels
+class _ConstantScaler():
+    """Scale channel types using constant values."""
+
+    def __init__(self, info, scalings, do_scaling=True):
+        self._scalings = scalings
+        self._info = info
+        self._do_scaling = do_scaling
+
+    def fit(self, X, y=None):
+        scalings = _check_scalings_user(self._scalings)
+        picks_by_type = _picks_by_type(pick_info(
+            self._info, _pick_data_channels(self._info, exclude=())))
+        std = np.ones(sum(len(p[1]) for p in picks_by_type))
+        if X.shape[1] != len(std):
+            raise ValueError('info had %d data channels but X has %d channels'
+                             % (len(std), len(X)))
+        if self._do_scaling:  # this is silly, but necessary for completeness
+            for kind, picks in picks_by_type:
+                std[picks] = 1. / scalings[kind]
+        self.std_ = std
+        self.mean_ = np.zeros_like(std)
+        return self
+
+    def transform(self, X, y=None):
+        return X / self.std_
+
+    def inverse_transform(self, X, y=None):
+        return X * self.std_
+
+    def fit_transform(self, X, y=None):
+        return self.fit(X, y).transform(X)
+
+
+def _sklearn_reshape_apply(func, return_result, X, *args, **kwargs):
+    """Reshape epochs and apply function."""
+    if not isinstance(X, np.ndarray):
+        raise ValueError("data should be an np.ndarray, got %s." % type(X))
+    X = np.atleast_3d(X)
+    orig_shape = X.shape
+    X = np.reshape(X.transpose(0, 2, 1), (-1, orig_shape[1]))
+    X = func(X, *args, **kwargs)
+    if return_result:
+        X.shape = (orig_shape[0], orig_shape[2], orig_shape[1])
+        X = X.transpose(0, 2, 1)
+        return X
+
+
+class Scaler(TransformerMixin, BaseEstimator):
+    u"""Standardize channel data.
+
+    This class scales data for each channel. It differs from scikit-learn
+    classes (e.g., :class:`sklearn.preprocessing.StandardScaler`) in that
+    it scales each *channel* by estimating μ and σ using data from all
+    time points and epochs, as opposed to standardizing each *feature*
+    (i.e., each time point for each channel) by estimating using μ and σ
+    using data from all epochs.
 
     Parameters
     ----------
-    info : instance of Info
-        The measurement info
+    info : instance of Info | None
+        The measurement info. Only necessary if ``scalings`` is a dict or
+        None.
+    scalings : dict, string, defaults to None.
+        Scaling method to be applied to data channel wise.
+
+        * if scalings is None (default), scales mag by 1e15, grad by 1e13,
+          and eeg by 1e6.
+        * if scalings is :class:`dict`, keys are channel types and values
+          are scale factors.
+        * if ``scalings=='median'``,
+          :class:`sklearn.preprocessing.RobustScaler`
+          is used (requires sklearn version 0.17+).
+        * if ``scalings=='mean'``,
+          :class:`sklearn.preprocessing.StandardScaler`
+          is used.
+
     with_mean : boolean, True by default
-        If True, center the data before scaling.
+        If True, center the data using mean (or median) before scaling.
+        Ignored for channel-type scaling.
     with_std : boolean, True by default
-        If True, scale the data to unit variance (or equivalently,
-        unit standard deviation).
+        If True, scale the data to unit variance (``scalings='mean'``),
+        quantile range (``scalings='median``), or using channel type
+        if ``scalings`` is a dict or None).
+    """
 
-    Attributes
-    ----------
-    info : instance of Info
-        The measurement info
-    ``ch_mean_`` : dict
-        The mean value for each channel type
-    ``std_`` : dict
-        The standard deviation for each channel type
-     """
-    def __init__(self, info, with_mean=True, with_std=True):
+    def __init__(self, info=None, scalings=None, with_mean=True,
+                 with_std=True):  # noqa: D102
         self.info = info
         self.with_mean = with_mean
         self.with_std = with_std
-        self.ch_mean_ = dict()  # TODO rename attribute
-        self.std_ = dict()  # TODO rename attribute
-
-    def fit(self, epochs_data, y):
-        """Standardizes data across channels
+        self.scalings = scalings
+
+        if not (scalings is None or isinstance(scalings, (dict, str))):
+            raise ValueError('scalings type should be dict, str, or None, '
+                             'got %s' % type(scalings))
+        if isinstance(scalings, string_types) and \
+                scalings not in ('mean', 'median'):
+            raise ValueError('Invalid method for scaling, must be "mean" or '
+                             '"median" but got %s' % scalings)
+        if scalings is None or isinstance(scalings, dict):
+            self._scaler = _ConstantScaler(info, scalings, self.with_std)
+        elif scalings == 'mean':
+            from sklearn.preprocessing import StandardScaler
+            self._scaler = StandardScaler(self.with_mean, self.with_std)
+        else:  # scalings == 'median':
+            if not check_version('sklearn', '0.17'):
+                raise ValueError("median requires version 0.17 of "
+                                 "sklearn library")
+            from sklearn.preprocessing import RobustScaler
+            self._scaler = RobustScaler(self.with_mean, self.with_std)
+
+    def fit(self, epochs_data, y=None):
+        """Standardize data across channels.
 
         Parameters
         ----------
@@ -61,34 +145,11 @@ class Scaler(TransformerMixin):
         self : instance of Scaler
             Returns the modified instance.
         """
-        if not isinstance(epochs_data, np.ndarray):
-            raise ValueError("epochs_data should be of type ndarray (got %s)."
-                             % type(epochs_data))
-
-        X = np.atleast_3d(epochs_data)
-
-        picks_list = dict()
-        picks_list['mag'] = pick_types(self.info, meg='mag', ref_meg=False,
-                                       exclude='bads')
-        picks_list['grad'] = pick_types(self.info, meg='grad', ref_meg=False,
-                                        exclude='bads')
-        picks_list['eeg'] = pick_types(self.info, eeg=True, ref_meg=False,
-                                       meg=False, exclude='bads')
-
-        self.picks_list_ = picks_list
-
-        for key, this_pick in picks_list.items():
-            if self.with_mean:
-                ch_mean = X[:, this_pick, :].mean(axis=1)[:, None, :]
-                self.ch_mean_[key] = ch_mean  # TODO rename attribute
-            if self.with_std:
-                ch_std = X[:, this_pick, :].mean(axis=1)[:, None, :]
-                self.std_[key] = ch_std  # TODO rename attribute
-
+        _sklearn_reshape_apply(self._scaler.fit, False, epochs_data, y=y)
         return self
 
     def transform(self, epochs_data, y=None):
-        """Standardizes data across channels
+        """Standardize data across channels.
 
         Parameters
         ----------
@@ -102,23 +163,24 @@ class Scaler(TransformerMixin):
         -------
         X : array, shape (n_epochs, n_channels, n_times)
             The data concatenated over channels.
-        """
-        if not isinstance(epochs_data, np.ndarray):
-            raise ValueError("epochs_data should be of type ndarray (got %s)."
-                             % type(epochs_data))
 
-        X = np.atleast_3d(epochs_data)
-
-        for key, this_pick in six.iteritems(self.picks_list_):
-            if self.with_mean:
-                X[:, this_pick, :] -= self.ch_mean_[key]
-            if self.with_std:
-                X[:, this_pick, :] /= self.std_[key]
+        Notes
+        -----
+        This function makes a copy of the data before the operations and the
+        memory usage may be large with big data.
+        """
+        if y is not None:
+            warn("The parameter y on transform() is "
+                 "deprecated and will be removed in 0.16",
+                 DeprecationWarning)
+        return _sklearn_reshape_apply(self._scaler.transform, True,
+                                      epochs_data)
 
-        return X
+    def fit_transform(self, epochs_data, y=None):
+        """Fit to data, then transform it.
 
-    def inverse_transform(self, epochs_data, y=None):
-        """ Inverse standardization of data across channels
+        Fits transformer to epochs_data and y and returns a transformed version
+        of epochs_data.
 
         Parameters
         ----------
@@ -126,130 +188,44 @@ class Scaler(TransformerMixin):
             The data.
         y : None | array, shape (n_epochs,)
             The label for each epoch.
-            If None not used. Defaults to None.
+            Defaults to None.
 
         Returns
         -------
         X : array, shape (n_epochs, n_channels, n_times)
             The data concatenated over channels.
-        """
-        if not isinstance(epochs_data, np.ndarray):
-            raise ValueError("epochs_data should be of type ndarray (got %s)."
-                             % type(epochs_data))
-
-        X = np.atleast_3d(epochs_data)
-
-        for key, this_pick in six.iteritems(self.picks_list_):
-            if self.with_mean:
-                X[:, this_pick, :] += self.ch_mean_[key]
-            if self.with_std:
-                X[:, this_pick, :] *= self.std_[key]
-
-        return X
-
-
- at deprecated("EpochsVectorizer will be deprecated in version 0.14; "
-            "use Vectorizer instead")
-class EpochsVectorizer(TransformerMixin):
-    """EpochsVectorizer transforms epoch data to fit into a scikit-learn pipeline.
 
-    Parameters
-    ----------
-    info : instance of Info
-        The measurement info.
-
-    Attributes
-    ----------
-    n_channels : int
-        The number of channels.
-    n_times : int
-        The number of time points.
-
-    """
-    def __init__(self, info=None):
-        self.info = info
-        self.n_channels = None
-        self.n_times = None
-
-    def fit(self, epochs_data, y):
-        """For each epoch, concatenate data from different channels into a single
-        feature vector.
-
-        Parameters
-        ----------
-        epochs_data : array, shape (n_epochs, n_channels, n_times)
-            The data to concatenate channels.
-        y : array, shape (n_epochs,)
-            The label for each epoch.
-
-        Returns
-        -------
-        self : instance of EpochsVectorizer
-            returns the modified instance
+        Notes
+        -----
+        This function makes a copy of the data before the operations and the
+        memory usage may be large with big data.
         """
-        if not isinstance(epochs_data, np.ndarray):
-            raise ValueError("epochs_data should be of type ndarray (got %s)."
-                             % type(epochs_data))
+        return self.fit(epochs_data, y).transform(epochs_data)
 
-        return self
-
-    def transform(self, epochs_data, y=None):
-        """For each epoch, concatenate data from different channels into a single
-        feature vector.
+    def inverse_transform(self, epochs_data):
+        """Invert standardization of data across channels.
 
         Parameters
         ----------
         epochs_data : array, shape (n_epochs, n_channels, n_times)
             The data.
-        y : None | array, shape (n_epochs,)
-            The label for each epoch.
-            If None not used. Defaults to None.
 
         Returns
         -------
-        X : array, shape (n_epochs, n_channels * n_times)
-            The data concatenated over channels
-        """
-        if not isinstance(epochs_data, np.ndarray):
-            raise ValueError("epochs_data should be of type ndarray (got %s)."
-                             % type(epochs_data))
-
-        epochs_data = np.atleast_3d(epochs_data)
-
-        n_epochs, n_channels, n_times = epochs_data.shape
-        X = epochs_data.reshape(n_epochs, n_channels * n_times)
-        # save attributes for inverse_transform
-        self.n_epochs = n_epochs
-        self.n_channels = n_channels
-        self.n_times = n_times
-
-        return X
-
-    def inverse_transform(self, X, y=None):
-        """For each epoch, reshape a feature vector into the original data shape
-
-        Parameters
-        ----------
-        X : array, shape (n_epochs, n_channels * n_times)
-            The feature vector concatenated over channels
-        y : None | array, shape (n_epochs,)
-            The label for each epoch.
-            If None not used. Defaults to None.
+        X : array, shape (n_epochs, n_channels, n_times)
+            The data concatenated over channels.
 
-        Returns
-        -------
-        epochs_data : array, shape (n_epochs, n_channels, n_times)
-            The original data
+        Notes
+        -----
+        This function makes a copy of the data before the operations and the
+        memory usage may be large with big data.
         """
-        if not isinstance(X, np.ndarray):
-            raise ValueError("epochs_data should be of type ndarray (got %s)."
-                             % type(X))
-
-        return X.reshape(-1, self.n_channels, self.n_times)
+        return _sklearn_reshape_apply(self._scaler.inverse_transform, True,
+                                      epochs_data)
 
 
 class Vectorizer(TransformerMixin):
-    """Transforms n-dimensional array into 2D array of n_samples by n_features.
+    """Transform n-dimensional array into 2D array of n_samples by n_features.
 
     This class reshapes an n-dimensional array into an n_samples * n_features
     array, usable by the estimators and transformers of scikit-learn.
@@ -266,7 +242,7 @@ class Vectorizer(TransformerMixin):
     """
 
     def fit(self, X, y=None):
-        """Stores the shape of the features of X.
+        """Store the shape of the features of X.
 
         Parameters
         ----------
@@ -351,7 +327,7 @@ class Vectorizer(TransformerMixin):
 
 
 class PSDEstimator(TransformerMixin):
-    """Compute power spectrum density (PSD) using a multi-taper method
+    """Compute power spectrum density (PSD) using a multi-taper method.
 
     Parameters
     ----------
@@ -376,15 +352,17 @@ class PSDEstimator(TransformerMixin):
         be normalized by the sampling rate as well as the length of
         the signal (as in nitime).
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     See Also
     --------
-    psd_multitaper
+    mne.time_frequency.psd_multitaper
     """
+
     def __init__(self, sfreq=2 * np.pi, fmin=0, fmax=np.inf, bandwidth=None,
                  adaptive=False, low_bias=True, n_jobs=1,
-                 normalization='length', verbose=None):
+                 normalization='length', verbose=None):  # noqa: D102
         self.sfreq = sfreq
         self.fmin = fmin
         self.fmax = fmax
@@ -396,7 +374,7 @@ class PSDEstimator(TransformerMixin):
         self.normalization = normalization
 
     def fit(self, epochs_data, y):
-        """Compute power spectrum density (PSD) using a multi-taper method
+        """Compute power spectrum density (PSD) using a multi-taper method.
 
         Parameters
         ----------
@@ -417,7 +395,7 @@ class PSDEstimator(TransformerMixin):
         return self
 
     def transform(self, epochs_data, y=None):
-        """Compute power spectrum density (PSD) using a multi-taper method
+        """Compute power spectrum density (PSD) using a multi-taper method.
 
         Parameters
         ----------
@@ -432,11 +410,10 @@ class PSDEstimator(TransformerMixin):
         psd : array, shape (n_signals, len(freqs)) or (len(freqs),)
             The computed PSD.
         """
-
         if not isinstance(epochs_data, np.ndarray):
             raise ValueError("epochs_data should be of type ndarray (got %s)."
                              % type(epochs_data))
-        psd, _ = _psd_multitaper(
+        psd, _ = psd_array_multitaper(
             epochs_data, sfreq=self.sfreq, fmin=self.fmin, fmax=self.fmax,
             bandwidth=self.bandwidth, adaptive=self.adaptive,
             low_bias=self.low_bias, normalization=self.normalization,
@@ -445,7 +422,7 @@ class PSDEstimator(TransformerMixin):
 
 
 class FilterEstimator(TransformerMixin):
-    """Estimator to filter RtEpochs
+    """Estimator to filter RtEpochs.
 
     Applies a zero-phase low-pass, high-pass, band-pass, or band-stop
     filter to the channels selected by "picks".
@@ -494,17 +471,28 @@ class FilterEstimator(TransformerMixin):
         Dictionary of parameters to use for IIR filtering.
         See mne.filter.construct_iir_filter for details. If iir_params
         is None and method="iir", 4th order Butterworth will be used.
+    fir_design : str
+        Can be "firwin" (default in 0.16) to use
+        :func:`scipy.signal.firwin`, or "firwin2" (default in 0.15 and
+        before) to use :func:`scipy.signal.firwin2`. "firwin" uses a
+        time-domain design technique that generally gives improved
+        attenuation using fewer samples than "firwin2".
+
+        ..versionadded:: 0.15
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
-        Defaults to self.verbose.
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more). Defaults to
+        self.verbose.
 
     See Also
     --------
     TemporalFilter
     """
-    def __init__(self, info, l_freq, h_freq, picks=None, filter_length='',
-                 l_trans_bandwidth=None, h_trans_bandwidth=None, n_jobs=1,
-                 method='fft', iir_params=None, verbose=None):
+
+    def __init__(self, info, l_freq, h_freq, picks=None, filter_length='auto',
+                 l_trans_bandwidth='auto', h_trans_bandwidth='auto', n_jobs=1,
+                 method='fft', iir_params=None, fir_design=None,
+                 verbose=None):  # noqa: D102
         self.info = info
         self.l_freq = l_freq
         self.h_freq = h_freq
@@ -515,9 +503,10 @@ class FilterEstimator(TransformerMixin):
         self.n_jobs = n_jobs
         self.method = method
         self.iir_params = iir_params
+        self.fir_design = fir_design
 
     def fit(self, epochs_data, y):
-        """Filters data
+        """Filter data.
 
         Parameters
         ----------
@@ -565,7 +554,7 @@ class FilterEstimator(TransformerMixin):
         return self
 
     def transform(self, epochs_data, y=None):
-        """Filters data
+        """Filter data.
 
         Parameters
         ----------
@@ -583,57 +572,17 @@ class FilterEstimator(TransformerMixin):
         if not isinstance(epochs_data, np.ndarray):
             raise ValueError("epochs_data should be of type ndarray (got %s)."
                              % type(epochs_data))
-
         epochs_data = np.atleast_3d(epochs_data)
-
-        if self.l_freq is None and self.h_freq is not None:
-            epochs_data = \
-                low_pass_filter(epochs_data, self.info['sfreq'], self.h_freq,
-                                filter_length=self.filter_length,
-                                trans_bandwidth=self.l_trans_bandwidth,
-                                method=self.method, iir_params=self.iir_params,
-                                picks=self.picks, n_jobs=self.n_jobs,
-                                copy=False, verbose=False)
-
-        if self.l_freq is not None and self.h_freq is None:
-            epochs_data = \
-                high_pass_filter(epochs_data, self.info['sfreq'], self.l_freq,
-                                 filter_length=self.filter_length,
-                                 trans_bandwidth=self.h_trans_bandwidth,
-                                 method=self.method,
-                                 iir_params=self.iir_params,
-                                 picks=self.picks, n_jobs=self.n_jobs,
-                                 copy=False, verbose=False)
-
-        if self.l_freq is not None and self.h_freq is not None:
-            if self.l_freq < self.h_freq:
-                epochs_data = \
-                    band_pass_filter(epochs_data, self.info['sfreq'],
-                                     self.l_freq, self.h_freq,
-                                     filter_length=self.filter_length,
-                                     l_trans_bandwidth=self.l_trans_bandwidth,
-                                     h_trans_bandwidth=self.h_trans_bandwidth,
-                                     method=self.method,
-                                     iir_params=self.iir_params,
-                                     picks=self.picks, n_jobs=self.n_jobs,
-                                     copy=False, verbose=False)
-            else:
-                epochs_data = \
-                    band_stop_filter(epochs_data, self.info['sfreq'],
-                                     self.h_freq, self.l_freq,
-                                     filter_length=self.filter_length,
-                                     l_trans_bandwidth=self.h_trans_bandwidth,
-                                     h_trans_bandwidth=self.l_trans_bandwidth,
-                                     method=self.method,
-                                     iir_params=self.iir_params,
-                                     picks=self.picks, n_jobs=self.n_jobs,
-                                     copy=False, verbose=False)
-        return epochs_data
+        return filter_data(
+            epochs_data, self.info['sfreq'], self.l_freq, self.h_freq,
+            self.picks, self.filter_length, self.l_trans_bandwidth,
+            self.h_trans_bandwidth, method=self.method,
+            iir_params=self.iir_params, n_jobs=self.n_jobs, copy=False,
+            fir_design=self.fir_design, verbose=False)
 
 
 class UnsupervisedSpatialFilter(TransformerMixin, BaseEstimator):
-    """Fit and transform with an unsupervised spatial filtering across time
-    and samples.
+    """Use unsupervised spatial filtering across time and samples.
 
     Parameters
     ----------
@@ -643,7 +592,8 @@ class UnsupervisedSpatialFilter(TransformerMixin, BaseEstimator):
         If True, the estimator is fitted on the average across samples
         (e.g. epochs).
     """
-    def __init__(self, estimator, average=False):
+
+    def __init__(self, estimator, average=False):  # noqa: D102
         # XXX: Use _check_estimator #3381
         for attr in ('fit', 'transform', 'fit_transform'):
             if not hasattr(estimator, attr):
@@ -694,7 +644,7 @@ class UnsupervisedSpatialFilter(TransformerMixin, BaseEstimator):
 
         Returns
         -------
-        X : array, shape (n_trials, n_channels, n_times)
+        X : array, shape (n_epochs, n_channels, n_times)
             The transformed data.
         """
         return self.fit(X).transform(X)
@@ -709,14 +659,47 @@ class UnsupervisedSpatialFilter(TransformerMixin, BaseEstimator):
 
         Returns
         -------
-        X : array, shape (n_trials, n_channels, n_times)
+        X : array, shape (n_epochs, n_channels, n_times)
+            The transformed data.
+        """
+        return self._apply_method(X, 'transform')
+
+    def inverse_transform(self, X):
+        """Inverse transform the data to its original space.
+
+        Parameters
+        ----------
+        X : array, shape (n_epochs, n_components, n_times)
+            The data to be inverted.
+
+        Returns
+        -------
+        X : array, shape (n_epochs, n_channels, n_times)
+            The transformed data.
+        """
+        return self._apply_method(X, 'inverse_transform')
+
+    def _apply_method(self, X, method):
+        """Vectorize time samples as trials, apply method and reshape back.
+
+        Parameters
+        ----------
+        X : array, shape (n_epochs, n_dims, n_times)
+            The data to be inverted.
+
+        Returns
+        -------
+        X : array, shape (n_epochs, n_dims, n_times)
             The transformed data.
         """
         n_epochs, n_channels, n_times = X.shape
         # trial as time samples
-        X = np.transpose(X, [1, 0, 2]).reshape([n_channels, n_epochs *
-                                                n_times]).T
-        X = self.estimator.transform(X)
+        X = np.transpose(X, [1, 0, 2])
+        X = np.reshape(X, [n_channels, n_epochs * n_times]).T
+        # apply method
+        method = getattr(self.estimator, method)
+        X = method(X)
+        # put it back to n_epochs, n_dimensions
         X = np.reshape(X.T, [-1, n_epochs, n_times]).transpose([1, 0, 2])
         return X
 
@@ -735,7 +718,7 @@ class TemporalFilter(TransformerMixin):
         - l_freq is not None, h_freq is None: low-pass filter
         - l_freq is None, h_freq is not None: high-pass filter
 
-    See ``mne.filter.filter_data``.
+    See :func:`mne.filter.filter_data`.
 
     Parameters
     ----------
@@ -748,6 +731,7 @@ class TemporalFilter(TransformerMixin):
         Sampling frequency in Hz.
     filter_length : str | int, defaults to 'auto'
         Length of the FIR filter to use (if applicable):
+
             * int: specified length in samples.
             * 'auto' (default in 0.14): the filter length is chosen based
               on the size of the transition regions (7 times the reciprocal
@@ -757,17 +741,22 @@ class TemporalFilter(TransformerMixin):
               converted to that number of samples if ``phase="zero"``, or
               the shortest power-of-two length at least that duration for
               ``phase="zero-double"``.
-    l_trans_bandwidth : float | str, defaults to 'auto'
+
+    l_trans_bandwidth : float | str
         Width of the transition band at the low cut-off frequency in Hz
         (high pass or cutoff 1 in bandpass). Can be "auto"
         (default in 0.14) to use a multiple of ``l_freq``::
+
             min(max(l_freq * 0.25, 2), l_freq)
+
         Only used for ``method='fir'``.
-    h_trans_bandwidth : float | str, defaults to 'auto'
+    h_trans_bandwidth : float | str
         Width of the transition band at the high cut-off frequency in Hz
         (low pass or cutoff 2 in bandpass). Can be "auto"
         (default in 0.14) to use a multiple of ``h_freq``::
+
             min(max(h_freq * 0.25, 2.), info['sfreq'] / 2. - h_freq)
+
         Only used for ``method='fir'``.
     n_jobs : int | str, defaults to 1
         Number of jobs to run in parallel. Can be 'cuda' if scikits.cuda
@@ -782,23 +771,32 @@ class TemporalFilter(TransformerMixin):
     fir_window : str, defaults to 'hamming'
         The window to use in FIR design, can be "hamming", "hann",
         or "blackman".
+    fir_design : str
+        Can be "firwin" (default in 0.16) to use
+        :func:`scipy.signal.firwin`, or "firwin2" (default in 0.15 and
+        before) to use :func:`scipy.signal.firwin2`. "firwin" uses a
+        time-domain design technique that generally gives improved
+        attenuation using fewer samples than "firwin2".
+
+        ..versionadded:: 0.15
+
     verbose : bool, str, int, or None, defaults to None
-        If not None, override default verbose level (see mne.verbose).
-        Defaults to self.verbose.
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more). Defaults to
+        self.verbose.
 
     See Also
     --------
     FilterEstimator
     Vectorizer
-    mne.filter.band_pass_filter
-    mne.filter.band_stop_filter
-    mne.filter.low_pass_filter
-    mne.filter.high_pass_filter
+    mne.filter.filter_data
     """
+
     def __init__(self, l_freq=None, h_freq=None, sfreq=1.0,
                  filter_length='auto', l_trans_bandwidth='auto',
                  h_trans_bandwidth='auto', n_jobs=1, method='fir',
-                 iir_params=None, fir_window='hamming', verbose=None):
+                 iir_params=None, fir_window='hamming', fir_design=None,
+                 verbose=None):  # noqa: D102
         self.l_freq = l_freq
         self.h_freq = h_freq
         self.sfreq = sfreq
@@ -809,6 +807,7 @@ class TemporalFilter(TransformerMixin):
         self.method = method
         self.iir_params = iir_params
         self.fir_window = fir_window
+        self.fir_design = fir_design
         self.verbose = verbose
 
         if not isinstance(self.n_jobs, int) and self.n_jobs == 'cuda':
@@ -816,7 +815,7 @@ class TemporalFilter(TransformerMixin):
                              % type(self.n_jobs))
 
     def fit(self, X, y=None):
-        """Does nothing. For scikit-learn compatibility purposes.
+        """Do nothing (for scikit-learn compatibility purposes).
 
         Parameters
         ----------
@@ -834,7 +833,7 @@ class TemporalFilter(TransformerMixin):
         return self
 
     def transform(self, X):
-        """Filters data along the last dimension.
+        """Filter data along the last dimension.
 
         Parameters
         ----------
@@ -856,18 +855,20 @@ class TemporalFilter(TransformerMixin):
         shape = X.shape
         X = X.reshape(-1, shape[-1])
         (X, self.sfreq, self.l_freq, self.h_freq, self.l_trans_bandwidth,
-         self.h_trans_bandwidth, self.filter_length, _, self.fir_window) = \
+         self.h_trans_bandwidth, self.filter_length, _, self.fir_window,
+         self.fir_design) = \
             _triage_filter_params(X, self.sfreq, self.l_freq, self.h_freq,
                                   self.l_trans_bandwidth,
                                   self.h_trans_bandwidth, self.filter_length,
                                   self.method, phase='zero',
-                                  fir_window=self.fir_window)
+                                  fir_window=self.fir_window,
+                                  fir_design=self.fir_design)
         X = filter_data(X, self.sfreq, self.l_freq, self.h_freq,
                         filter_length=self.filter_length,
                         l_trans_bandwidth=self.l_trans_bandwidth,
                         h_trans_bandwidth=self.h_trans_bandwidth,
                         n_jobs=self.n_jobs, method=self.method,
                         iir_params=self.iir_params, copy=False,
-                        fir_window=self.fir_window,
+                        fir_window=self.fir_window, fir_design=self.fir_design,
                         verbose=self.verbose)
         return X.reshape(shape)
diff --git a/mne/defaults.py b/mne/defaults.py
index fd657aa..872636f 100644
--- a/mne/defaults.py
+++ b/mne/defaults.py
@@ -37,11 +37,31 @@ DEFAULTS = dict(
                      linewidth=0,
                      markeredgewidth=1,
                      markersize=4),
+    coreg=dict(
+        mri_fid_opacity=1.0,
+        dig_fid_opacity=0.3,
+
+        mri_fid_scale=1e-2,
+        dig_fid_scale=3e-2,
+        extra_scale=4e-3,
+        eeg_scale=4e-3, eegp_scale=20e-3, eegp_height=0.1,
+        ecog_scale=5e-3,
+        hpi_scale=15e-3,
+
+        head_color=(0.988, 0.89, 0.74),
+        hpi_color=(1., 0., 1.),
+        extra_color=(1., 1., 1.),
+        eeg_color=(1., 0.596, 0.588), eegp_color=(0.839, 0.15, 0.16),
+        ecog_color=(1., 1., 1.),
+        lpa_color=(1., 0., 0.),
+        nasion_color=(0., 1., 0.),
+        rpa_color=(0., 0., 1.),
+    ),
 )
 
 
 def _handle_default(k, v=None):
-    """Helper to avoid dicts as default keyword arguments
+    """Avoid dicts as default keyword arguments.
 
     Use this function instead to resolve default dict values. Example usage::
 
diff --git a/mne/dipole.py b/mne/dipole.py
index 7abe161..1252aa3 100644
--- a/mne/dipole.py
+++ b/mne/dipole.py
@@ -1,3 +1,6 @@
+# -*- coding: utf-8 -*-
+"""Single-dipole functions and classes."""
+
 # Authors: Alexandre Gramfort <alexandre.gramfort at telecom-paristech.fr>
 #          Eric Larson <larson.eric.d at gmail.com>
 #
@@ -10,33 +13,32 @@ import re
 import numpy as np
 from scipy import linalg
 
-from .cov import read_cov, _get_whitener_data
+from .cov import read_cov, compute_whitener
 from .io.constants import FIFF
 from .io.pick import pick_types, channel_type
 from .io.proj import make_projector, _needs_eeg_average_ref_proj
 from .bem import _fit_sphere
 from .evoked import _read_evoked, _aspect_rev, _write_evokeds
 from .transforms import (_print_coord_trans, _coord_frame_name,
-                         apply_trans, invert_transform, Transform)
+                         apply_trans, Transform)
 from .viz.evoked import _plot_evoked
-
 from .forward._make_forward import (_get_trans, _setup_bem,
                                     _prep_meg_channels, _prep_eeg_channels)
 from .forward._compute_forward import (_compute_forwards_meeg,
                                        _prep_field_computation)
 
 from .externals.six import string_types
-from .surface import (transform_surface_to, _normalize_vectors,
-                      _get_ico_surface, _compute_nearest)
+from .surface import transform_surface_to, _compute_nearest
 from .bem import _bem_find_surface, _bem_explain_surface
 from .source_space import (_make_volume_source_space, SourceSpaces,
                            _points_outside_surface)
 from .parallel import parallel_func
-from .utils import logger, verbose, _time_mask, warn, _check_fname, check_fname
+from .utils import (logger, verbose, _time_mask, warn, _check_fname,
+                    check_fname, _pl)
 
 
 class Dipole(object):
-    """Dipole class for sequential dipole fits
+    u"""Dipole class for sequential dipole fits.
 
     .. note:: This class should usually not be instantiated directly,
               instead :func:`mne.read_dipole` should be used.
@@ -53,18 +55,35 @@ class Dipole(object):
     pos : array, shape (n_dipoles, 3)
         The dipoles positions (m) in head coordinates.
     amplitude : array, shape (n_dipoles,)
-        The amplitude of the dipoles (nAm).
+        The amplitude of the dipoles (Am).
     ori : array, shape (n_dipoles, 3)
         The dipole orientations (normalized to unit length).
     gof : array, shape (n_dipoles,)
         The goodness of fit.
     name : str | None
         Name of the dipole.
+    conf : dict
+        Confidence limits in dipole orientation for "vol" in m^3 (volume),
+        "depth" in m (along the depth axis), "long" in m (longitudinal axis),
+        "trans" in m (transverse axis), "qlong" in Am, and "qtrans" in Am
+        (currents). The current confidence limit in the depth direction is
+        assumed to be zero (although it can be non-zero when a BEM is used).
+
+        .. versionadded:: 0.15
+    khi2 : array, shape (n_dipoles,)
+        The χ^2 values for the fits.
+
+        .. versionadded:: 0.15
+    nfree : array, shape (n_dipoles,)
+        The number of free parameters for each fit.
+
+        .. versionadded:: 0.15
 
     See Also
     --------
-    read_dipole
+    fit_dipole
     DipoleFixed
+    read_dipole
 
     Notes
     -----
@@ -72,47 +91,77 @@ class Dipole(object):
     changes as a function of time. For fixed dipole fits, where the
     position is fixed as a function of time, use :class:`mne.DipoleFixed`.
     """
-    def __init__(self, times, pos, amplitude, ori, gof, name=None):
+
+    def __init__(self, times, pos, amplitude, ori, gof,
+                 name=None, conf=None, khi2=None, nfree=None):  # noqa: D102
         self.times = np.array(times)
         self.pos = np.array(pos)
         self.amplitude = np.array(amplitude)
         self.ori = np.array(ori)
         self.gof = np.array(gof)
         self.name = name
+        self.conf = deepcopy(conf) if conf is not None else dict()
+        self.khi2 = np.array(khi2) if khi2 is not None else None
+        self.nfree = np.array(nfree) if nfree is not None else None
 
-    def __repr__(self):
+    def __repr__(self):  # noqa: D105
         s = "n_times : %s" % len(self.times)
-        s += ", tmin : %s" % np.min(self.times)
-        s += ", tmax : %s" % np.max(self.times)
+        s += ", tmin : %0.3f" % np.min(self.times)
+        s += ", tmax : %0.3f" % np.max(self.times)
         return "<Dipole  |  %s>" % s
 
     def save(self, fname):
-        """Save dipole in a .dip file
+        """Save dipole in a .dip file.
 
         Parameters
         ----------
         fname : str
             The name of the .dip file.
         """
-        fmt = "  %7.1f %7.1f %8.2f %8.2f %8.2f %8.3f %8.3f %8.3f %8.3f %6.1f"
+        # obligatory fields
+        fmt = '  %7.1f %7.1f %8.2f %8.2f %8.2f %8.3f %8.3f %8.3f %8.3f %6.2f'
+        header = ('#   begin     end   X (mm)   Y (mm)   Z (mm)'
+                  '   Q(nAm)  Qx(nAm)  Qy(nAm)  Qz(nAm)    g/%')
+        t = self.times[:, np.newaxis] * 1000.
+        gof = self.gof[:, np.newaxis]
+        amp = 1e9 * self.amplitude[:, np.newaxis]
+        out = (t, t, self.pos / 1e-3, amp, self.ori * amp, gof)
+
+        # optional fields
+        fmts = dict(khi2=('    khi^2', ' %8.1f', 1.),
+                    nfree=('  free', ' %5d', 1),
+                    vol=('  vol/mm^3', ' %9.3f', 1e9),
+                    depth=('  depth/mm', ' %9.3f', 1e3),
+                    long=('  long/mm', ' %8.3f', 1e3),
+                    trans=('  trans/mm', ' %9.3f', 1e3),
+                    qlong=('  Qlong/nAm', ' %10.3f', 1e9),
+                    qtrans=('  Qtrans/nAm', ' %11.3f', 1e9),
+                    )
+        for key in ('khi2', 'nfree'):
+            data = getattr(self, key)
+            if data is not None:
+                header += fmts[key][0]
+                fmt += fmts[key][1]
+                out += (data[:, np.newaxis] * fmts[key][2],)
+        for key in ('vol', 'depth', 'long', 'trans', 'qlong', 'qtrans'):
+            data = self.conf.get(key)
+            if data is not None:
+                header += fmts[key][0]
+                fmt += fmts[key][1]
+                out += (data[:, np.newaxis] * fmts[key][2],)
+        out = np.concatenate(out, axis=-1)
+
         # NB CoordinateSystem is hard-coded as Head here
         with open(fname, 'wb') as fid:
             fid.write('# CoordinateSystem "Head"\n'.encode('utf-8'))
-            fid.write('#   begin     end   X (mm)   Y (mm)   Z (mm)'
-                      '   Q(nAm)  Qx(nAm)  Qy(nAm)  Qz(nAm)    g/%\n'
-                      .encode('utf-8'))
-            t = self.times[:, np.newaxis] * 1000.
-            gof = self.gof[:, np.newaxis]
-            amp = 1e9 * self.amplitude[:, np.newaxis]
-            out = np.concatenate((t, t, self.pos / 1e-3, amp,
-                                  self.ori * amp, gof), axis=-1)
+            fid.write((header + '\n').encode('utf-8'))
             np.savetxt(fid, out, fmt=fmt)
             if self.name is not None:
                 fid.write(('## Name "%s dipoles" Style "Dipoles"'
                            % self.name).encode('utf-8'))
 
     def crop(self, tmin=None, tmax=None):
-        """Crop data to a given time interval
+        """Crop data to a given time interval.
 
         Parameters
         ----------
@@ -120,16 +169,26 @@ class Dipole(object):
             Start time of selection in seconds.
         tmax : float | None
             End time of selection in seconds.
+
+        Returns
+        -------
+        self : instance of Dipole
+            The cropped intance.
         """
         sfreq = None
         if len(self.times) > 1:
             sfreq = 1. / np.median(np.diff(self.times))
         mask = _time_mask(self.times, tmin, tmax, sfreq=sfreq)
-        for attr in ('times', 'pos', 'gof', 'amplitude', 'ori'):
-            setattr(self, attr, getattr(self, attr)[mask])
+        for attr in ('times', 'pos', 'gof', 'amplitude', 'ori',
+                     'khi2', 'nfree'):
+            if getattr(self, attr) is not None:
+                setattr(self, attr, getattr(self, attr)[mask])
+        for key in self.conf.keys():
+            self.conf[key] = self.conf[key][mask]
+        return self
 
     def copy(self):
-        """Copy the Dipoles object
+        """Copy the Dipoles object.
 
         Returns
         -------
@@ -140,11 +199,10 @@ class Dipole(object):
 
     @verbose
     def plot_locations(self, trans, subject, subjects_dir=None,
-                       bgcolor=(1, 1, 1), opacity=0.3,
-                       brain_color=(1, 1, 0), fig_name=None,
-                       fig_size=(600, 600), mode='cone',
-                       scale_factor=0.1e-1, colors=None, verbose=None):
-        """Plot dipole locations as arrows
+                       mode='orthoview', coord_frame='mri', idx='gof',
+                       show_all=True, ax=None, block=False, show=True,
+                       verbose=None):
+        """Plot dipole locations in 3d.
 
         Parameters
         ----------
@@ -157,43 +215,75 @@ class Dipole(object):
             The path to the freesurfer subjects reconstructions.
             It corresponds to Freesurfer environment variable SUBJECTS_DIR.
             The default is None.
-        bgcolor : tuple of length 3
-            Background color in 3D.
-        opacity : float in [0, 1]
-            Opacity of brain mesh.
-        brain_color : tuple of length 3
-            Brain color.
-        fig_name : tuple of length 2
-            Mayavi figure name.
-        fig_size : tuple of length 2
-            Mayavi figure size.
         mode : str
-            Should be ``'cone'`` or ``'sphere'`` to specify how the
-            dipoles should be shown.
-        scale_factor : float
-            The scaling applied to amplitudes for the plot.
-        colors: list of colors | None
-            Color to plot with each dipole. If None defaults colors are used.
+            Currently only ``'orthoview'`` is supported.
+
+            .. versionadded:: 0.14.0
+        coord_frame : str
+            Coordinate frame to use, 'head' or 'mri'. Defaults to 'mri'.
+
+            .. versionadded:: 0.14.0
+        idx : int | 'gof' | 'amplitude'
+            Index of the initially plotted dipole. Can also be 'gof' to plot
+            the dipole with highest goodness of fit value or 'amplitude' to
+            plot the dipole with the highest amplitude. The dipoles can also be
+            browsed through using up/down arrow keys or mouse scroll. Defaults
+            to 'gof'. Only used if mode equals 'orthoview'.
+
+            .. versionadded:: 0.14.0
+        show_all : bool
+            Whether to always plot all the dipoles. If True (default), the
+            active dipole is plotted as a red dot and it's location determines
+            the shown MRI slices. The the non-active dipoles are plotted as
+            small blue dots. If False, only the active dipole is plotted.
+            Only used if mode equals 'orthoview'.
+
+            .. versionadded:: 0.14.0
+        ax : instance of matplotlib Axes3D | None
+            Axes to plot into. If None (default), axes will be created.
+            Only used if mode equals 'orthoview'.
+
+            .. versionadded:: 0.14.0
+        block : bool
+            Whether to halt program execution until the figure is closed.
+            Defaults to False. Only used if mode equals 'orthoview'.
+
+            .. versionadded:: 0.14.0
+        show : bool
+            Show figure if True. Defaults to True.
+            Only used if mode equals 'orthoview'.
+
+            .. versionadded:: 0.14.0
         verbose : bool, str, int, or None
-            If not None, override default verbose level (see mne.verbose).
+            If not None, override default verbose level (see
+            :func:`mne.verbose` and :ref:`Logging documentation <tut_logging>`
+            for more).
 
         Returns
         -------
-        fig : instance of mlab.Figure
-            The mayavi figure.
+        fig : instance of mlab.Figure or matplotlib Figure
+            The mayavi figure or matplotlib Figure.
+
+        Notes
+        -----
+        .. versionadded:: 0.9.0
         """
         from .viz import plot_dipole_locations
-        dipoles = []
-        for t in self.times:
-            dipoles.append(self.copy())
-            dipoles[-1].crop(t, t)
+        dipoles = self
+        if mode in [None, 'cone', 'sphere']:  # support old behavior
+            dipoles = []
+            for t in self.times:
+                dipoles.append(self.copy())
+                dipoles[-1].crop(t, t)
+        elif mode != 'orthoview':
+            raise ValueError("mode must be 'cone', 'sphere' or 'orthoview'. "
+                             "Got %s." % mode)
         return plot_dipole_locations(
-            dipoles, trans, subject, subjects_dir, bgcolor, opacity,
-            brain_color, fig_name, fig_size, mode, scale_factor,
-            colors)
+            dipoles, trans, subject, subjects_dir, mode, coord_frame, idx,
+            show_all, ax, block, show)
 
     def plot_amplitudes(self, color='k', show=True):
-        """Plot the dipole amplitudes as a function of time
+        """Plot the dipole amplitudes as a function of time.
 
         Parameters
         ----------
@@ -211,7 +301,7 @@ class Dipole(object):
         return plot_dipole_amplitudes([self], [color], show)
 
     def __getitem__(self, item):
-        """Get a time slice
+        """Get a time slice.
 
         Parameters
         ----------
@@ -232,12 +322,18 @@ class Dipole(object):
         selected_ori = self.ori[item, :].copy()
         selected_gof = self.gof[item].copy()
         selected_name = self.name
+        selected_conf = dict()
+        for key in self.conf.keys():
+            selected_conf[key] = self.conf[key][item]
+        selected_khi2 = self.khi2[item] if self.khi2 is not None else None
+        selected_nfree = self.nfree[item] if self.nfree is not None else None
         return Dipole(
             selected_times, selected_pos, selected_amplitude, selected_ori,
-            selected_gof, selected_name)
+            selected_gof, selected_name, selected_conf, selected_khi2,
+            selected_nfree)
 
     def __len__(self):
-        """The number of dipoles
+        """Return the number of dipoles.
 
         Returns
         -------
@@ -256,9 +352,8 @@ class Dipole(object):
 
 
 def _read_dipole_fixed(fname):
-    """Helper to read a fixed dipole FIF file"""
+    """Read a fixed dipole FIF file."""
     logger.info('Reading %s ...' % fname)
-    _check_fname(fname, overwrite=True, must_exist=True)
     info, nave, aspect_kind, first, last, comment, times, data = \
         _read_evoked(fname)
     return DipoleFixed(info, data, times, nave, aspect_kind, first, last,
@@ -266,7 +361,7 @@ def _read_dipole_fixed(fname):
 
 
 class DipoleFixed(object):
-    """Dipole class for fixed-position dipole fits
+    """Dipole class for fixed-position dipole fits.
 
     .. note:: This class should usually not be instantiated directly,
               instead :func:`mne.read_dipole` should be used.
@@ -290,12 +385,14 @@ class DipoleFixed(object):
     comment : str
         The dipole comment.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     See Also
     --------
     read_dipole
     Dipole
+    fit_dipole
 
     Notes
     -----
@@ -305,9 +402,10 @@ class DipoleFixed(object):
 
     .. versionadded:: 0.12
     """
+
     @verbose
     def __init__(self, info, data, times, nave, aspect_kind, first, last,
-                 comment, verbose=None):
+                 comment, verbose=None):  # noqa: D102
         self.info = info
         self.nave = nave
         self._aspect_kind = aspect_kind
@@ -319,13 +417,20 @@ class DipoleFixed(object):
         self.data = data
         self.verbose = verbose
 
+    def __repr__(self):  # noqa: D105
+        s = "n_times : %s" % len(self.times)
+        s += ", tmin : %s" % np.min(self.times)
+        s += ", tmax : %s" % np.max(self.times)
+        return "<DipoleFixed  |  %s>" % s
+
     @property
     def ch_names(self):
+        """Channel names."""
         return self.info['ch_names']
 
     @verbose
     def save(self, fname, verbose=None):
-        """Save dipole in a .fif file
+        """Save dipole in a .fif file.
 
         Parameters
         ----------
@@ -334,14 +439,16 @@ class DipoleFixed(object):
             ``'.fif.gz'`` to make it explicit that the file contains
             dipole information in FIF format.
         verbose : bool, str, int, or None
-            If not None, override default verbose level (see mne.verbose).
+            If not None, override default verbose level (see
+            :func:`mne.verbose` and :ref:`Logging documentation <tut_logging>`
+            for more).
         """
         check_fname(fname, 'DipoleFixed', ('-dip.fif', '-dip.fif.gz'),
                     ('.fif', '.fif.gz'))
         _write_evokeds(fname, self, check=False)
 
     def plot(self, show=True):
-        """Plot dipole data
+        """Plot dipole data.
 
         Parameters
         ----------
@@ -364,14 +471,15 @@ class DipoleFixed(object):
 # IO
 @verbose
 def read_dipole(fname, verbose=None):
-    """Read .dip file from Neuromag/xfit or MNE
+    """Read .dip file from Neuromag/xfit or MNE.
 
     Parameters
     ----------
     fname : str
         The name of the .dip or .fif file.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -380,10 +488,11 @@ def read_dipole(fname, verbose=None):
 
     See Also
     --------
-    mne.Dipole
-    mne.DipoleFixed
+    Dipole
+    DipoleFixed
+    fit_dipole
     """
-    _check_fname(fname, overwrite=True, must_exist=True)
+    _check_fname(fname, overwrite='read', must_exist=True)
     if fname.endswith('.fif') or fname.endswith('.fif.gz'):
         return _read_dipole_fixed(fname)
     else:
@@ -418,23 +527,31 @@ def _read_dipole_text(fname):
     # actually parse the fields
     def_line = def_line.lstrip('%').lstrip('#').strip()
     # MNE writes it out differently than Elekta, let's standardize them...
-    fields = re.sub('([X|Y|Z] )\(mm\)',  # "X (mm)", etc.
+    fields = re.sub(r'([X|Y|Z] )\(mm\)',  # "X (mm)", etc.
                     lambda match: match.group(1).strip() + '/mm', def_line)
-    fields = re.sub('\((.*?)\)',  # "Q(nAm)", etc.
+    fields = re.sub(r'\((.*?)\)',  # "Q(nAm)", etc.
                     lambda match: '/' + match.group(1), fields)
     fields = re.sub('(begin|end) ',  # "begin" and "end" with no units
                     lambda match: match.group(1) + '/ms', fields)
     fields = fields.lower().split()
-    used_fields = ('begin/ms',
-                   'x/mm', 'y/mm', 'z/mm',
-                   'q/nam',
-                   'qx/nam', 'qy/nam', 'qz/nam',
-                   'g/%')
-    missing_fields = sorted(set(used_fields) - set(fields))
+    required_fields = ('begin/ms',
+                       'x/mm', 'y/mm', 'z/mm',
+                       'q/nam', 'qx/nam', 'qy/nam', 'qz/nam',
+                       'g/%')
+    optional_fields = ('khi^2', 'free',  # standard ones
+                       # now the confidence fields (up to 5!)
+                       'vol/mm^3', 'depth/mm', 'long/mm', 'trans/mm',
+                       'qlong/nam', 'qtrans/nam')
+    conf_scales = [1e-9, 1e-3, 1e-3, 1e-3, 1e-9, 1e-9]
+    missing_fields = sorted(set(required_fields) - set(fields))
     if len(missing_fields) > 0:
         raise RuntimeError('Could not find necessary fields in header: %s'
                            % (missing_fields,))
-    ignored_fields = sorted(set(fields) - set(used_fields) - set(['end/ms']))
+    handled_fields = set(required_fields) | set(optional_fields)
+    assert len(handled_fields) == len(required_fields) + len(optional_fields)
+    ignored_fields = sorted(set(fields) -
+                            set(handled_fields) -
+                            set(['end/ms']))
     if len(ignored_fields) > 0:
         warn('Ignoring extra fields in dipole file: %s' % (ignored_fields,))
     if len(fields) != data.shape[1]:
@@ -450,8 +567,8 @@ def _read_dipole_text(fname):
                  'to store time values')
 
     # Find the correct column in our data array, then scale to proper units
-    idx = [fields.index(field) for field in used_fields]
-    assert len(idx) == 9
+    idx = [fields.index(field) for field in required_fields]
+    assert len(idx) >= 9
     times = data[:, idx[0]] / 1000.
     pos = 1e-3 * data[:, idx[1:4]]  # put data in meters
     amplitude = data[:, idx[4]]
@@ -460,14 +577,24 @@ def _read_dipole_text(fname):
     norm[norm == 0] = 1
     ori = data[:, idx[5:8]] / norm[:, np.newaxis]
     gof = data[:, idx[8]]
-    return Dipole(times, pos, amplitude, ori, gof, name)
+    # Deal with optional fields
+    optional = [None] * 2
+    for fi, field in enumerate(optional_fields[:2]):
+        if field in fields:
+            optional[fi] = data[:, fields.index(field)]
+    khi2, nfree = optional
+    conf = dict()
+    for field, scale in zip(optional_fields[2:], conf_scales):  # confidence
+        if field in fields:
+            conf[field.split('/')[0]] = scale * data[:, fields.index(field)]
+    return Dipole(times, pos, amplitude, ori, gof, name, conf, khi2, nfree)
 
 
 # #############################################################################
 # Fitting
 
 def _dipole_forwards(fwd_data, whitener, rr, n_jobs=1):
-    """Compute the forward solution and do other nice stuff"""
+    """Compute the forward solution and do other nice stuff."""
     B = _compute_forwards_meeg(rr, fwd_data, n_jobs, verbose=False)
     B = np.concatenate(B, axis=1)
     B_orig = B.copy()
@@ -484,24 +611,19 @@ def _dipole_forwards(fwd_data, whitener, rr, n_jobs=1):
     return B, B_orig, scales
 
 
-def _make_guesses(surf_or_rad, r0, grid, exclude, mindist, n_jobs):
-    """Make a guess space inside a sphere or BEM surface"""
-    if isinstance(surf_or_rad, dict):
-        surf = surf_or_rad
+def _make_guesses(surf, grid, exclude, mindist, n_jobs):
+    """Make a guess space inside a sphere or BEM surface."""
+    if 'rr' in surf:
         logger.info('Guess surface (%s) is in %s coordinates'
                     % (_bem_explain_surface(surf['id']),
                        _coord_frame_name(surf['coord_frame'])))
     else:
-        radius = surf_or_rad[0]
         logger.info('Making a spherical guess space with radius %7.1f mm...'
-                    % (1000 * radius))
-        surf = _get_ico_surface(3)
-        _normalize_vectors(surf['rr'])
-        surf['rr'] *= radius
-        surf['rr'] += r0
+                    % (1000 * surf['R']))
     logger.info('Filtering (grid = %6.f mm)...' % (1000 * grid))
     src = _make_volume_source_space(surf, grid, exclude, 1000 * mindist,
                                     do_neighbors=False, n_jobs=n_jobs)
+    assert 'vertno' in src
     # simplify the result to make things easier later
     src = dict(rr=src['rr'][src['vertno']], nn=src['nn'][src['vertno']],
                nuse=src['nuse'], coord_frame=src['coord_frame'],
@@ -510,7 +632,7 @@ def _make_guesses(surf_or_rad, r0, grid, exclude, mindist, n_jobs):
 
 
 def _fit_eval(rd, B, B2, fwd_svd=None, fwd_data=None, whitener=None):
-    """Calculate the residual sum of squares"""
+    """Calculate the residual sum of squares."""
     if fwd_svd is None:
         fwd = _dipole_forwards(fwd_data, whitener, rd[np.newaxis, :])[0]
         uu, sing, vv = linalg.svd(fwd, overwrite_a=True, full_matrices=False)
@@ -522,8 +644,8 @@ def _fit_eval(rd, B, B2, fwd_svd=None, fwd_data=None, whitener=None):
 
 
 def _dipole_gof(uu, sing, vv, B, B2):
-    """Calculate the goodness of fit from the forward SVD"""
-    ncomp = 3 if sing[2] / sing[0] > 0.2 else 2
+    """Calculate the goodness of fit from the forward SVD."""
+    ncomp = 3 if sing[2] / (sing[0] if sing[0] > 0 else 1.) > 0.2 else 2
     one = np.dot(vv[:ncomp], B)
     Bm2 = np.sum(one * one)
     gof = Bm2 / B2
@@ -531,7 +653,7 @@ def _dipole_gof(uu, sing, vv, B, B2):
 
 
 def _fit_Q(fwd_data, whitener, proj_op, B, B2, B_orig, rd, ori=None):
-    """Fit the dipole moment once the location is known"""
+    """Fit the dipole moment once the location is known."""
     if 'fwd' in fwd_data:
         # should be a single precomputed "guess" (i.e., fixed position)
         assert rd is None
@@ -561,33 +683,41 @@ def _fit_Q(fwd_data, whitener, proj_op, B, B2, B_orig, rd, ori=None):
         one = np.dot(fwd / sing, B)
         gof = (one * one)[0] / B2
         Q = ori * (scales[0] * np.sum(one / sing))
+        ncomp = 3
     B_residual = _compute_residual(proj_op, B_orig, fwd_orig, Q)
-    return Q, gof, B_residual
+    return Q, gof, B_residual, ncomp
 
 
 def _compute_residual(proj_op, B_orig, fwd_orig, Q):
-    """Compute the residual"""
+    """Compute the residual."""
     # apply the projector to both elements
     return np.dot(proj_op, B_orig) - np.dot(np.dot(Q, fwd_orig), proj_op.T)
 
 
 def _fit_dipoles(fun, min_dist_to_inner_skull, data, times, guess_rrs,
-                 guess_data, fwd_data, whitener, proj_op, ori, n_jobs):
-    """Fit a single dipole to the given whitened, projected data"""
+                 guess_data, fwd_data, whitener, proj_op, ori, n_jobs, rank):
+    """Fit a single dipole to the given whitened, projected data."""
     from scipy.optimize import fmin_cobyla
     parallel, p_fun, _ = parallel_func(fun, n_jobs)
     # parallel over time points
     res = parallel(p_fun(min_dist_to_inner_skull, B, t, guess_rrs,
                          guess_data, fwd_data, whitener, proj_op,
-                         fmin_cobyla, ori)
+                         fmin_cobyla, ori, rank)
                    for B, t in zip(data.T, times))
     pos = np.array([r[0] for r in res])
     amp = np.array([r[1] for r in res])
     ori = np.array([r[2] for r in res])
     gof = np.array([r[3] for r in res]) * 100  # convert to percentage
-    residual = np.array([r[4] for r in res]).T
+    conf = None
+    if res[0][4] is not None:
+        conf = np.array([r[4] for r in res])
+        keys = ['vol', 'depth', 'long', 'trans', 'qlong', 'qtrans']
+        conf = {key: conf[:, ki] for ki, key in enumerate(keys)}
+    khi2 = np.array([r[5] for r in res])
+    nfree = np.array([r[6] for r in res])
+    residual = np.array([r[7] for r in res]).T
 
-    return pos, amp, ori, gof, residual
+    return pos, amp, ori, gof, conf, khi2, nfree, residual
 
 
 '''Simplex code in case we ever want/need it for testing
@@ -683,8 +813,70 @@ def _simplex_minimize(p, ftol, stol, fun, max_eval=1000):
 '''
 
 
+def _fit_confidence(rd, Q, ori, whitener, fwd_data, proj):
+    # As describedd in the Xfit manual, confidence intervals can be calculated
+    # by examining a linearization of model at the best-fitting location,
+    # i.e. taking the Jacobian and using the whitener:
+    #
+    #     J = [∂b/∂x ∂b/∂y ∂b/∂z ∂b/∂Qx ∂b/∂Qy ∂b/∂Qz]
+    #     C = (J.T C^-1 J)^-1
+    #
+    # And then the confidence interval is the diagonal of C, scaled by 1.96
+    # (for 95% confidence).
+    direction = np.empty((3, 3))
+    # The coordinate system has the x axis aligned with the dipole orientation,
+    direction[0] = ori
+    # the z axis through the origin of the sphere model
+    rvec = rd - fwd_data['inner_skull']['r0']
+    direction[2] = rvec - ori * np.dot(ori, rvec)  # orthogonalize
+    direction[2] /= np.linalg.norm(direction[2])
+    # and the y axis perpendical with these forming a right-handed system.
+    direction[1] = np.cross(direction[2], direction[0])
+    assert np.allclose(np.dot(direction, direction.T), np.eye(3))
+    # Get spatial deltas in dipole coordinate directions
+    deltas = (-1e-4, 1e-4)
+    J = np.empty((whitener.shape[0], 6))
+    for ii in range(3):
+        fwds = []
+        for delta in deltas:
+            this_r = rd[np.newaxis] + delta * direction[ii]
+            fwds.append(
+                np.dot(Q, _dipole_forwards(fwd_data, whitener, this_r)[0]))
+        J[:, ii] = np.diff(fwds, axis=0)[0] / np.diff(deltas)[0]
+    # Get current (Q) deltas in the dipole directions
+    deltas = np.array([-0.01, 0.01]) * np.linalg.norm(Q)
+    this_fwd = _dipole_forwards(fwd_data, whitener, rd[np.newaxis])[0]
+    for ii in range(3):
+        fwds = []
+        for delta in deltas:
+            fwds.append(np.dot(Q + delta * direction[ii], this_fwd))
+        J[:, ii + 3] = np.diff(fwds, axis=0)[0] / np.diff(deltas)[0]
+    # J is already whitened, so we don't need to do np.dot(whitener, J).
+    # However, the units in the Jacobian are potentially quite different,
+    # so we need to do some normalization during inversion, then revert.
+    direction_norm = np.linalg.norm(J[:, :3])
+    Q_norm = np.linalg.norm(J[:, 3:5])  # omit possible zero Z
+    norm = np.array([direction_norm] * 3 + [Q_norm] * 3)
+    J /= norm
+    J = np.dot(J.T, J)
+    C = linalg.pinvh(J, rcond=1e-14)
+    C /= norm
+    C /= norm[:, np.newaxis]
+    conf = 1.96 * np.sqrt(np.diag(C))
+    # The confidence volume of the dipole location is obtained from by
+    # taking the eigenvalues of the upper left submatrix and computing
+    # v = 4π/3 √(c^3 λ1 λ2 λ3) with c = 7.81, or:
+    vol_conf = 4 * np.pi / 3. * np.sqrt(
+        476.379541 * np.prod(linalg.eigh(C[:3, :3], eigvals_only=True)))
+    conf = np.concatenate([conf, [vol_conf]])
+    # Now we reorder and subselect the proper columns:
+    # vol, depth, long, trans, Qlong, Qtrans (discard Qdepth, assumed zero)
+    conf = conf[[6, 2, 0, 1, 3, 4]]
+    return conf
+
+
 def _surface_constraint(rd, surf, min_dist_to_inner_skull):
-    """Surface fitting constraint"""
+    """Surface fitting constraint."""
     dist = _compute_nearest(surf['rr'], rd[np.newaxis, :],
                             return_dists=True)[1][0]
     if _points_outside_surface(rd[np.newaxis, :], surf, 1)[0]:
@@ -698,27 +890,26 @@ def _surface_constraint(rd, surf, min_dist_to_inner_skull):
 
 
 def _sphere_constraint(rd, r0, R_adj):
-    """Sphere fitting constraint"""
+    """Sphere fitting constraint."""
     return R_adj - np.sqrt(np.sum((rd - r0) ** 2))
 
 
 def _fit_dipole(min_dist_to_inner_skull, B_orig, t, guess_rrs,
                 guess_data, fwd_data, whitener, proj_op,
-                fmin_cobyla, ori):
-    """Fit a single bit of data"""
+                fmin_cobyla, ori, rank):
+    """Fit a single bit of data."""
     B = np.dot(whitener, B_orig)
 
     # make constraint function to keep the solver within the inner skull
-    if isinstance(fwd_data['inner_skull'], dict):  # bem
+    if 'rr' in fwd_data['inner_skull']:  # bem
         surf = fwd_data['inner_skull']
         constraint = partial(_surface_constraint, surf=surf,
                              min_dist_to_inner_skull=min_dist_to_inner_skull)
     else:  # sphere
         surf = None
-        R, r0 = fwd_data['inner_skull']
-        constraint = partial(_sphere_constraint, r0=r0,
-                             R_adj=R - min_dist_to_inner_skull)
-        del R, r0
+        constraint = partial(
+            _sphere_constraint, r0=fwd_data['inner_skull']['r0'],
+            R_adj=fwd_data['inner_skull']['R'] - min_dist_to_inner_skull)
 
     # Find a good starting point (find_best_guess in C)
     B2 = np.dot(B, B)
@@ -744,12 +935,16 @@ def _fit_dipole(min_dist_to_inner_skull, B_orig, t, guess_rrs,
     # rd_final = simplex[0]
 
     # Compute the dipole moment at the final point
-    Q, gof, residual = _fit_Q(fwd_data, whitener, proj_op, B, B2, B_orig,
-                              rd_final, ori=ori)
+    Q, gof, residual, n_comp = _fit_Q(
+        fwd_data, whitener, proj_op, B, B2, B_orig, rd_final, ori=ori)
+    khi2 = (1 - gof) * B2
+    nfree = rank - n_comp
     amp = np.sqrt(np.dot(Q, Q))
     norm = 1. if amp == 0. else amp
     ori = Q / norm
 
+    conf = _fit_confidence(rd_final, Q, ori, whitener, fwd_data, proj_op)
+
     msg = '---- Fitted : %7.1f ms' % (1000. * t)
     if surf is not None:
         dist_to_inner_skull = _compute_nearest(
@@ -758,35 +953,39 @@ def _fit_dipole(min_dist_to_inner_skull, B_orig, t, guess_rrs,
                 % (dist_to_inner_skull * 1000.))
 
     logger.info(msg)
-    return rd_final, amp, ori, gof, residual
+    return rd_final, amp, ori, gof, conf, khi2, nfree, residual
 
 
 def _fit_dipole_fixed(min_dist_to_inner_skull, B_orig, t, guess_rrs,
                       guess_data, fwd_data, whitener, proj_op,
-                      fmin_cobyla, ori):
-    """Fit a data using a fixed position"""
+                      fmin_cobyla, ori, rank):
+    """Fit a data using a fixed position."""
     B = np.dot(whitener, B_orig)
     B2 = np.dot(B, B)
     if B2 == 0:
         warn('Zero field found for time %s' % t)
-        return np.zeros(3), 0, np.zeros(3), 0
+        return np.zeros(3), 0, np.zeros(3), 0, np.zeros(6)
     # Compute the dipole moment
     Q, gof, residual = _fit_Q(guess_data, whitener, proj_op, B, B2, B_orig,
-                              rd=None, ori=ori)
+                              rd=None, ori=ori)[:3]
     if ori is None:
         amp = np.sqrt(np.dot(Q, Q))
         norm = 1. if amp == 0. else amp
         ori = Q / norm
     else:
         amp = np.dot(Q, ori)
+    rd_final = guess_rrs[0]
+    # This will be slow, and we don't use it anyway, so omit it for now:
+    # conf = _fit_confidence(rd_final, Q, ori, whitener, fwd_data, proj_op)
+    conf = khi2 = nfree = None
     # No corresponding 'logger' message here because it should go *very* fast
-    return guess_rrs[0], amp, ori, gof, residual
+    return rd_final, amp, ori, gof, conf, khi2, nfree, residual
 
 
 @verbose
 def fit_dipole(evoked, cov, bem, trans=None, min_dist=5., n_jobs=1,
                pos=None, ori=None, verbose=None):
-    """Fit a dipole
+    """Fit a dipole.
 
     Parameters
     ----------
@@ -826,13 +1025,15 @@ def fit_dipole(evoked, cov, bem, trans=None, min_dist=5., n_jobs=1,
         .. versionadded:: 0.12
 
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
     dip : instance of Dipole or DipoleFixed
         The dipole fits. A :class:`mne.DipoleFixed` is returned if
-        ``pos`` and ``ori`` are both not None.
+        ``pos`` and ``ori`` are both not None, otherwise a
+        :class:`mne.Dipole` is returned.
     residual : ndarray, shape (n_meeg_channels, n_times)
         The good M-EEG data channels with the fitted dipolar activity
         removed.
@@ -840,6 +1041,9 @@ def fit_dipole(evoked, cov, bem, trans=None, min_dist=5., n_jobs=1,
     See Also
     --------
     mne.beamformer.rap_music
+    Dipole
+    DipoleFixed
+    read_dipole
 
     Notes
     -----
@@ -860,6 +1064,8 @@ def fit_dipole(evoked, cov, bem, trans=None, min_dist=5., n_jobs=1,
         raise ValueError('pos must be provided if ori is not None')
 
     data = evoked.data
+    if not np.isfinite(data).all():
+        raise ValueError('Evoked data must be finite')
     info = evoked.info
     times = evoked.times.copy()
     comment = evoked.comment
@@ -880,7 +1086,7 @@ def fit_dipole(evoked, cov, bem, trans=None, min_dist=5., n_jobs=1,
         logger.info('MRI transform     : %s' % trans)
         mri_head_t, trans = _get_trans(trans)
     else:
-        mri_head_t = Transform('head', 'mri', np.eye(4))
+        mri_head_t = Transform('head', 'mri')
     bem = _setup_bem(bem, bem_extra, neeg, mri_head_t, verbose=False)
     if not bem['is_sphere']:
         if trans is None:
@@ -891,9 +1097,11 @@ def fit_dipole(evoked, cov, bem, trans=None, min_dist=5., n_jobs=1,
         R, r0 = _fit_sphere(inner_skull['rr'], disp=False)
         # r0 back to head frame for logging
         r0 = apply_trans(mri_head_t['trans'], r0[np.newaxis, :])[0]
+        inner_skull['r0'] = r0
         logger.info('Head origin       : '
                     '%6.1f %6.1f %6.1f mm rad = %6.1f mm.'
                     % (1000 * r0[0], 1000 * r0[1], 1000 * r0[2], 1000 * R))
+        del R, r0
     else:
         r0 = bem['r0']
         if len(bem.get('layers', [])) > 0:
@@ -913,9 +1121,8 @@ def fit_dipole(evoked, cov, bem, trans=None, min_dist=5., n_jobs=1,
         logger.info('Sphere model      : origin at (% 7.2f % 7.2f % 7.2f) mm, '
                     '%s = %6.1f mm'
                     % (1000 * r0[0], 1000 * r0[1], 1000 * r0[2], kind, R))
-        inner_skull = [R, r0]  # NB sphere model defined in head frame
-    r0_mri = apply_trans(invert_transform(mri_head_t)['trans'],
-                         r0[np.newaxis, :])[0]
+        inner_skull = dict(R=R, r0=r0)  # NB sphere model defined in head frame
+        del R, r0
     accurate = False  # can be an option later (shouldn't make big diff)
 
     # Deal with DipoleFixed cases here
@@ -1000,7 +1207,8 @@ def fit_dipole(evoked, cov, bem, trans=None, min_dist=5., n_jobs=1,
     # whitener[nzero, nzero] = 1.0 / np.sqrt(cov['eig'][nzero])
     # whitener = np.dot(whitener, cov['eigvec'])
 
-    whitener = _get_whitener_data(info, cov, picks, verbose=False)
+    whitener, _, rank = compute_whitener(cov, info, picks=picks,
+                                         return_rank=True)
 
     # Proceed to computing the fits (make_guess_data)
     if fixed_position:
@@ -1008,23 +1216,23 @@ def fit_dipole(evoked, cov, bem, trans=None, min_dist=5., n_jobs=1,
         logger.info('Compute forward for dipole location...')
     else:
         logger.info('\n---- Computing the forward solution for the guesses...')
-        guess_src = _make_guesses(inner_skull, r0_mri,
-                                  guess_grid, guess_exclude, guess_mindist,
-                                  n_jobs=n_jobs)[0]
+        guess_src = _make_guesses(inner_skull, guess_grid, guess_exclude,
+                                  guess_mindist, n_jobs=n_jobs)[0]
         # grid coordinates go from mri to head frame
         transform_surface_to(guess_src, 'head', mri_head_t)
         logger.info('Go through all guess source locations...')
 
     # inner_skull goes from mri to head frame
-    if isinstance(inner_skull, dict):
+    if 'rr' in inner_skull:
         transform_surface_to(inner_skull, 'head', mri_head_t)
     if fixed_position:
-        if isinstance(inner_skull, dict):
+        if 'rr' in inner_skull:
             check = _surface_constraint(pos, inner_skull,
                                         min_dist_to_inner_skull)
         else:
-            check = _sphere_constraint(pos, r0,
-                                       R_adj=R - min_dist_to_inner_skull)
+            check = _sphere_constraint(
+                pos, inner_skull['r0'],
+                R_adj=inner_skull['R'] - min_dist_to_inner_skull)
         if check <= 0:
             raise ValueError('fixed position is %0.1fmm outside the inner '
                              'skull boundary' % (-1000 * check,))
@@ -1045,8 +1253,8 @@ def fit_dipole(evoked, cov, bem, trans=None, min_dist=5., n_jobs=1,
     guess_data = dict(fwd=guess_fwd, fwd_svd=guess_fwd_svd,
                       fwd_orig=guess_fwd_orig, scales=guess_fwd_scales)
     del guess_fwd, guess_fwd_svd, guess_fwd_orig, guess_fwd_scales  # destroyed
-    pl = '' if guess_src['nuse'] == 1 else 's'
-    logger.info('[done %d source%s]' % (guess_src['nuse'], pl))
+    logger.info('[done %d source%s]' % (guess_src['nuse'],
+                                        _pl(guess_src['nuse'])))
 
     # Do actual fits
     data = data[picks]
@@ -1055,7 +1263,8 @@ def fit_dipole(evoked, cov, bem, trans=None, min_dist=5., n_jobs=1,
     fun = _fit_dipole_fixed if fixed_position else _fit_dipole
     out = _fit_dipoles(
         fun, min_dist_to_inner_skull, data, times, guess_src['rr'],
-        guess_data, fwd_data, whitener, proj_op, ori, n_jobs)
+        guess_data, fwd_data, whitener, proj_op, ori, n_jobs, rank)
+    assert len(out) == 8
     if fixed_position and ori is not None:
         # DipoleFixed
         data = np.array([out[1], out[3]])
@@ -1083,26 +1292,25 @@ def fit_dipole(evoked, cov, bem, trans=None, min_dist=5., n_jobs=1,
                               evoked._aspect_kind, evoked.first, evoked.last,
                               comment)
     else:
-        dipoles = Dipole(times, out[0], out[1], out[2], out[3], comment)
-    residual = out[4]
+        dipoles = Dipole(times, out[0], out[1], out[2], out[3], comment,
+                         out[4], out[5], out[6])
+    residual = out[-1]
     logger.info('%d time points fitted' % len(dipoles.times))
     return dipoles, residual
 
 
 def get_phantom_dipoles(kind='vectorview'):
-    """Get standard phantom dipole locations and orientations
+    """Get standard phantom dipole locations and orientations.
 
     Parameters
     ----------
     kind : str
-        Get the information for the given system.
+        Get the information for the given system:
 
             ``vectorview`` (default)
               The Neuromag VectorView phantom.
-
-            ``122``
-              The Neuromag-122 phantom. This has the same dipoles
-              as the VectorView phantom, but in a different order.
+            ``otaniemi``
+              The older Neuromag phantom used at Otaniemi.
 
     Returns
     -------
@@ -1110,40 +1318,67 @@ def get_phantom_dipoles(kind='vectorview'):
         The dipole positions.
     ori : ndarray, shape (n_dipoles, 3)
         The dipole orientations.
+
+    Notes
+    -----
+    The Elekta phantoms have a radius of 79.5mm, and HPI coil locations
+    in the XY-plane at the axis extrema (e.g., (79.5, 0), (0, -79.5), ...).
     """
-    _valid_types = ('122', 'vectorview')
+    _valid_types = ('vectorview', 'otaniemi')
     if not isinstance(kind, string_types) or kind not in _valid_types:
         raise ValueError('kind must be one of %s, got %s'
                          % (_valid_types, kind,))
-    if kind in ('122', 'vectorview'):
+    if kind == 'vectorview':
+        # these values were pulled from a scanned image provided by
+        # Elekta folks
         a = np.array([59.7, 48.6, 35.8, 24.8, 37.2, 27.5, 15.8, 7.9])
-        b = np.array([46.1, 41.9, 38.3, 31.5, 13.9, 16.2, 20, 19.3])
+        b = np.array([46.1, 41.9, 38.3, 31.5, 13.9, 16.2, 20.0, 19.3])
         x = np.concatenate((a, [0] * 8, -b, [0] * 8))
         y = np.concatenate(([0] * 8, -a, [0] * 8, b))
-        c = [22.9, 23.5, 25.5, 23.1, 52, 46.4, 41, 33]
-        d = [44.4, 34, 21.6, 12.7, 62.4, 51.5, 39.1, 27.9]
+        c = [22.9, 23.5, 25.5, 23.1, 52.0, 46.4, 41.0, 33.0]
+        d = [44.4, 34.0, 21.6, 12.7, 62.4, 51.5, 39.1, 27.9]
         z = np.concatenate((c, c, d, d))
-        pos = np.vstack((x, y, z)).T / 1000.
-        if kind == 122:
-            reorder = (list(range(8, 16)) + list(range(0, 8)) +
-                       list(range(24, 32) + list(range(16, 24))))
-            pos = pos[reorder]
-        # Locs are always in XZ or YZ, and so are the oris. The oris are
-        # also in the same plane and tangential, so it's easy to determine
-        # the orientation.
-        ori = list()
-        for this_pos in pos:
-            this_ori = np.zeros(3)
-            idx = np.where(this_pos == 0)[0]
-            # assert len(idx) == 1
-            idx = np.setdiff1d(np.arange(3), idx[0])
-            this_ori[idx] = (this_pos[idx][::-1] /
-                             np.linalg.norm(this_pos[idx])) * [1, -1]
-            # Now we have this quality, which we could uncomment to
-            # double-check:
-            # np.testing.assert_allclose(np.dot(this_ori, this_pos) /
-            #                            np.linalg.norm(this_pos), 0,
-            #                            atol=1e-15)
-            ori.append(this_ori)
-        ori = np.array(ori)
+    elif kind == 'otaniemi':
+        # these values were pulled from an Neuromag manual
+        # (NM20456A, 13.7.1999, p.65)
+        a = np.array([56.3, 47.6, 39.0, 30.3])
+        b = np.array([32.5, 27.5, 22.5, 17.5])
+        c = np.zeros(4)
+        x = np.concatenate((a, b, c, c, -a, -b, c, c))
+        y = np.concatenate((c, c, -a, -b, c, c, b, a))
+        z = np.concatenate((b, a, b, a, b, a, a, b))
+    pos = np.vstack((x, y, z)).T / 1000.
+    # Locs are always in XZ or YZ, and so are the oris. The oris are
+    # also in the same plane and tangential, so it's easy to determine
+    # the orientation.
+    ori = list()
+    for this_pos in pos:
+        this_ori = np.zeros(3)
+        idx = np.where(this_pos == 0)[0]
+        # assert len(idx) == 1
+        idx = np.setdiff1d(np.arange(3), idx[0])
+        this_ori[idx] = (this_pos[idx][::-1] /
+                         np.linalg.norm(this_pos[idx])) * [1, -1]
+        # Now we have this quality, which we could uncomment to
+        # double-check:
+        # np.testing.assert_allclose(np.dot(this_ori, this_pos) /
+        #                            np.linalg.norm(this_pos), 0,
+        #                            atol=1e-15)
+        ori.append(this_ori)
+    ori = np.array(ori)
     return pos, ori
+
+
+def _concatenate_dipoles(dipoles):
+    """Concatenate a list of dipoles."""
+    times, pos, amplitude, ori, gof = [], [], [], [], []
+    for dipole in dipoles:
+        times.append(dipole.times)
+        pos.append(dipole.pos)
+        amplitude.append(dipole.amplitude)
+        ori.append(dipole.ori)
+        gof.append(dipole.gof)
+
+    return Dipole(np.concatenate(times), np.concatenate(pos),
+                  np.concatenate(amplitude), np.concatenate(ori),
+                  np.concatenate(gof), name=None)
diff --git a/mne/epochs.py b/mne/epochs.py
index 7ffebdd..9ed0476 100644
--- a/mne/epochs.py
+++ b/mne/epochs.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 
-"""Tools for working with epoched data"""
+"""Tools for working with epoched data."""
 
 # Authors: Alexandre Gramfort <alexandre.gramfort at telecom-paristech.fr>
 #          Matti Hamalainen <msh at nmr.mgh.harvard.edu>
@@ -30,27 +30,26 @@ from .io.pick import (pick_types, channel_indices_by_type, channel_type,
                       pick_channels, pick_info, _pick_data_channels,
                       _pick_aux_channels, _DATA_CH_TYPES_SPLIT)
 from .io.proj import setup_proj, ProjMixin, _proj_equal
-from .io.base import _BaseRaw, ToDataFrameMixin, TimeMixin
+from .io.base import BaseRaw, ToDataFrameMixin, TimeMixin
 from .bem import _check_origin
 from .evoked import EvokedArray, _check_decim
 from .baseline import rescale, _log_rescale
 from .channels.channels import (ContainsMixin, UpdateChannelsMixin,
                                 SetChannelsMixin, InterpolationMixin)
-from .filter import resample, detrend, FilterMixin
+from .filter import detrend, FilterMixin
 from .event import _read_events_fif, make_fixed_length_events
 from .fixes import _get_args
 from .viz import (plot_epochs, plot_epochs_psd, plot_epochs_psd_topomap,
                   plot_epochs_image, plot_topo_image_epochs, plot_drop_log)
 from .utils import (check_fname, logger, verbose, _check_type_picks,
-                    _time_mask, check_random_state, warn, _check_copy_dep,
+                    _time_mask, check_random_state, warn, _pl, _ensure_int,
                     sizeof_fmt, SizeMixin, copy_function_doc_to_method_doc)
 from .externals.six import iteritems, string_types
 from .externals.six.moves import zip
 
 
 def _save_split(epochs, fname, part_idx, n_parts):
-    """Split epochs"""
-
+    """Split epochs."""
     # insert index in filename
     path, base = op.split(fname)
     idx = base.find('.')
@@ -83,6 +82,7 @@ def _save_split(epochs, fname, part_idx, n_parts):
 
     # write events out after getting data to ensure bad events are dropped
     data = epochs.get_data()
+    assert data.dtype == 'float64'
     start_block(fid, FIFF.FIFFB_MNE_EVENTS)
     write_int(fid, FIFF.FIFF_MNE_EVENT_LIST, epochs.events.T)
     mapping_ = ';'.join([k + ':' + str(v) for k, v in
@@ -139,24 +139,82 @@ def _save_split(epochs, fname, part_idx, n_parts):
     end_file(fid)
 
 
-class _BaseEpochs(ProjMixin, ContainsMixin, UpdateChannelsMixin,
-                  SetChannelsMixin, InterpolationMixin, FilterMixin,
-                  ToDataFrameMixin, TimeMixin, SizeMixin):
-    """Abstract base class for Epochs-type classes
+class BaseEpochs(ProjMixin, ContainsMixin, UpdateChannelsMixin,
+                 SetChannelsMixin, InterpolationMixin, FilterMixin,
+                 ToDataFrameMixin, TimeMixin, SizeMixin):
+    """Abstract base class for Epochs-type classes.
 
     This class provides basic functionality and should never be instantiated
     directly. See Epochs below for an explanation of the parameters.
+
+    Parameters
+    ----------
+    info : dict
+        A copy of the info dict from the raw object.
+    data : ndarray | None
+        If ``None``, data will be read from the Raw object. If ndarray, must be
+        of shape (n_epochs, n_channels, n_times).
+    events : array of int, shape (n_events, 3)
+        See `Epochs` docstring.
+    event_id : int | list of int | dict | None
+        See `Epochs` docstring.
+    tmin : float
+        See `Epochs` docstring.
+    tmax : float
+        See `Epochs` docstring.
+    baseline : None or tuple of length 2 (default (None, 0))
+        See `Epochs` docstring.
+    raw : Raw object
+        An instance of Raw.
+    picks : array-like of int | None (default)
+        See `Epochs` docstring.
+    reject : dict | None
+        See `Epochs` docstring.
+    flat : dict | None
+        See `Epochs` docstring.
+    decim : int
+        See `Epochs` docstring.
+    reject_tmin : scalar | None
+        See `Epochs` docstring.
+    reject_tmax : scalar | None
+        See `Epochs` docstring.
+    detrend : int | None
+        See `Epochs` docstring.
+    proj : bool | 'delayed'
+        See `Epochs` docstring.
+    on_missing : str
+        See `Epochs` docstring.
+    preload_at_end : bool
+        Load all epochs from disk when creating the object
+        or wait before accessing each epoch (more memory
+        efficient but can be slower).
+    selection : iterable | None
+        Iterable of indices of selected epochs. If ``None``, will be
+        automatically generated, corresponding to all non-zero events.
+    drop_log : list | None
+        List of lists of strings indicating which epochs have been marked to be
+        ignored.
+    filename : str | None
+        The filename (if the epochs are read from disk).
+    verbose : bool, str, int, or None
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more). Defaults to
+        raw.verbose.
+
+    Notes
+    -----
+    The ``BaseEpochs`` class is public to allow for stable type-checking in
+    user code (i.e., ``isinstance(my_epochs, BaseEpochs)``) but should not be
+    used as a constructor for Epochs objects (use instead :class:`mne.Epochs`).
     """
+
     def __init__(self, info, data, events, event_id=None, tmin=-0.2, tmax=0.5,
-                 baseline=(None, 0), raw=None,
-                 picks=None, name='Unknown', reject=None, flat=None,
-                 decim=1, reject_tmin=None, reject_tmax=None, detrend=None,
-                 add_eeg_ref=False, proj=True, on_missing='error',
+                 baseline=(None, 0), raw=None, picks=None, reject=None,
+                 flat=None, decim=1, reject_tmin=None, reject_tmax=None,
+                 detrend=None, proj=True, on_missing='error',
                  preload_at_end=False, selection=None, drop_log=None,
-                 verbose=None):
-
+                 filename=None, verbose=None):  # noqa: D102
         self.verbose = verbose
-        self.name = name
 
         if on_missing not in ['error', 'warning', 'ignore']:
             raise ValueError('on_missing must be one of: error, '
@@ -164,31 +222,31 @@ class _BaseEpochs(ProjMixin, ContainsMixin, UpdateChannelsMixin,
 
         # check out event_id dict
         if event_id is None:  # convert to int to make typing-checks happy
-            event_id = dict((str(e), int(e)) for e in np.unique(events[:, 2]))
-        elif isinstance(event_id, dict):
-            if not all(isinstance(v, int) for v in event_id.values()):
-                raise ValueError('Event IDs must be of type integer')
-            if not all(isinstance(k, string_types) for k in event_id):
-                raise ValueError('Event names must be of type str')
+            event_id = list(np.unique(events[:, 2]))
+        if isinstance(event_id, dict):
+            for key in event_id.keys():
+                if not isinstance(key, string_types):
+                    raise TypeError('Event names must be of type str, '
+                                    'got %s (%s)' % (key, type(key)))
+            event_id = dict((key, _ensure_int(val, 'event_id[%s]' % key))
+                            for key, val in event_id.items())
         elif isinstance(event_id, list):
-            if not all(isinstance(v, int) for v in event_id):
-                raise ValueError('Event IDs must be of type integer')
+            event_id = [_ensure_int(v, 'event_id[%s]' % vi)
+                        for vi, v in enumerate(event_id)]
             event_id = dict(zip((str(i) for i in event_id), event_id))
-        elif isinstance(event_id, int):
-            event_id = {str(event_id): event_id}
         else:
-            raise ValueError('event_id must be dict or int.')
+            event_id = _ensure_int(event_id, 'event_id')
+            event_id = {str(event_id): event_id}
         self.event_id = event_id
         del event_id
 
         if events is not None:  # RtEpochs can have events=None
-
+            events = np.asarray(events)
             if events.dtype.kind not in ['i', 'u']:
                 raise ValueError('events must be an array of type int')
+            events = events.astype(int)
             if events.ndim != 2 or events.shape[1] != 3:
                 raise ValueError('events must be 2D with 3 columns')
-            if len(np.unique(events[:, 0])) != len(events):
-                raise RuntimeError('Event time samples were not unique')
             for key, val in self.event_id.items():
                 if val not in events[:, 2]:
                     msg = ('No matching events found for %s '
@@ -212,6 +270,8 @@ class _BaseEpochs(ProjMixin, ContainsMixin, UpdateChannelsMixin,
             else:
                 self.drop_log = drop_log
             events = events[selected]
+            if len(np.unique(events[:, 0])) != len(events):
+                raise RuntimeError('Event time samples were not unique')
             n_events = len(events)
             if n_events > 1:
                 if np.diff(events.astype(np.int64)[:, 0]).min() <= 0:
@@ -297,9 +357,8 @@ class _BaseEpochs(ProjMixin, ContainsMixin, UpdateChannelsMixin,
             logger.info('Entering delayed SSP mode.')
         else:
             self._do_delayed_proj = False
-        add_eeg_ref = _dep_eeg_ref(add_eeg_ref) if 'eeg' in self else False
         activate = False if self._do_delayed_proj else proj
-        self._projector, self.info = setup_proj(self.info, add_eeg_ref,
+        self._projector, self.info = setup_proj(self.info, False,
                                                 activate=activate)
         if preload_at_end:
             assert self._data is None
@@ -312,9 +371,10 @@ class _BaseEpochs(ProjMixin, ContainsMixin, UpdateChannelsMixin,
             # more memory safe in most instances
             for ii, epoch in enumerate(self._data):
                 self._data[ii] = np.dot(self._projector, epoch)
+        self._filename = str(filename) if filename is not None else filename
 
     def load_data(self):
-        """Load the data if not already preloaded
+        """Load the data if not already preloaded.
 
         Returns
         -------
@@ -335,10 +395,12 @@ class _BaseEpochs(ProjMixin, ContainsMixin, UpdateChannelsMixin,
         self._decim = 1
         self._raw_times = self.times
         assert self._data.shape[-1] == len(self.times)
+        self._raw = None  # shouldn't need it anymore
         return self
 
-    def decimate(self, decim, offset=0):
-        """Decimate the epochs
+    @verbose
+    def decimate(self, decim, offset=0, verbose=None):
+        """Decimate the epochs.
 
         .. note:: No filtering is performed. To avoid aliasing, ensure
                   your data are properly lowpassed.
@@ -354,6 +416,11 @@ class _BaseEpochs(ProjMixin, ContainsMixin, UpdateChannelsMixin,
 
             .. versionadded:: 0.12
 
+        verbose : bool, str, int, or None
+            If not None, override default verbose level (see
+            :func:`mne.verbose` and :ref:`Logging documentation <tut_logging>`
+            for more).
+
         Returns
         -------
         epochs : instance of Epochs
@@ -361,9 +428,9 @@ class _BaseEpochs(ProjMixin, ContainsMixin, UpdateChannelsMixin,
 
         See Also
         --------
-        Evoked.decimate
-        Epochs.resample
-        Raw.resample
+        mne.Evoked.decimate
+        mne.Epochs.resample
+        mne.io.Raw.resample
 
         Notes
         -----
@@ -393,7 +460,7 @@ class _BaseEpochs(ProjMixin, ContainsMixin, UpdateChannelsMixin,
 
     @verbose
     def apply_baseline(self, baseline=(None, 0), verbose=None):
-        """Baseline correct epochs
+        """Baseline correct epochs.
 
         Parameters
         ----------
@@ -407,7 +474,9 @@ class _BaseEpochs(ProjMixin, ContainsMixin, UpdateChannelsMixin,
             from the data. The baseline (a, b) includes both endpoints, i.e.
             all timepoints t such that a <= t <= b.
         verbose : bool, str, int, or None
-            If not None, override default verbose level (see mne.verbose).
+            If not None, override default verbose level (see
+            :func:`mne.verbose` and :ref:`Logging documentation <tut_logging>`
+            for more).
 
         Returns
         -------
@@ -423,7 +492,7 @@ class _BaseEpochs(ProjMixin, ContainsMixin, UpdateChannelsMixin,
         if not self.preload:
             # Eventually we can relax this restriction, but it will require
             # more careful checking of baseline (e.g., refactor with the
-            # _BaseEpochs.__init__ checks)
+            # BaseEpochs.__init__ checks)
             raise RuntimeError('Data must be loaded to apply a new baseline')
         _check_baseline(baseline, self.tmin, self.tmax, self.info['sfreq'])
 
@@ -439,7 +508,7 @@ class _BaseEpochs(ProjMixin, ContainsMixin, UpdateChannelsMixin,
         return self
 
     def _reject_setup(self, reject, flat):
-        """Sets self._reject_time and self._channel_type_idx"""
+        """Set self._reject_time and self._channel_type_idx."""
         idx = channel_indices_by_type(self.info)
         reject = deepcopy(reject) if reject is not None else dict()
         flat = deepcopy(flat) if flat is not None else dict()
@@ -512,7 +581,7 @@ class _BaseEpochs(ProjMixin, ContainsMixin, UpdateChannelsMixin,
 
     @verbose
     def _is_good_epoch(self, data, verbose=None):
-        """Determine if epoch is good"""
+        """Determine if epoch is good."""
         if isinstance(data, string_types):
             return False, [data]
         if data is None:
@@ -533,7 +602,7 @@ class _BaseEpochs(ProjMixin, ContainsMixin, UpdateChannelsMixin,
 
     @verbose
     def _detrend_offset_decim(self, epoch, verbose=None):
-        """Aux Function: detrend, baseline correct, offset, decim
+        """Aux Function: detrend, baseline correct, offset, decim.
 
         Note: operates inplace
         """
@@ -562,7 +631,7 @@ class _BaseEpochs(ProjMixin, ContainsMixin, UpdateChannelsMixin,
         return epoch
 
     def iter_evoked(self):
-        """Iterate over epochs as a sequence of Evoked objects
+        """Iterate over epochs as a sequence of Evoked objects.
 
         The Evoked objects yielded will each contain a single epoch (i.e., no
         averaging is performed).
@@ -580,7 +649,7 @@ class _BaseEpochs(ProjMixin, ContainsMixin, UpdateChannelsMixin,
             yield EvokedArray(data, info, tmin, comment=str(event_id))
 
     def subtract_evoked(self, evoked=None):
-        """Subtract an evoked response from each epoch
+        """Subtract an evoked response from each epoch.
 
         Can be used to exclude the evoked response when analyzing induced
         activity, see e.g. [1].
@@ -652,11 +721,11 @@ class _BaseEpochs(ProjMixin, ContainsMixin, UpdateChannelsMixin,
         return self
 
     def __next__(self, *args, **kwargs):
-        """Wrapper for Py3k"""
+        """Provide a wrapper for Py3k."""
         return self.next(*args, **kwargs)
 
     def average(self, picks=None):
-        """Compute average of epochs
+        """Compute average of epochs.
 
         Parameters
         ----------
@@ -674,11 +743,16 @@ class _BaseEpochs(ProjMixin, ContainsMixin, UpdateChannelsMixin,
         Computes an average of all epochs in the instance, even if
         they correspond to different conditions. To average by condition,
         do ``epochs[condition].average()`` for each condition separately.
+
+        When picks is None and epochs contain only ICA channels, no channels
+        are selected, resulting in an error. This is because ICA channels
+        are not considered data channels (they are of misc type) and only data
+        channels are selected when picks is None.
         """
         return self._compute_mean_or_stderr(picks, 'ave')
 
     def standard_error(self, picks=None):
-        """Compute standard error over epochs
+        """Compute standard error over epochs.
 
         Parameters
         ----------
@@ -694,10 +768,20 @@ class _BaseEpochs(ProjMixin, ContainsMixin, UpdateChannelsMixin,
         return self._compute_mean_or_stderr(picks, 'stderr')
 
     def _compute_mean_or_stderr(self, picks, mode='ave'):
-        """Compute the mean or std over epochs and return Evoked"""
-
+        """Compute the mean or std over epochs and return Evoked."""
         _do_std = True if mode == 'stderr' else False
 
+        # if instance contains ICA channels they won't be included unless picks
+        # is specified
+        if picks is None:
+            check_ICA = [x.startswith('ICA') for x in self.ch_names]
+            if np.all(check_ICA):
+                raise TypeError('picks must be specified (i.e. not None) for '
+                                'ICA channel data')
+            elif np.any(check_ICA):
+                warn('ICA channels will not be included unless explicitly '
+                     'selected in picks')
+
         n_channels = len(self.ch_names)
         n_times = len(self.times)
 
@@ -732,15 +816,30 @@ class _BaseEpochs(ProjMixin, ContainsMixin, UpdateChannelsMixin,
         else:
             kind = 'standard_error'
             data /= np.sqrt(n_events)
+
         return self._evoked_from_epoch_data(data, self.info, picks, n_events,
-                                            kind)
+                                            kind, self._name)
 
-    def _evoked_from_epoch_data(self, data, info, picks, n_events, kind):
-        """Helper to create an evoked object from epoch data"""
+    @property
+    def _name(self):
+        """Give a nice string representation based on event ids."""
+        if len(self.event_id) == 1:
+            comment = next(iter(self.event_id.keys()))
+        else:
+            count = np.bincount(self.events[:, 2])
+            comments = list()
+            for key, value in self.event_id.items():
+                comments.append('%.2f * %s' % (
+                    float(count[value]) / len(self.events), key))
+            comment = ' + '.join(comments)
+        return comment
+
+    def _evoked_from_epoch_data(self, data, info, picks, n_events, kind,
+                                comment):
+        """Create an evoked object from epoch data."""
         info = deepcopy(info)
-        evoked = EvokedArray(data, info, tmin=self.times[0],
-                             comment=self.name, nave=n_events, kind=kind,
-                             verbose=self.verbose)
+        evoked = EvokedArray(data, info, tmin=self.times[0], comment=comment,
+                             nave=n_events, kind=kind, verbose=self.verbose)
         # XXX: above constructor doesn't recreate the times object precisely
         evoked.times = self.times.copy()
 
@@ -761,15 +860,18 @@ class _BaseEpochs(ProjMixin, ContainsMixin, UpdateChannelsMixin,
 
     @property
     def ch_names(self):
-        """Channel names"""
+        """Channel names."""
         return self.info['ch_names']
 
     @copy_function_doc_to_method_doc(plot_epochs)
     def plot(self, picks=None, scalings=None, n_epochs=20, n_channels=20,
-             title=None, show=True, block=False):
+             title=None, events=None, event_colors=None, show=True,
+             block=False, decim='auto'):
         return plot_epochs(self, picks=picks, scalings=scalings,
                            n_epochs=n_epochs, n_channels=n_channels,
-                           title=title, show=show, block=block)
+                           title=title, events=events,
+                           event_colors=event_colors, show=show, block=block,
+                           decim=decim)
 
     @copy_function_doc_to_method_doc(plot_epochs_psd)
     def plot_psd(self, fmin=0, fmax=np.inf, tmin=None, tmax=None, proj=False,
@@ -837,8 +939,9 @@ class _BaseEpochs(ProjMixin, ContainsMixin, UpdateChannelsMixin,
             If flat is None then no rejection is done. If 'existing',
             then the flat parameters set at instantiation are used.
         verbose : bool, str, int, or None
-            If not None, override default verbose level (see mne.verbose).
-            Defaults to self.verbose.
+            If not None, override default verbose level (see
+            :func:`mne.verbose` and :ref:`Logging documentation <tut_logging>`
+            for more). Defaults to self.verbose.
 
         Returns
         -------
@@ -897,19 +1000,22 @@ class _BaseEpochs(ProjMixin, ContainsMixin, UpdateChannelsMixin,
                              show=show)
 
     @copy_function_doc_to_method_doc(plot_epochs_image)
-    def plot_image(self, picks=None, sigma=0., vmin=None,
-                   vmax=None, colorbar=True, order=None, show=True,
-                   units=None, scalings=None, cmap='RdBu_r',
-                   fig=None, axes=None, overlay_times=None):
+    def plot_image(self, picks=None, sigma=0., vmin=None, vmax=None,
+                   colorbar=True, order=None, show=True, units=None,
+                   scalings=None, cmap=None, fig=None, axes=None,
+                   overlay_times=None, combine=None, group_by=None,
+                   evoked=True, ts_args=dict(), title=None):
         return plot_epochs_image(self, picks=picks, sigma=sigma, vmin=vmin,
                                  vmax=vmax, colorbar=colorbar, order=order,
                                  show=show, units=units, scalings=scalings,
                                  cmap=cmap, fig=fig, axes=axes,
-                                 overlay_times=overlay_times)
+                                 overlay_times=overlay_times, combine=combine,
+                                 group_by=group_by, evoked=evoked,
+                                 ts_args=ts_args, title=title)
 
     @verbose
     def drop(self, indices, reason='USER', verbose=None):
-        """Drop epochs based on indices or boolean mask
+        """Drop epochs based on indices or boolean mask.
 
         .. note:: The indices refer to the current set of undropped epochs
                   rather than the complete set of dropped and undropped epochs.
@@ -930,8 +1036,9 @@ class _BaseEpochs(ProjMixin, ContainsMixin, UpdateChannelsMixin,
             Reason for dropping the epochs ('ECG', 'timeout', 'blink' etc).
             Default: 'USER'.
         verbose : bool, str, int, or None
-            If not None, override default verbose level (see mne.verbose).
-            Defaults to self.verbose.
+            If not None, override default verbose level (see
+            :func:`mne.verbose` and :ref:`Logging documentation <tut_logging>`
+            for more). Defaults to self.verbose.
 
         Returns
         -------
@@ -960,15 +1067,15 @@ class _BaseEpochs(ProjMixin, ContainsMixin, UpdateChannelsMixin,
             self._data = np.delete(self._data, indices, axis=0)
 
         count = len(indices)
-        logger.info('Dropped %d epoch%s' % (count, '' if count == 1 else 's'))
+        logger.info('Dropped %d epoch%s' % (count, _pl(count)))
         return self
 
     def _get_epoch_from_raw(self, idx, verbose=None):
-        """Method to get a given epoch from disk"""
+        """Get a given epoch from disk."""
         raise NotImplementedError
 
     def _project_epoch(self, epoch):
-        """Helper to process a raw epoch based on the delayed param"""
+        """Process a raw epoch based on the delayed param."""
         # whenever requested, the first epoch is being projected.
         if (epoch is None) or isinstance(epoch, string_types):
             # can happen if t < 0 or reject based on annotations
@@ -980,7 +1087,7 @@ class _BaseEpochs(ProjMixin, ContainsMixin, UpdateChannelsMixin,
 
     @verbose
     def _get_data(self, out=True, verbose=None):
-        """Load all data, dropping bad epochs along the way
+        """Load all data, dropping bad epochs along the way.
 
         Parameters
         ----------
@@ -988,8 +1095,9 @@ class _BaseEpochs(ProjMixin, ContainsMixin, UpdateChannelsMixin,
             Return the data. Setting this to False is used to reject bad
             epochs without caching all the data, which saves memory.
         verbose : bool, str, int, or None
-            If not None, override default verbose level (see mne.verbose).
-            Defaults to self.verbose.
+            If not None, override default verbose level (see
+            :func:`mne.verbose` and :ref:`Logging documentation <tut_logging>`
+            for more). Defaults to self.verbose.
         """
         n_events = len(self.events)
         # in case there are no good events
@@ -1074,17 +1182,17 @@ class _BaseEpochs(ProjMixin, ContainsMixin, UpdateChannelsMixin,
         return data if out else None
 
     def get_data(self):
-        """Get all epochs as a 3D array
+        """Get all epochs as a 3D array.
 
         Returns
         -------
         data : array of shape (n_epochs, n_channels, n_times)
-            A copy of the epochs data.
+            A view on epochs data.
         """
         return self._get_data()
 
     def __len__(self):
-        """The number of epochs
+        """Return the number of epochs.
 
         Returns
         -------
@@ -1116,7 +1224,7 @@ class _BaseEpochs(ProjMixin, ContainsMixin, UpdateChannelsMixin,
         return len(self.events)
 
     def __iter__(self):
-        """Function to make iteration over epochs easy
+        """Facilitate iteration over epochs.
 
         Notes
         -----
@@ -1178,14 +1286,21 @@ class _BaseEpochs(ProjMixin, ContainsMixin, UpdateChannelsMixin,
 
     @property
     def tmin(self):
+        """First time point."""
         return self.times[0]
 
     @property
+    def filename(self):
+        """The filename."""
+        return self._filename
+
+    @property
     def tmax(self):
+        """Last time point."""
         return self.times[-1]
 
     def __repr__(self):
-        """ Build string representation"""
+        """Build string representation."""
         s = 'n_events : %s ' % len(self.events)
         s += '(all good)' if self._bad_dropped else '(good & bad)'
         s += ', tmin : %s (s)' % self.tmin
@@ -1198,7 +1313,7 @@ class _BaseEpochs(ProjMixin, ContainsMixin, UpdateChannelsMixin,
                       for k, v in sorted(self.event_id.items())]
             s += ',\n %s' % ', '.join(counts)
         class_name = self.__class__.__name__
-        class_name = 'Epochs' if class_name == '_BaseEpochs' else class_name
+        class_name = 'Epochs' if class_name == 'BaseEpochs' else class_name
         return '<%s  |  %s>' % (class_name, s)
 
     def _keys_to_idx(self, keys):
@@ -1207,7 +1322,7 @@ class _BaseEpochs(ProjMixin, ContainsMixin, UpdateChannelsMixin,
                          for k in _hid_match(self.event_id, keys)]).any(axis=0)
 
     def __getitem__(self, item):
-        """Return an Epochs object with a copied subset of epochs
+        """Return an Epochs object with a copied subset of epochs.
 
         Parameters
         ----------
@@ -1260,7 +1375,6 @@ class _BaseEpochs(ProjMixin, ContainsMixin, UpdateChannelsMixin,
         if isinstance(item, (list, tuple)) and \
                 isinstance(item[0], string_types):
             select = epochs._keys_to_idx(item)
-            epochs.name = '+'.join(item)
         else:
             select = item if isinstance(item, slice) else np.atleast_1d(item)
 
@@ -1279,7 +1393,7 @@ class _BaseEpochs(ProjMixin, ContainsMixin, UpdateChannelsMixin,
         return epochs
 
     def crop(self, tmin=None, tmax=None):
-        """Crops a time interval from epochs object.
+        """Crop a time interval from epochs object.
 
         Parameters
         ----------
@@ -1324,57 +1438,8 @@ class _BaseEpochs(ProjMixin, ContainsMixin, UpdateChannelsMixin,
         self._data = self._data[:, :, tmask]
         return self
 
-    @verbose
-    def resample(self, sfreq, npad='auto', window='boxcar', n_jobs=1,
-                 verbose=None):
-        """Resample preloaded data
-
-        Parameters
-        ----------
-        sfreq : float
-            New sample rate to use
-        npad : int | str
-            Amount to pad the start and end of the data.
-            Can also be "auto" to use a padding that will result in
-            a power-of-two size (can be much faster).
-        window : string or tuple
-            Window to use in resampling. See scipy.signal.resample.
-        n_jobs : int
-            Number of jobs to run in parallel.
-        verbose : bool, str, int, or None
-            If not None, override default verbose level (see mne.verbose).
-            Defaults to self.verbose.
-
-        Returns
-        -------
-        epochs : instance of Epochs
-            The resampled epochs object.
-
-        See Also
-        --------
-        mne.Epochs.savgol_filter
-        mne.io.Raw.resample
-
-        Notes
-        -----
-        For some data, it may be more accurate to use npad=0 to reduce
-        artifacts. This is dataset dependent -- check your data!
-        """
-        # XXX this could operate on non-preloaded data, too
-        if not self.preload:
-            raise RuntimeError('Can only resample preloaded data')
-        o_sfreq = self.info['sfreq']
-        self._data = resample(self._data, sfreq, o_sfreq, npad, window=window,
-                              n_jobs=n_jobs)
-        # adjust indirectly affected variables
-        self.info['sfreq'] = float(sfreq)
-        self.times = (np.arange(self._data.shape[2], dtype=np.float) /
-                      sfreq + self.times[0])
-        self._raw_times = self.times
-        return self
-
     def copy(self):
-        """Return copy of Epochs instance"""
+        """Return copy of Epochs instance."""
         raw = self._raw
         del self._raw
         new = deepcopy(self)
@@ -1383,7 +1448,7 @@ class _BaseEpochs(ProjMixin, ContainsMixin, UpdateChannelsMixin,
         return new
 
     def save(self, fname, split_size='2GB'):
-        """Save epochs in a fif file
+        """Save epochs in a fif file.
 
         Parameters
         ----------
@@ -1410,6 +1475,7 @@ class _BaseEpochs(ProjMixin, ContainsMixin, UpdateChannelsMixin,
         # bad epochs anyway
         self.drop_bad()
         total_size = self[0].get_data().nbytes * len(self)
+        total_size /= 2  # 64bit data converted to 32bit before writing.
         n_parts = int(np.ceil(total_size / float(split_size)))
         epoch_idxs = np.array_split(np.arange(len(self)), n_parts)
 
@@ -1419,8 +1485,8 @@ class _BaseEpochs(ProjMixin, ContainsMixin, UpdateChannelsMixin,
             this_epochs.event_id = self.event_id
             _save_split(this_epochs, fname, part_idx, n_parts)
 
-    def equalize_event_counts(self, event_ids, method='mintime', copy=None):
-        """Equalize the number of trials in each condition
+    def equalize_event_counts(self, event_ids, method='mintime'):
+        """Equalize the number of trials in each condition.
 
         It tries to make the remaining epochs occurring as close as possible in
         time. This method works based on the idea that if there happened to be
@@ -1449,10 +1515,6 @@ class _BaseEpochs(ProjMixin, ContainsMixin, UpdateChannelsMixin,
             If 'truncate', events will be truncated from the end of each event
             list. If 'mintime', timing differences between each event list
             will be minimized.
-        copy : bool
-            This parameter has been deprecated and will be removed in 0.14.
-            Use inst.copy() instead.
-            Whether to return a new instance or modify in place.
 
         Returns
         -------
@@ -1476,16 +1538,15 @@ class _BaseEpochs(ProjMixin, ContainsMixin, UpdateChannelsMixin,
         conditions will contribute evenly. E.g., it is possible to end up
         with 70 'Nonspatial' trials, 69 'Left' and 1 'Right'.
         """
-        epochs = _check_copy_dep(self, copy)
         if len(event_ids) == 0:
             raise ValueError('event_ids must have at least one element')
-        if not epochs._bad_dropped:
-            epochs.drop_bad()
+        if not self._bad_dropped:
+            self.drop_bad()
         # figure out how to equalize
         eq_inds = list()
 
         # deal with hierarchical tags
-        ids = epochs.event_id
+        ids = self.event_id
         orig_ids = list(event_ids)
         tagging = False
         if "/" in "".join(ids):
@@ -1515,7 +1576,7 @@ class _BaseEpochs(ProjMixin, ContainsMixin, UpdateChannelsMixin,
 
             # raise for non-orthogonal tags
             if tagging is True:
-                events_ = [set(epochs[x].events[:, 0]) for x in event_ids]
+                events_ = [set(self[x].events[:, 0]) for x in event_ids]
                 doubles = events_[0].intersection(events_[1])
                 if len(doubles):
                     raise ValueError("The two sets of epochs are "
@@ -1523,15 +1584,15 @@ class _BaseEpochs(ProjMixin, ContainsMixin, UpdateChannelsMixin,
                                      "orthogonal selection.")
 
         for eq in event_ids:
-            eq_inds.append(np.where(epochs._keys_to_idx(eq))[0])
+            eq_inds.append(np.where(self._keys_to_idx(eq))[0])
 
-        event_times = [epochs.events[e, 0] for e in eq_inds]
+        event_times = [self.events[e, 0] for e in eq_inds]
         indices = _get_drop_indices(event_times, method)
         # need to re-index indices
         indices = np.concatenate([e[idx] for e, idx in zip(eq_inds, indices)])
-        epochs.drop(indices, reason='EQUALIZED_COUNT')
+        self.drop(indices, reason='EQUALIZED_COUNT')
         # actually remove the indices
-        return epochs, indices
+        return self, indices
 
 
 def _hid_match(event_id, keys):
@@ -1565,7 +1626,7 @@ def _hid_match(event_id, keys):
 
 
 def _check_baseline(baseline, tmin, tmax, sfreq):
-    """Helper to check for a valid baseline"""
+    """Check for a valid baseline."""
     if baseline is not None:
         if not isinstance(baseline, tuple) or len(baseline) != 2:
             raise ValueError('`baseline=%s` is an invalid argument.'
@@ -1594,7 +1655,8 @@ def _check_baseline(baseline, tmin, tmax, sfreq):
 
 
 def _drop_log_stats(drop_log, ignore=('IGNORED',)):
-    """
+    """Compute drop log stats.
+
     Parameters
     ----------
     drop_log : list of lists
@@ -1614,26 +1676,8 @@ def _drop_log_stats(drop_log, ignore=('IGNORED',)):
     return perc
 
 
-def _dep_eeg_ref(add_eeg_ref, current_default=True):
-    """Helper for deprecation add_eeg_ref -> False"""
-    if current_default is True:
-        if add_eeg_ref is None:
-            add_eeg_ref = True
-            warn('add_eeg_ref defaults to True in 0.13, will default to '
-                 'False in 0.14, and will be removed in 0.15. We recommend '
-                 'to use add_eeg_ref=False and set_eeg_reference() instead.',
-                 DeprecationWarning)
-    # current_default is False
-    elif add_eeg_ref is None:
-        add_eeg_ref = False
-    else:
-        warn('add_eeg_ref will be removed in 0.14, use set_eeg_reference()'
-             ' instead', DeprecationWarning)
-    return add_eeg_ref
-
-
-class Epochs(_BaseEpochs):
-    """Epochs extracted from a Raw instance
+class Epochs(BaseEpochs):
+    """Epochs extracted from a Raw instance.
 
     Parameters
     ----------
@@ -1666,8 +1710,6 @@ class Epochs(_BaseEpochs):
         a <= t <= b.
     picks : array-like of int | None (default)
         Indices of channels to include (if None, all channels are used).
-    name : string
-        Comment that describes the Epochs data created.
     preload : boolean
         Load all epochs from disk when creating the object
         or wait before accessing each epoch (more memory
@@ -1717,11 +1759,6 @@ class Epochs(_BaseEpochs):
         either turn off baseline correction, as this may introduce a DC
         shift, or set baseline correction to use the entire time interval
         (will yield equivalent results but be slower).
-    add_eeg_ref : bool
-        If True, an EEG average reference will be added (unless one
-        already exists). The default value of True in 0.13 will change to
-        False in 0.14, and the parameter will be removed in 0.15. Use
-        :func:`mne.set_eeg_reference` instead.
     on_missing : str
         What to do if one or several event ids are not found in the recording.
         Valid keys are 'error' | 'warning' | 'ignore'
@@ -1734,12 +1771,13 @@ class Epochs(_BaseEpochs):
         overlapping with segments whose description begins with ``'bad'`` are
         rejected. If False, no rejection based on annotations is performed.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
-        Defaults to raw.verbose.
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more). Defaults to
+        raw.verbose.
 
     Attributes
     ----------
-    info: dict
+    info : instance of Info
         Measurement info.
     event_id : dict
         Names of conditions corresponding to event_ids.
@@ -1762,6 +1800,8 @@ class Epochs(_BaseEpochs):
         names of channels that exceeded the amplitude threshold;
         'EQUALIZED_COUNTS' (see equalize_event_counts);
         or 'USER' for user-defined reasons (see drop method).
+    filename : str
+        The filename of the object.
     verbose : bool, str, int, or None
         See above.
 
@@ -1778,13 +1818,14 @@ class Epochs(_BaseEpochs):
     For indexing and slicing using ``epochs[...]``, see
     :func:`mne.Epochs.__getitem__`.
     """
+
     @verbose
     def __init__(self, raw, events, event_id=None, tmin=-0.2, tmax=0.5,
-                 baseline=(None, 0), picks=None, name='Unknown', preload=False,
+                 baseline=(None, 0), picks=None, preload=False,
                  reject=None, flat=None, proj=True, decim=1, reject_tmin=None,
-                 reject_tmax=None, detrend=None, add_eeg_ref=None,
-                 on_missing='error', reject_by_annotation=True, verbose=None):
-        if not isinstance(raw, _BaseRaw):
+                 reject_tmax=None, detrend=None, on_missing='error',
+                 reject_by_annotation=True, verbose=None):  # noqa: D102
+        if not isinstance(raw, BaseRaw):
             raise ValueError('The first argument to `Epochs` must be an '
                              'instance of `mne.io.Raw`')
         info = deepcopy(raw.info)
@@ -1793,17 +1834,17 @@ class Epochs(_BaseEpochs):
         proj = proj or raw.proj
 
         self.reject_by_annotation = reject_by_annotation
-        # call _BaseEpochs constructor
+        # call BaseEpochs constructor
         super(Epochs, self).__init__(
             info, None, events, event_id, tmin, tmax, baseline=baseline,
-            raw=raw, picks=picks, name=name, reject=reject, flat=flat,
-            decim=decim, reject_tmin=reject_tmin, reject_tmax=reject_tmax,
-            detrend=detrend, add_eeg_ref=add_eeg_ref, proj=proj,
-            on_missing=on_missing, preload_at_end=preload, verbose=verbose)
+            raw=raw, picks=picks, reject=reject, flat=flat, decim=decim,
+            reject_tmin=reject_tmin, reject_tmax=reject_tmax, detrend=detrend,
+            proj=proj, on_missing=on_missing, preload_at_end=preload,
+            verbose=verbose)
 
     @verbose
     def _get_epoch_from_raw(self, idx, verbose=None):
-        """Load one epoch from disk
+        """Load one epoch from disk.
 
         Returns
         -------
@@ -1828,13 +1869,14 @@ class Epochs(_BaseEpochs):
         return data
 
 
-class EpochsArray(_BaseEpochs):
-    """Epochs object from numpy array
+class EpochsArray(BaseEpochs):
+    """Epochs object from numpy array.
 
     Parameters
     ----------
     data : array, shape (n_epochs, n_channels, n_times)
-        The channels' time series for each epoch.
+        The channels' time series for each epoch. See notes for proper units of
+        measure.
     info : instance of Info
         Info dictionary. Consider using ``create_info`` to populate
         this structure.
@@ -1845,7 +1887,7 @@ class EpochsArray(_BaseEpochs):
         If None (default), all event values are set to 1 and event time-samples
         are set to range(n_epochs).
     tmin : float
-        Start time before event. If nothing provided, defaults to -0.2.
+        Start time before event. If nothing provided, defaults to 0.
     event_id : int | list of int | dict | None
         The id of the event to consider. If dict,
         the keys can later be used to access associated events. Example:
@@ -1887,9 +1929,21 @@ class EpochsArray(_BaseEpochs):
         a <= t <= b.
     proj : bool | 'delayed'
         Apply SSP projection vectors. See :class:`mne.Epochs` for details.
+    on_missing : str
+        See :class:`mne.Epochs` docstring for details.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
-        Defaults to raw.verbose.
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
+
+    Notes
+    -----
+    Proper units of measure:
+    * V: eeg, eog, seeg, emg, ecg, bio, ecog
+    * T: mag
+    * T/m: grad
+    * M: hbo, hbr
+    * Am: dipole
+    * AU: misc
 
     See Also
     --------
@@ -1899,7 +1953,8 @@ class EpochsArray(_BaseEpochs):
     @verbose
     def __init__(self, data, info, events=None, tmin=0, event_id=None,
                  reject=None, flat=None, reject_tmin=None,
-                 reject_tmax=None, baseline=None, proj=True, verbose=None):
+                 reject_tmax=None, baseline=None, proj=True,
+                 on_missing='error', verbose=None):  # noqa: D102
         dtype = np.complex128 if np.any(np.iscomplex(data)) else np.float64
         data = np.asanyarray(data, dtype=dtype)
         if data.ndim != 3:
@@ -1916,7 +1971,7 @@ class EpochsArray(_BaseEpochs):
         if data.shape[0] != len(events):
             raise ValueError('The number of epochs and the number of events'
                              'must match')
-        info = deepcopy(info)  # do not modify original info
+        info = info.copy()  # do not modify original info
         tmax = (data.shape[2] - 1) / info['sfreq'] + tmin
         if event_id is None:  # convert to int to make typing-checks happy
             event_id = dict((str(e), int(e)) for e in np.unique(events[:, 2]))
@@ -1924,7 +1979,7 @@ class EpochsArray(_BaseEpochs):
                                           tmax, baseline, reject=reject,
                                           flat=flat, reject_tmin=reject_tmin,
                                           reject_tmax=reject_tmax, decim=1,
-                                          proj=proj)
+                                          proj=proj, on_missing=on_missing)
         if len(events) != np.in1d(self.events[:, 2],
                                   list(self.event_id.values())).sum():
             raise ValueError('The events must only contain event numbers from '
@@ -1936,7 +1991,7 @@ class EpochsArray(_BaseEpochs):
 
 
 def combine_event_ids(epochs, old_event_ids, new_event_id, copy=True):
-    """Collapse event_ids from an epochs instance into a new event_id
+    """Collapse event_ids from an epochs instance into a new event_id.
 
     Parameters
     ----------
@@ -1991,7 +2046,7 @@ def combine_event_ids(epochs, old_event_ids, new_event_id, copy=True):
 
 
 def equalize_epoch_counts(epochs_list, method='mintime'):
-    """Equalize the number of trials in multiple Epoch instances
+    """Equalize the number of trials in multiple Epoch instances.
 
     It tries to make the remaining epochs occurring as close as possible in
     time. This method works based on the idea that if there happened to be some
@@ -2018,7 +2073,7 @@ def equalize_epoch_counts(epochs_list, method='mintime'):
         list. If 'mintime', timing differences between each event list will be
         minimized.
     """
-    if not all(isinstance(e, _BaseEpochs) for e in epochs_list):
+    if not all(isinstance(e, BaseEpochs) for e in epochs_list):
         raise ValueError('All inputs must be Epochs instances')
 
     # make sure bad epochs are dropped
@@ -2032,7 +2087,7 @@ def equalize_epoch_counts(epochs_list, method='mintime'):
 
 
 def _get_drop_indices(event_times, method):
-    """Helper to get indices to drop from multiple event timing lists"""
+    """Get indices to drop from multiple event timing lists."""
     small_idx = np.argmin([e.shape[0] for e in event_times])
     small_e_times = event_times[small_idx]
     if method not in ['mintime', 'truncate']:
@@ -2051,14 +2106,14 @@ def _get_drop_indices(event_times, method):
 
 
 def _fix_fill(fill):
-    """Helper to fix bug on old scipy"""
+    """Fix bug on old scipy."""
     if LooseVersion(scipy.__version__) < LooseVersion('0.12'):
         fill = fill[:, np.newaxis]
     return fill
 
 
 def _minimize_time_diff(t_shorter, t_longer):
-    """Find a boolean mask to minimize timing differences"""
+    """Find a boolean mask to minimize timing differences."""
     from scipy.interpolate import interp1d
     keep = np.ones((len(t_longer)), dtype=bool)
     if len(t_shorter) == 0:
@@ -2095,9 +2150,10 @@ def _minimize_time_diff(t_shorter, t_longer):
 @verbose
 def _is_good(e, ch_names, channel_type_idx, reject, flat, full_report=False,
              ignore_chs=[], verbose=None):
-    """Test if data segment e is good according to the criteria
-    defined in reject and flat. If full_report=True, it will give
-    True/False as well as a list of all offending channels.
+    """Test if data segment e is good according to reject and flat.
+
+    If full_report=True, it will give True/False as well as a list of all
+    offending channels.
     """
     bad_list = list()
     has_printed = False
@@ -2136,13 +2192,11 @@ def _is_good(e, ch_names, channel_type_idx, reject, flat, full_report=False,
             return False, bad_list
 
 
-def _read_one_epoch_file(f, tree, fname, preload):
-    """Helper to read a single FIF file"""
-
+def _read_one_epoch_file(f, tree, preload):
+    """Read a single FIF file."""
     with f as fid:
         #   Read the measurement info
         info, meas = read_meas_info(fid, tree, clean_bads=True)
-        info['filename'] = fname
 
         events, mappings = _read_events_fif(fid, tree)
 
@@ -2164,7 +2218,6 @@ def _read_one_epoch_file(f, tree, fname, preload):
         my_epochs = epochs_node[0]
 
         # Now find the data in the block
-        name = None
         data = None
         data_tag = None
         bmin, bmax = None, None
@@ -2180,9 +2233,6 @@ def _read_one_epoch_file(f, tree, fname, preload):
             elif kind == FIFF.FIFF_LAST_SAMPLE:
                 tag = read_tag(fid, pos)
                 last = int(tag.data)
-            elif kind == FIFF.FIFF_COMMENT:
-                tag = read_tag(fid, pos)
-                name = tag.data
             elif kind == FIFF.FIFF_EPOCH:
                 # delay reading until later
                 fid.seek(pos, 0)
@@ -2208,9 +2258,9 @@ def _read_one_epoch_file(f, tree, fname, preload):
 
         n_samp = last - first + 1
         logger.info('    Found the data of interest:')
-        logger.info('        t = %10.2f ... %10.2f ms (%s)'
+        logger.info('        t = %10.2f ... %10.2f ms'
                     % (1000 * first / info['sfreq'],
-                       1000 * last / info['sfreq'], name))
+                       1000 * last / info['sfreq']))
         if info['comps'] is not None:
             logger.info('        %d CTF compensation matrices available'
                         % len(info['comps']))
@@ -2246,14 +2296,13 @@ def _read_one_epoch_file(f, tree, fname, preload):
         if drop_log is None:
             drop_log = [[] for _ in range(len(events))]
 
-    return (info, data, data_tag, events, event_id, tmin, tmax, baseline, name,
+    return (info, data, data_tag, events, event_id, tmin, tmax, baseline,
             selection, drop_log, epoch_shape, cals)
 
 
 @verbose
-def read_epochs(fname, proj=True, add_eeg_ref=None, preload=True,
-                verbose=None):
-    """Read epochs from a fif file
+def read_epochs(fname, proj=True, preload=True, verbose=None):
+    """Read epochs from a fif file.
 
     Parameters
     ----------
@@ -2270,28 +2319,26 @@ def read_epochs(fname, proj=True, add_eeg_ref=None, preload=True,
         detrending and temporal decimation will be postponed.
         If proj is False no projections will be applied which is the
         recommended value if SSPs are not used for cleaning the data.
-    add_eeg_ref : bool
-        If True, an EEG average reference will be added (unless one
-        already exists). This parameter is deprecated and will be
-        removed in 0.14, use :func:`mne.set_eeg_reference` instead.
     preload : bool
         If True, read all epochs from disk immediately. If False, epochs will
         be read on demand.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
-        Defaults to raw.verbose.
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
     epochs : instance of Epochs
         The epochs
     """
-    add_eeg_ref = _dep_eeg_ref(add_eeg_ref, False)
-    return EpochsFIF(fname, proj, add_eeg_ref, preload, verbose)
+    return EpochsFIF(fname, proj, preload, verbose)
 
 
 class _RawContainer(object):
-    def __init__(self, fid, data_tag, event_samps, epoch_shape, cals):
+    """Helper for a raw data container."""
+
+    def __init__(self, fid, data_tag, event_samps, epoch_shape,
+                 cals):  # noqa: D102
         self.fid = fid
         self.data_tag = data_tag
         self.event_samps = event_samps
@@ -2299,12 +2346,12 @@ class _RawContainer(object):
         self.cals = cals
         self.proj = False
 
-    def __del__(self):
+    def __del__(self):  # noqa: D105
         self.fid.close()
 
 
-class EpochsFIF(_BaseEpochs):
-    """Epochs read from disk
+class EpochsFIF(BaseEpochs):
+    """Epochs read from disk.
 
     Parameters
     ----------
@@ -2321,17 +2368,13 @@ class EpochsFIF(_BaseEpochs):
         detrending and temporal decimation will be postponed.
         If proj is False no projections will be applied which is the
         recommended value if SSPs are not used for cleaning the data.
-    add_eeg_ref : bool
-        If True, an EEG average reference will be added (unless one
-        already exists). The default value of True in 0.13 will change to
-        False in 0.14, and the parameter will be removed in 0.15. Use
-        :func:`mne.set_eeg_reference` instead.
     preload : bool
         If True, read all epochs from disk immediately. If False, epochs will
         be read on demand.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
-        Defaults to raw.verbose.
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more). Defaults to
+        raw.verbose.
 
     See Also
     --------
@@ -2339,9 +2382,10 @@ class EpochsFIF(_BaseEpochs):
     mne.epochs.combine_event_ids
     mne.Epochs.equalize_event_counts
     """
+
     @verbose
-    def __init__(self, fname, proj=True, add_eeg_ref=None, preload=True,
-                 verbose=None):
+    def __init__(self, fname, proj=True, preload=True,
+                 verbose=None):  # noqa: D102
         check_fname(fname, 'epochs', ('-epo.fif', '-epo.fif.gz'))
         fnames = [fname]
         ep_list = list()
@@ -2351,11 +2395,11 @@ class EpochsFIF(_BaseEpochs):
             fid, tree, _ = fiff_open(fname)
             next_fname = _get_next_fname(fid, fname, tree)
             (info, data, data_tag, events, event_id, tmin, tmax, baseline,
-             name, selection, drop_log, epoch_shape, cals) = \
-                _read_one_epoch_file(fid, tree, fname, preload)
+             selection, drop_log, epoch_shape, cals) = \
+                _read_one_epoch_file(fid, tree, preload)
             # here we ignore missing events, since users should already be
             # aware of missing events if they have saved data that way
-            epoch = _BaseEpochs(
+            epoch = BaseEpochs(
                 info, data, events, event_id, tmin, tmax, baseline,
                 on_missing='ignore', selection=selection, drop_log=drop_log,
                 proj=False, verbose=False)
@@ -2386,19 +2430,19 @@ class EpochsFIF(_BaseEpochs):
                         drop_log[k] = b
         drop_log = drop_log[:step]
 
-        # call _BaseEpochs constructor
+        # call BaseEpochs constructor
         super(EpochsFIF, self).__init__(
             info, data, events, event_id, tmin, tmax, baseline, raw=raw,
-            name=name, proj=proj, add_eeg_ref=add_eeg_ref,
-            preload_at_end=False, on_missing='ignore', selection=selection,
-            drop_log=drop_log, verbose=verbose)
+            proj=proj, preload_at_end=False, on_missing='ignore',
+            selection=selection, drop_log=drop_log, filename=fname,
+            verbose=verbose)
         # use the private property instead of drop_bad so that epochs
         # are not all read from disk for preload=False
         self._bad_dropped = True
 
     @verbose
     def _get_epoch_from_raw(self, idx, verbose=None):
-        """Load one epoch from disk"""
+        """Load one epoch from disk."""
         # Find the right file and offset to use
         event_samp = self.events[idx, 0]
         for raw in self._raw:
@@ -2429,7 +2473,7 @@ class EpochsFIF(_BaseEpochs):
 
 
 def bootstrap(epochs, random_state=None):
-    """Compute epochs selected by bootstrapping
+    """Compute epochs selected by bootstrapping.
 
     Parameters
     ----------
@@ -2457,7 +2501,7 @@ def bootstrap(epochs, random_state=None):
 
 
 def _check_merge_epochs(epochs_list):
-    """Aux function"""
+    """Aux function."""
     if len(set(tuple(epochs.event_id.items()) for epochs in epochs_list)) != 1:
         raise NotImplementedError("Epochs with unequal values for event_id")
     if len(set(epochs.tmin for epochs in epochs_list)) != 1:
@@ -2469,31 +2513,23 @@ def _check_merge_epochs(epochs_list):
 
 
 @verbose
-def add_channels_epochs(epochs_list, name='Unknown', add_eeg_ref=None,
-                        verbose=None):
-    """Concatenate channels, info and data from two Epochs objects
+def add_channels_epochs(epochs_list, verbose=None):
+    """Concatenate channels, info and data from two Epochs objects.
 
     Parameters
     ----------
     epochs_list : list of Epochs
         Epochs object to concatenate.
-    name : str
-        Comment that describes the Epochs data created.
-    add_eeg_ref : bool
-        If True, an EEG average reference will be added (unless there is
-        no EEG in the data). The default value of True in 0.13 will change to
-        False in 0.14, and the parameter will be removed in 0.15. Use
-        :func:`mne.set_eeg_reference` instead.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
-        Defaults to True if any of the input epochs have verbose=True.
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more). Defaults to
+        True if any of the input epochs have verbose=True.
 
     Returns
     -------
     epochs : instance of Epochs
         Concatenated epochs.
     """
-    add_eeg_ref = _dep_eeg_ref(add_eeg_ref)
     if not all(e.preload for e in epochs_list):
         raise ValueError('All epochs must be preloaded.')
 
@@ -2516,7 +2552,7 @@ def add_channels_epochs(epochs_list, name='Unknown', add_eeg_ref=None,
     if not all_same:
         raise ValueError('Events must be the same.')
 
-    proj = any(e.proj for e in epochs_list) or add_eeg_ref
+    proj = any(e.proj for e in epochs_list)
 
     if verbose is None:
         verbose = any(e.verbose for e in epochs_list)
@@ -2524,19 +2560,18 @@ def add_channels_epochs(epochs_list, name='Unknown', add_eeg_ref=None,
     epochs = epochs_list[0].copy()
     epochs.info = info
     epochs.picks = None
-    epochs.name = name
     epochs.verbose = verbose
     epochs.events = events
     epochs.preload = True
     epochs._bad_dropped = True
     epochs._data = data
-    epochs._projector, epochs.info = setup_proj(epochs.info, add_eeg_ref,
+    epochs._projector, epochs.info = setup_proj(epochs.info, False,
                                                 activate=proj)
     return epochs
 
 
 def _compare_epochs_infos(info1, info2, ind):
-    """Compare infos"""
+    """Compare infos."""
     info1._check_consistency()
     info2._check_consistency()
     if info1['nchan'] != info2['nchan']:
@@ -2572,7 +2607,7 @@ def _concatenate_epochs(epochs_list, with_data=True):
         raise TypeError('epochs_list must be a list or tuple, got %s'
                         % (type(epochs_list),))
     for ei, epochs in enumerate(epochs_list):
-        if not isinstance(epochs, _BaseEpochs):
+        if not isinstance(epochs, BaseEpochs):
             raise TypeError('epochs_list[%d] must be an instance of Epochs, '
                             'got %s' % (ei, type(epochs)))
     out = epochs_list[0]
@@ -2584,6 +2619,9 @@ def _concatenate_epochs(epochs_list, with_data=True):
     drop_log = deepcopy(out.drop_log)
     event_id = deepcopy(out.event_id)
     selection = out.selection
+    # offset is the last epoch + tmax + 10 second
+    events_offset = (np.max(out.events[:, 0]) +
+                     int((10 + tmax) * epochs.info['sfreq']))
     for ii, epochs in enumerate(epochs_list[1:]):
         _compare_epochs_infos(epochs.info, info, ii)
         if not np.allclose(epochs.times, epochs_list[0].times):
@@ -2592,9 +2630,26 @@ def _concatenate_epochs(epochs_list, with_data=True):
         if epochs.baseline != baseline:
             raise ValueError('Baseline must be same for all epochs')
 
+        # compare event_id
+        common_keys = list(set(event_id).intersection(set(epochs.event_id)))
+        for key in common_keys:
+            if not event_id[key] == epochs.event_id[key]:
+                msg = ('event_id values must be the same for identical keys '
+                       'for all concatenated epochs. Key "{}" maps to {} in '
+                       'some epochs and to {} in others.')
+                raise ValueError(msg.format(key, event_id[key],
+                                            epochs.event_id[key]))
+
         if with_data:
             data.append(epochs.get_data())
-        events.append(epochs.events)
+        evs = epochs.events.copy()
+        # add offset
+        evs[:, 0] += events_offset
+        # Update offset for the next iteration.
+        # offset is the last epoch + tmax + 10 second
+        events_offset += (np.max(evs[:, 0]) +
+                          int((10 + tmax) * epochs.info['sfreq']))
+        events.append(evs)
         selection = np.concatenate((selection, epochs.selection))
         drop_log.extend(epochs.drop_log)
         event_id.update(epochs.event_id)
@@ -2607,10 +2662,9 @@ def _concatenate_epochs(epochs_list, with_data=True):
 
 def _finish_concat(info, data, events, event_id, tmin, tmax, baseline,
                    selection, drop_log, verbose):
-    """Helper to finish concatenation for epochs not read from disk"""
-    events[:, 0] = np.arange(len(events))  # arbitrary after concat
+    """Finish concatenation for epochs not read from disk."""
     selection = np.where([len(d) == 0 for d in drop_log])[0]
-    out = _BaseEpochs(
+    out = BaseEpochs(
         info, data, events, event_id, tmin, tmax, baseline=baseline,
         selection=selection, drop_log=drop_log, proj=False,
         on_missing='ignore', verbose=verbose)
@@ -2619,7 +2673,7 @@ def _finish_concat(info, data, events, event_id, tmin, tmax, baseline,
 
 
 def concatenate_epochs(epochs_list):
-    """Concatenate a list of epochs into one epochs object
+    """Concatenate a list of epochs into one epochs object.
 
     Parameters
     ----------
@@ -2643,7 +2697,7 @@ def average_movements(epochs, head_pos=None, orig_sfreq=None, picks=None,
                       origin='auto', weight_all=True, int_order=8, ext_order=3,
                       destination=None, ignore_ref=False, return_mapping=False,
                       mag_scale=100., verbose=None):
-    """Average data using Maxwell filtering, transforming using head positions
+    u"""Average data using Maxwell filtering, transforming using head positions.
 
     Parameters
     ----------
@@ -2705,7 +2759,8 @@ def average_movements(epochs, head_pos=None, orig_sfreq=None, picks=None,
         .. versionadded:: 0.13
 
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -2739,7 +2794,7 @@ def average_movements(epochs, head_pos=None, orig_sfreq=None, picks=None,
     .. [2] Wehner DT, Hämäläinen MS, Mody M, Ahlfors SP. "Head movements
            of children in MEG: Quantification, effects on source
            estimation, and compensation. NeuroImage 40:541–550, 2008.
-    """
+    """  # noqa: E501
     from .preprocessing.maxwell import (_trans_sss_basis, _reset_meg_bads,
                                         _check_usable, _col_norm_pinv,
                                         _get_n_moments, _get_mf_picks,
@@ -2748,7 +2803,7 @@ def average_movements(epochs, head_pos=None, orig_sfreq=None, picks=None,
     if head_pos is None:
         raise TypeError('head_pos must be provided and cannot be None')
     from .chpi import head_pos_to_trans_rot_t
-    if not isinstance(epochs, _BaseEpochs):
+    if not isinstance(epochs, BaseEpochs):
         raise TypeError('epochs must be an instance of Epochs, not %s'
                         % (type(epochs),))
     orig_sfreq = epochs.info['sfreq'] if orig_sfreq is None else orig_sfreq
@@ -2849,7 +2904,8 @@ def average_movements(epochs, head_pos=None, orig_sfreq=None, picks=None,
         data[meg_picks] = np.dot(mapping, data[good_picks])
     info_to['dev_head_t'] = recon_trans  # set the reconstruction transform
     evoked = epochs._evoked_from_epoch_data(data, info_to, picks,
-                                            n_events=count, kind='average')
+                                            n_events=count, kind='average',
+                                            comment=epochs._name)
     _remove_meg_projs(evoked)  # remove MEG projectors, they won't apply now
     logger.info('Created Evoked dataset from %s epochs' % (count,))
     return (evoked, mapping) if return_mapping else evoked
@@ -2857,8 +2913,7 @@ def average_movements(epochs, head_pos=None, orig_sfreq=None, picks=None,
 
 @verbose
 def _segment_raw(raw, segment_length=1., verbose=None, **kwargs):
-    """Divide continuous raw data into equal-sized
-    consecutive epochs.
+    """Divide continuous raw data into equal-sized consecutive epochs.
 
     Parameters
     ----------
@@ -2878,4 +2933,4 @@ def _segment_raw(raw, segment_length=1., verbose=None, **kwargs):
     """
     events = make_fixed_length_events(raw, 1, duration=segment_length)
     return Epochs(raw, events, event_id=[1], tmin=0., tmax=segment_length,
-                  verbose=verbose, baseline=None, add_eeg_ref=False, **kwargs)
+                  verbose=verbose, baseline=None, **kwargs)
diff --git a/mne/event.py b/mne/event.py
index e2084d5..faa8a42 100644
--- a/mne/event.py
+++ b/mne/event.py
@@ -1,5 +1,4 @@
-"""IO with fif files containing events
-"""
+"""IO with fif files containing events."""
 
 # Authors: Alexandre Gramfort <alexandre.gramfort at telecom-paristech.fr>
 #          Matti Hamalainen <msh at nmr.mgh.harvard.edu>
@@ -19,10 +18,11 @@ from .io.tag import read_tag
 from .io.open import fiff_open
 from .io.write import write_int, start_block, start_file, end_block, end_file
 from .io.pick import pick_channels
+from .externals.six import string_types
 
 
 def pick_events(events, include=None, exclude=None, step=False):
-    """Select some events
+    """Select some events.
 
     Parameters
     ----------
@@ -75,7 +75,7 @@ def pick_events(events, include=None, exclude=None, step=False):
 
 def define_target_events(events, reference_id, target_id, sfreq, tmin, tmax,
                          new_id=None, fill_na=None):
-    """Define new events by co-occurrence of existing events
+    """Define new events by co-occurrence of existing events.
 
     This function can be used to evaluate events depending on the
     temporal lag to another event. For example, this can be used to
@@ -110,7 +110,6 @@ def define_target_events(events, reference_id, target_id, sfreq, tmin, tmax,
     lag : ndarray
         time lag between reference and target in milliseconds.
     """
-
     if new_id is None:
         new_id = reference_id
 
@@ -148,7 +147,7 @@ def define_target_events(events, reference_id, target_id, sfreq, tmin, tmax,
 
 
 def _read_events_fif(fid, tree):
-    """Aux function"""
+    """Aux function."""
     #   Find the desired block
     events = dir_tree_find(tree, FIFF.FIFFB_MNE_EVENTS)
 
@@ -191,7 +190,10 @@ def _read_events_fif(fid, tree):
 
 def read_events(filename, include=None, exclude=None, mask=None,
                 mask_type=None):
-    """Reads events from fif or text file
+    """Read events from fif or text file.
+
+    See :ref:`tut_epoching_and_averaging` as well as :ref:`ex_read_events`
+    for more information about events.
 
     Parameters
     ----------
@@ -214,7 +216,8 @@ def read_events(filename, include=None, exclude=None, mask=None,
         If None (default), no masking is performed.
     mask_type: 'and' | 'not_and'
         The type of operation between the mask and the trigger.
-        Choose 'and' for MNE-C masking behavior.
+        Choose 'and' for MNE-C masking behavior. The default ('not_and')
+        will change to 'and' in 0.16.
 
         .. versionadded:: 0.13
 
@@ -241,14 +244,14 @@ def read_events(filename, include=None, exclude=None, mask=None,
     ext = splitext(filename)[1].lower()
     if ext == '.fif' or ext == '.gz':
         fid, tree, _ = fiff_open(filename)
-        try:
-            event_list, _ = _read_events_fif(fid, tree)
-        finally:
-            fid.close()
+        with fid as f:
+            event_list, _ = _read_events_fif(f, tree)
+        # hack fix for windows to avoid bincount problems
+        event_list = event_list.astype(int)
     else:
         #  Have to read this in as float64 then convert because old style
         #  eve/lst files had a second float column that will raise errors
-        lines = np.loadtxt(filename, dtype=np.float64).astype(np.uint32)
+        lines = np.loadtxt(filename, dtype=np.float64).astype(int)
         if len(lines) == 0:
             raise ValueError('No text lines found')
 
@@ -280,7 +283,7 @@ def read_events(filename, include=None, exclude=None, mask=None,
 
 
 def write_events(filename, event_list):
-    """Write events to file
+    """Write events to file.
 
     Parameters
     ----------
@@ -366,7 +369,7 @@ def _find_stim_steps(data, first_samp, pad_start=None, pad_stop=None, merge=0):
 
 def find_stim_steps(raw, pad_start=None, pad_stop=None, merge=0,
                     stim_channel=None):
-    """Find all steps in data from a stim channel
+    """Find all steps in data from a stim channel.
 
     Parameters
     ----------
@@ -402,7 +405,6 @@ def find_stim_steps(raw, pad_start=None, pad_stop=None, merge=0,
     --------
     find_events : More sophisticated options for finding events in a Raw file.
     """
-
     # pull stim channel from config if necessary
     stim_channel = _get_stim_channel(stim_channel, raw.info)
 
@@ -420,9 +422,11 @@ def find_stim_steps(raw, pad_start=None, pad_stop=None, merge=0,
 
 
 def _find_events(data, first_samp, verbose=None, output='onset',
-                 consecutive='increasing', min_samples=0, mask=0,
+                 consecutive='increasing', min_samples=0, mask=None,
                  uint_cast=False, mask_type=None):
-    """Helper function for find events"""
+    """Help find events."""
+    assert data.shape[0] == 1  # data should be only a row vector
+
     if min_samples > 0:
         merge = int(min_samples // 1)
         if merge == min_samples:
@@ -482,7 +486,7 @@ def _find_events(data, first_samp, verbose=None, output='onset',
         events[:, 2] = event_id
         events[:, 0] -= 1
     else:
-        raise Exception("Invalid output parameter %r" % output)
+        raise ValueError("Invalid output parameter %r" % output)
 
     logger.info("%s events found" % len(events))
     logger.info("Events id: %s" % np.unique(events[:, 2]))
@@ -490,12 +494,27 @@ def _find_events(data, first_samp, verbose=None, output='onset',
     return events
 
 
+def _find_unique_events(events):
+    """Uniquify events (ie remove duplicated rows."""
+    e = np.ascontiguousarray(events).view(
+        np.dtype((np.void, events.dtype.itemsize * events.shape[1])))
+    _, idx = np.unique(e, return_index=True)
+    n_dupes = len(events) - len(idx)
+    if n_dupes > 0:
+        warn("Some events are duplicated in your different stim channels."
+             " %d events were ignored during deduplication." % n_dupes)
+    return events[idx]
+
+
 @verbose
 def find_events(raw, stim_channel=None, output='onset',
                 consecutive='increasing', min_duration=0,
                 shortest_event=2, mask=None, uint_cast=False,
                 mask_type=None, verbose=None):
-    """Find events from raw file
+    """Find events from raw file.
+
+    See :ref:`tut_epoching_and_averaging` as well as :ref:`ex_read_events`
+    for more information about events.
 
     Parameters
     ----------
@@ -503,11 +522,13 @@ def find_events(raw, stim_channel=None, output='onset',
         The raw data.
     stim_channel : None | string | list of string
         Name of the stim channel or all the stim channels
-        affected by the trigger. If None, the config variables
+        affected by triggers. If None, the config variables
         'MNE_STIM_CHANNEL', 'MNE_STIM_CHANNEL_1', 'MNE_STIM_CHANNEL_2',
         etc. are read. If these are not found, it will fall back to
         'STI 014' if present, then fall back to the first channel of type
-        'stim', if present.
+        'stim', if present. If multiple channels are provided
+        then the returned events are the union of all the events
+        extracted from individual stim channels.
     output : 'onset' | 'offset' | 'step'
         Whether to report when events start, when events end, or both.
     consecutive : bool | 'increasing'
@@ -537,12 +558,14 @@ def find_events(raw, stim_channel=None, output='onset',
 
     mask_type: 'and' | 'not_and'
         The type of operation between the mask and the trigger.
-        Choose 'and' for MNE-C masking behavior.
+        Choose 'and' for MNE-C masking behavior. The default ('not_and')
+        will change to 'and' in 0.16.
 
         .. versionadded:: 0.13
 
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -645,30 +668,49 @@ def find_events(raw, stim_channel=None, output='onset',
     # pull stim channel from config if necessary
     stim_channel = _get_stim_channel(stim_channel, raw.info)
 
-    pick = pick_channels(raw.info['ch_names'], include=stim_channel)
-    if len(pick) == 0:
+    picks = pick_channels(raw.info['ch_names'], include=stim_channel)
+    if len(picks) == 0:
         raise ValueError('No stim channel found to extract event triggers.')
-    data, _ = raw[pick, :]
-
-    events = _find_events(data, raw.first_samp, verbose=verbose, output=output,
-                          consecutive=consecutive, min_samples=min_samples,
-                          mask=mask, uint_cast=uint_cast, mask_type=mask_type)
-
-    # add safety check for spurious events (for ex. from neuromag syst.) by
-    # checking the number of low sample events
-    n_short_events = np.sum(np.diff(events[:, 0]) < shortest_event)
-    if n_short_events > 0:
-        raise ValueError("You have %i events shorter than the "
-                         "shortest_event. These are very unusual and you "
-                         "may want to set min_duration to a larger value e.g."
-                         " x / raw.info['sfreq']. Where x = 1 sample shorter "
-                         "than the shortest event length." % (n_short_events))
+    data, _ = raw[picks, :]
 
+    events_list = []
+    for d in data:
+        events = _find_events(d[np.newaxis, :], raw.first_samp,
+                              verbose=verbose, output=output,
+                              consecutive=consecutive, min_samples=min_samples,
+                              mask=mask, uint_cast=uint_cast,
+                              mask_type=mask_type)
+        # add safety check for spurious events (for ex. from neuromag syst.) by
+        # checking the number of low sample events
+        n_short_events = np.sum(np.diff(events[:, 0]) < shortest_event)
+        if n_short_events > 0:
+            raise ValueError("You have %i events shorter than the "
+                             "shortest_event. These are very unusual and you "
+                             "may want to set min_duration to a larger value "
+                             "e.g. x / raw.info['sfreq']. Where x = 1 sample "
+                             "shorter than the shortest event "
+                             "length." % (n_short_events))
+
+        events_list.append(events)
+
+    events = np.concatenate(events_list, axis=0)
+    events = _find_unique_events(events)
+    events = events[np.argsort(events[:, 0])]
     return events
 
 
 def _mask_trigs(events, mask, mask_type):
-    """Helper function for masking digital trigger values"""
+    """Mask digital trigger values."""
+    if mask_type is None:
+        mask_type = 'not_and'
+        if mask is not None:
+            warn('The default mask type "not_and" will change to "and" in '
+                 '0.16, set it explicitly to avoid this warning.',
+                 DeprecationWarning)
+    if not isinstance(mask_type, string_types) or \
+            mask_type not in ('not_and', 'and'):
+        raise ValueError('mask_type must be "not_and" or "and", got %s'
+                         % (mask_type,))
     if mask is not None:
         if not isinstance(mask, int):
             raise TypeError('You provided a(n) %s.' % type(mask) +
@@ -678,10 +720,6 @@ def _mask_trigs(events, mask, mask_type):
         return events.copy()
 
     if mask is not None:
-        if mask_type is None:
-            warn("The default setting for mask_type will change from "
-                 "'not and' to 'and' in v0.14.", DeprecationWarning)
-            mask_type = 'not_and'
         if mask_type == 'not_and':
             mask = np.bitwise_not(mask)
         elif mask_type != 'and':
@@ -695,7 +733,7 @@ def _mask_trigs(events, mask, mask_type):
 
 
 def merge_events(events, ids, new_id, replace_events=True):
-    """Merge a set of events
+    """Merge a set of events.
 
     Parameters
     ----------
@@ -756,7 +794,7 @@ def merge_events(events, ids, new_id, replace_events=True):
 
 
 def shift_time_events(events, ids, tshift, sfreq):
-    """Shift an event
+    """Shift an event.
 
     Parameters
     ----------
@@ -783,7 +821,7 @@ def shift_time_events(events, ids, tshift, sfreq):
 
 def make_fixed_length_events(raw, id, start=0, stop=None, duration=1.,
                              first_samp=True):
-    """Make a set of events separated by a fixed duration
+    """Make a set of events separated by a fixed duration.
 
     Parameters
     ----------
@@ -810,8 +848,8 @@ def make_fixed_length_events(raw, id, start=0, stop=None, duration=1.,
     new_events : array
         The new events.
     """
-    from .io.base import _BaseRaw
-    if not isinstance(raw, _BaseRaw):
+    from .io.base import BaseRaw
+    if not isinstance(raw, BaseRaw):
         raise ValueError('Input data must be an instance of Raw, got'
                          ' %s instead.' % (type(raw)))
     if not isinstance(id, int):
@@ -843,22 +881,19 @@ def make_fixed_length_events(raw, id, start=0, stop=None, duration=1.,
 
 
 def concatenate_events(events, first_samps, last_samps):
-    """Concatenate event lists in a manner compatible with
-    concatenate_raws
+    """Concatenate event lists to be compatible with concatenate_raws.
 
     This is useful, for example, if you processed and/or changed
     events in raw files separately before combining them using
-    concatenate_raws.
+    :func:`mne.concatenate_raws`.
 
     Parameters
     ----------
     events : list of arrays
         List of event arrays, typically each extracted from a
         corresponding raw file that is being concatenated.
-
     first_samps : list or array of int
         First sample numbers of the raw files concatenated.
-
     last_samps : list or array of int
         Last sample numbers of the raw files concatenated.
 
@@ -866,6 +901,10 @@ def concatenate_events(events, first_samps, last_samps):
     -------
     events : array
         The concatenated events.
+
+    See Also
+    --------
+    mne.concatenate_raws
     """
     if not isinstance(events, list):
         raise ValueError('events must be a list of arrays')
@@ -889,7 +928,7 @@ def concatenate_events(events, first_samps, last_samps):
 
 
 class AcqParserFIF(object):
-    """ Parser for Elekta data acquisition settings.
+    """Parser for Elekta data acquisition settings.
 
     This class parses parameters (e.g. events and averaging categories) that
     are defined in the Elekta TRIUX/VectorView data acquisition software (DACQ)
@@ -917,6 +956,10 @@ class AcqParserFIF(object):
     acq_dict : dict
         All DACQ parameters.
 
+    See Also
+    --------
+    mne.io.Raw.acqparser : access the parser through a Raw attribute
+
     Notes
     -----
     Any averaging category (also non-active ones) can be accessed by indexing
@@ -937,7 +980,7 @@ class AcqParserFIF(object):
     _event_vars_compat = ('Comment', 'Delay')
 
     _cat_vars = ('Comment', 'Display', 'Start', 'State', 'End', 'Event',
-                 'Nave', 'ReqEvent', 'ReqWhen', 'ReqWithin',  'SubAve')
+                 'Nave', 'ReqEvent', 'ReqWhen', 'ReqWithin', 'SubAve')
 
     # new versions only (DACQ >= 3.4)
     _dacq_vars = _dacq_vars_compat + ('magMax', 'magMin', 'magNoise',
@@ -946,11 +989,11 @@ class AcqParserFIF(object):
     _event_vars = _event_vars_compat + ('Name', 'Channel', 'NewBits',
                                         'OldBits', 'NewMask', 'OldMask')
 
-    def __init__(self, info):
+    def __init__(self, info):  # noqa: D102
         acq_pars = info['acq_pars']
         if not acq_pars:
             raise ValueError('No acquisition parameters')
-        self.acq_dict = self._acqpars_dict(acq_pars)
+        self.acq_dict = dict(self._acqpars_gen(acq_pars))
         if 'ERFversion' in self.acq_dict:
             self.compat = False  # DACQ ver >= 3.4
         elif 'ERFncateg' in self.acq_dict:  # probably DACQ < 3.4
@@ -990,7 +1033,7 @@ class AcqParserFIF(object):
         self.flat = {k: float(v) for k, v in self.flat.items()
                      if float(v) > 0}
 
-    def __repr__(self):
+    def __repr__(self):  # noqa: D105
         s = '<AcqParserFIF | '
         s += 'categories: %d ' % self.ncateg
         cats_in_use = len(self._categories_in_use)
@@ -1006,7 +1049,7 @@ class AcqParserFIF(object):
         return s
 
     def __getitem__(self, item):
-        """ Return an averaging category, or list of categories.
+        """Return an averaging category, or list of categories.
 
         Parameters
         ----------
@@ -1064,15 +1107,16 @@ class AcqParserFIF(object):
         return cats[0] if len(cats) == 1 else cats
 
     def __len__(self):
-        """ Return number of averaging categories marked active in DACQ. """
+        """Return number of averaging categories marked active in DACQ."""
         return len(self.categories)
 
     def _events_from_acq_pars(self):
-        """ Collect DACQ events into a dict.
+        """Collect DACQ events into a dict.
 
         Events are keyed by number starting from 1 (DACQ index of event).
         Each event is itself represented by a dict containing the event
-        parameters. """
+        parameters.
+        """
         # lookup table for event number -> bits for old DACQ versions
         _compat_event_lookup = {1: 1, 2: 2, 3: 4, 4: 8, 5: 16, 6: 32, 7: 3,
                                 8: 5, 9: 6, 10: 7, 11: 9, 12: 10, 13: 11,
@@ -1106,13 +1150,8 @@ class AcqParserFIF(object):
             events[evnum] = evdi
         return events
 
-    def _acqpars_dict(self, acq_pars):
-        """ Parse `` info['acq_pars']`` into a dict. """
-        return dict(self._acqpars_gen(acq_pars))
-
     def _acqpars_gen(self, acq_pars):
-        """ Yields key/value pairs from ``info['acq_pars'])``. """
-        # DACQ variable names always start with one of these
+        """Yield key/value pairs from ``info['acq_pars'])``."""
         key, val = '', ''
         for line in acq_pars.split():
             if any([line.startswith(x) for x in self._acq_var_magic]):
@@ -1126,10 +1165,11 @@ class AcqParserFIF(object):
             yield key, val
 
     def _categories_from_acq_pars(self):
-        """ Collect DACQ averaging categories into a dict.
+        """Collect DACQ averaging categories into a dict.
 
         Categories are keyed by the comment field in DACQ. Each category is
-        itself represented a dict containing the category parameters. """
+        itself represented a dict containing the category parameters.
+        """
         cats = dict()
         for catnum in [str(x).zfill(2) for x in range(1, self.nevent + 1)]:
             catdi = dict()
@@ -1152,14 +1192,15 @@ class AcqParserFIF(object):
         return cats
 
     def _events_mne_to_dacq(self, mne_events):
-        """ Creates list of DACQ events based on mne trigger transitions list.
+        """Create list of DACQ events based on mne trigger transitions list.
 
         mne_events is typically given by mne.find_events (use consecutive=True
         to get all transitions). Output consists of rows in the form
         [t, 0, event_codes] where t is time in samples and event_codes is all
         DACQ events compatible with the transition, bitwise ORed together:
         e.g. [t1, 0, 5] means that events 1 and 3 occurred at time t1,
-        as 2**(1 - 1) + 2**(3 - 1) = 5. """
+        as 2**(1 - 1) + 2**(3 - 1) = 5.
+        """
         events_ = mne_events.copy()
         events_[:, 1:3] = 0
         for n, ev in self._events.items():
@@ -1175,13 +1216,12 @@ class AcqParserFIF(object):
         return events_
 
     def _mne_events_to_category_t0(self, cat, mne_events, sfreq):
-        """ Translate mne_events to epoch zero times (t0).
+        """Translate mne_events to epoch zero times (t0).
 
         First mne events (trigger transitions) are converted into DACQ events.
         Then the zero times for the epochs are obtained by considering the
         reference and conditional (required) events and the delay to stimulus.
         """
-
         cat_ev = cat['event']
         cat_reqev = cat['reqevent']
         # first convert mne events to dacq event list
@@ -1216,7 +1256,7 @@ class AcqParserFIF(object):
 
     @property
     def categories(self):
-        """ Return list of averaging categories ordered by DACQ index.
+        """Return list of averaging categories ordered by DACQ index.
 
         Only returns categories marked active in DACQ.
         """
@@ -1226,7 +1266,7 @@ class AcqParserFIF(object):
 
     @property
     def events(self):
-        """ Return events ordered by DACQ index.
+        """Return events ordered by DACQ index.
 
         Only returns events that are in use (referred to by a category).
         """
@@ -1243,7 +1283,7 @@ class AcqParserFIF(object):
 
     def get_condition(self, raw, condition=None, stim_channel=None, mask=None,
                       uint_cast=None, mask_type='and', delayed_lookup=True):
-        """ Get averaging parameters for a condition (averaging category).
+        """Get averaging parameters for a condition (averaging category).
 
         Output is designed to be used with the Epochs class to extract the
         corresponding epochs.
@@ -1287,7 +1327,6 @@ class AcqParserFIF(object):
             transitions with a resolution of one sample, use
             delayed_lookup=False.
 
-
         Returns
         -------
         conds_data : dict or list of dict, each with following keys:
diff --git a/mne/evoked.py b/mne/evoked.py
index dd486e3..8ad286f 100644
--- a/mne/evoked.py
+++ b/mne/evoked.py
@@ -14,9 +14,10 @@ from .baseline import rescale
 from .channels.channels import (ContainsMixin, UpdateChannelsMixin,
                                 SetChannelsMixin, InterpolationMixin,
                                 equalize_channels)
-from .filter import resample, detrend, FilterMixin
+from .channels.layout import _merge_grad_data, _pair_grad_sensors
+from .filter import detrend, FilterMixin
 from .utils import (check_fname, logger, verbose, _time_mask, warn, sizeof_fmt,
-                    deprecated, SizeMixin, copy_function_doc_to_method_doc)
+                    SizeMixin, copy_function_doc_to_method_doc)
 from .viz import (plot_evoked, plot_evoked_topomap, plot_evoked_field,
                   plot_evoked_image, plot_evoked_topo)
 from .viz.evoked import (_plot_evoked_white, plot_evoked_joint,
@@ -45,7 +46,7 @@ _aspect_rev = {str(FIFF.FIFFV_ASPECT_AVERAGE): 'average',
 class Evoked(ProjMixin, ContainsMixin, UpdateChannelsMixin,
              SetChannelsMixin, InterpolationMixin, FilterMixin,
              ToDataFrameMixin, TimeMixin, SizeMixin):
-    """Evoked data
+    """Evoked data.
 
     Parameters
     ----------
@@ -55,16 +56,6 @@ class Evoked(ProjMixin, ContainsMixin, UpdateChannelsMixin,
     condition : int, or str
         Dataset ID number (int) or comment/name (str). Optional if there is
         only one data set in file.
-    baseline : tuple or list of length 2, or None
-        This parameter has been deprecated and will be removed in 0.14
-        Use inst.apply_baseline(baseline) instead.
-        The time interval to apply rescaling / baseline correction.
-        If None do not apply it. If baseline is (a, b)
-        the interval is between "a (s)" and "b (s)".
-        If a is None the beginning of the data is used
-        and if b is None then b is set to the end of the interval.
-        If baseline is equal to (None, None) all the time
-        interval is used. If None, no correction is applied.
     proj : bool, optional
         Apply SSP projection vectors
     kind : str
@@ -77,7 +68,8 @@ class Evoked(ProjMixin, ContainsMixin, UpdateChannelsMixin,
         SSS/tSSS to remove the compensation signals that may also affect brain
         activity. Can also be "yes" to load without eliciting a warning.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Attributes
     ----------
@@ -105,11 +97,12 @@ class Evoked(ProjMixin, ContainsMixin, UpdateChannelsMixin,
     Notes
     -----
     Evoked objects contain a single condition only.
-
     """
+
     @verbose
-    def __init__(self, fname, condition=None, baseline=None, proj=True,
-                 kind='average', allow_maxshield=False, verbose=None):
+    def __init__(self, fname, condition=None, proj=True,
+                 kind='average', allow_maxshield=False,
+                 verbose=None):  # noqa: D102
         if not isinstance(proj, bool):
             raise ValueError(r"'proj' must be 'True' or 'False'")
         # Read the requested data
@@ -118,14 +111,24 @@ class Evoked(ProjMixin, ContainsMixin, UpdateChannelsMixin,
                 fname, condition, kind, allow_maxshield)
         self.kind = _aspect_rev.get(str(self._aspect_kind), 'Unknown')
         self.verbose = verbose
+        self.preload = True
         # project and baseline correct
         if proj:
             self.apply_proj()
-        self.apply_baseline(baseline, self.verbose)
+
+    @property
+    def data(self):
+        """The data matrix."""
+        return self._data
+
+    @data.setter
+    def data(self, data):
+        """Set the data matrix."""
+        self._data = data
 
     @verbose
     def apply_baseline(self, baseline=(None, 0), verbose=None):
-        """Baseline correct evoked data
+        """Baseline correct evoked data.
 
         Parameters
         ----------
@@ -139,7 +142,9 @@ class Evoked(ProjMixin, ContainsMixin, UpdateChannelsMixin,
             from the data. The baseline (a, b) includes both endpoints, i.e.
             all timepoints t such that a <= t <= b.
         verbose : bool, str, int, or None
-            If not None, override default verbose level (see mne.verbose).
+            If not None, override default verbose level (see
+            :func:`mne.verbose` and :ref:`Logging documentation <tut_logging>`
+            for more).
 
         Returns
         -------
@@ -170,7 +175,7 @@ class Evoked(ProjMixin, ContainsMixin, UpdateChannelsMixin,
         """
         write_evokeds(fname, self)
 
-    def __repr__(self):
+    def __repr__(self):  # noqa: D105
         s = "comment : '%s'" % self.comment
         s += ', kind : %s' % self.kind
         s += ", time : [%f, %f]" % (self.times[0], self.times[-1])
@@ -181,11 +186,11 @@ class Evoked(ProjMixin, ContainsMixin, UpdateChannelsMixin,
 
     @property
     def ch_names(self):
-        """Channel names"""
+        """Channel names."""
         return self.info['ch_names']
 
     def crop(self, tmin=None, tmax=None):
-        """Crop data to a given time interval
+        """Crop data to a given time interval.
 
         Parameters
         ----------
@@ -212,7 +217,7 @@ class Evoked(ProjMixin, ContainsMixin, UpdateChannelsMixin,
         return self
 
     def decimate(self, decim, offset=0):
-        """Decimate the evoked data
+        """Decimate the evoked data.
 
         .. note:: No filtering is performed. To avoid aliasing, ensure
                   your data are properly lowpassed.
@@ -235,7 +240,7 @@ class Evoked(ProjMixin, ContainsMixin, UpdateChannelsMixin,
         --------
         Epochs.decimate
         Epochs.resample
-        Raw.resample
+        mne.io.Raw.resample
 
         Notes
         -----
@@ -255,7 +260,7 @@ class Evoked(ProjMixin, ContainsMixin, UpdateChannelsMixin,
         return self
 
     def shift_time(self, tshift, relative=True):
-        """Shift time scale in evoked data
+        """Shift time scale in evoked data.
 
         Parameters
         ----------
@@ -286,13 +291,14 @@ class Evoked(ProjMixin, ContainsMixin, UpdateChannelsMixin,
     def plot(self, picks=None, exclude='bads', unit=True, show=True, ylim=None,
              xlim='tight', proj=False, hline=None, units=None, scalings=None,
              titles=None, axes=None, gfp=False, window_title=None,
-             spatial_colors=False, zorder='unsorted', selectable=True):
+             spatial_colors=False, zorder='unsorted', selectable=True,
+             verbose=None):
         return plot_evoked(
             self, picks=picks, exclude=exclude, unit=unit, show=show,
             ylim=ylim, proj=proj, xlim=xlim, hline=hline, units=units,
             scalings=scalings, titles=titles, axes=axes, gfp=gfp,
             window_title=window_title, spatial_colors=spatial_colors,
-            zorder=zorder, selectable=selectable)
+            zorder=zorder, selectable=selectable, verbose=verbose)
 
     @copy_function_doc_to_method_doc(plot_evoked_image)
     def plot_image(self, picks=None, exclude='bads', unit=True, show=True,
@@ -306,9 +312,10 @@ class Evoked(ProjMixin, ContainsMixin, UpdateChannelsMixin,
     @copy_function_doc_to_method_doc(plot_evoked_topo)
     def plot_topo(self, layout=None, layout_scale=0.945, color=None,
                   border='none', ylim=None, scalings=None, title=None,
-                  proj=False, vline=[0.0], fig_facecolor='k',
-                  fig_background=None, axis_facecolor='k', font_color='w',
-                  merge_grads=False, show=True):
+                  proj=False, vline=[0.0], fig_facecolor=None,
+                  fig_background=None, axis_facecolor=None, font_color=None,
+                  merge_grads=False, legend=True, axes=None,
+                  background_color=None, show=True):
         """
 
         Notes
@@ -322,29 +329,28 @@ class Evoked(ProjMixin, ContainsMixin, UpdateChannelsMixin,
                                 fig_background=fig_background,
                                 axis_facecolor=axis_facecolor,
                                 font_color=font_color, merge_grads=merge_grads,
-                                show=show)
+                                legend=legend, axes=axes,
+                                background_color=background_color, show=show)
 
     @copy_function_doc_to_method_doc(plot_evoked_topomap)
     def plot_topomap(self, times="auto", ch_type=None, layout=None, vmin=None,
                      vmax=None, cmap=None, sensors=True, colorbar=True,
-                     scale=None, scale_time=1e3, unit=None, res=64, size=1,
-                     cbar_fmt="%3.1f", time_format='%01d ms', proj=False,
-                     show=True, show_names=False, title=None, mask=None,
-                     mask_params=None, outlines='head', contours=6,
-                     image_interp='bilinear', average=None, head_pos=None,
-                     axes=None):
-        return plot_evoked_topomap(self, times=times, ch_type=ch_type,
-                                   layout=layout, vmin=vmin, vmax=vmax,
-                                   cmap=cmap, sensors=sensors,
-                                   colorbar=colorbar, scale=scale,
-                                   scale_time=scale_time, unit=unit, res=res,
-                                   proj=proj, size=size, cbar_fmt=cbar_fmt,
-                                   time_format=time_format, show=show,
-                                   show_names=show_names, title=title,
-                                   mask=mask, mask_params=mask_params,
-                                   outlines=outlines, contours=contours,
-                                   image_interp=image_interp, average=average,
-                                   head_pos=head_pos, axes=axes)
+                     scalings=None, scaling_time=1e3, units=None, res=64,
+                     size=1, cbar_fmt="%3.1f", time_format='%01d ms',
+                     proj=False, show=True, show_names=False, title=None,
+                     mask=None, mask_params=None, outlines='head',
+                     contours=6, image_interp='bilinear', average=None,
+                     head_pos=None, axes=None, scale=None, scale_time=None,
+                     unit=None):
+        return plot_evoked_topomap(
+            self, times=times, ch_type=ch_type, layout=layout, vmin=vmin,
+            vmax=vmax, cmap=cmap, sensors=sensors, colorbar=colorbar,
+            scalings=scalings, scaling_time=scaling_time, units=units, res=res,
+            size=size, cbar_fmt=cbar_fmt, time_format=time_format,
+            proj=proj, show=show, show_names=show_names, title=title,
+            mask=mask, mask_params=mask_params, outlines=outlines, unit=unit,
+            contours=contours, image_interp=image_interp, average=average,
+            head_pos=head_pos, axes=axes, scale=scale, scale_time=scale_time)
 
     @copy_function_doc_to_method_doc(plot_evoked_field)
     def plot_field(self, surf_maps, time=None, time_label='t = %0.0f ms',
@@ -352,8 +358,8 @@ class Evoked(ProjMixin, ContainsMixin, UpdateChannelsMixin,
         return plot_evoked_field(self, surf_maps, time=time,
                                  time_label=time_label, n_jobs=n_jobs)
 
-    def plot_white(self, noise_cov, show=True):
-        """Plot whitened evoked response
+    def plot_white(self, noise_cov, show=True, rank=None):
+        """Plot whitened evoked response.
 
         Plots the whitened evoked response and the whitened GFP as described in
         [1]_. If one single covariance object is passed, the GFP panel (bottom)
@@ -373,6 +379,14 @@ class Evoked(ProjMixin, ContainsMixin, UpdateChannelsMixin,
             The noise covariance as computed by ``mne.cov.compute_covariance``.
         show : bool
             Whether to show the figure or not. Defaults to True.
+        rank : dict of int | None
+            Dict of ints where keys are 'eeg', 'meg', mag' or 'grad'. If None,
+            the rank is detected automatically. Defaults to None. 'mag' or
+            'grad' cannot be specified jointly with 'meg'. For SSS'd data,
+            only 'meg' is valid. For non-SSS'd data, 'mag' and/or 'grad' must
+            be specified separately. If only one is specified, the other one
+            gets estimated. Note. The rank estimation will be printed by the
+            logger for each noise covariance estimator that is passed.
 
         Returns
         -------
@@ -390,7 +404,7 @@ class Evoked(ProjMixin, ContainsMixin, UpdateChannelsMixin,
         .. versionadded:: 0.9.0
         """
         return _plot_evoked_white(self, noise_cov=noise_cov, scalings=None,
-                                  rank=None, show=show)
+                                  rank=rank, show=show)
 
     @copy_function_doc_to_method_doc(plot_evoked_joint)
     def plot_joint(self, times="peaks", title='', picks=None,
@@ -402,9 +416,11 @@ class Evoked(ProjMixin, ContainsMixin, UpdateChannelsMixin,
 
     def animate_topomap(self, ch_type='mag', times=None, frame_rate=None,
                         butterfly=False, blit=True, show=True):
-        """Make animation of evoked data as topomap timeseries. Animation can
-        be paused/resumed with left mouse button. Left and right arrow keys can
-        be used to move backward or forward in time
+        """Make animation of evoked data as topomap timeseries.
+
+        The animation can be paused/resumed with left mouse button.
+        Left and right arrow keys can be used to move backward or forward
+        in time.
 
         Parameters
         ----------
@@ -446,11 +462,10 @@ class Evoked(ProjMixin, ContainsMixin, UpdateChannelsMixin,
                                        show=show)
 
     def as_type(self, ch_type='grad', mode='fast'):
-        """Compute virtual evoked using interpolated fields in mag/grad
-        channels.
+        """Compute virtual evoked using interpolated fields.
 
         .. Warning:: Using virtual evoked to compute inverse can yield
-            unexpected results. The virtual channels have `'_virtual'` appended
+            unexpected results. The virtual channels have `'_v'` appended
             at the end of the names to emphasize that the data contained in
             them are interpolated.
 
@@ -475,40 +490,8 @@ class Evoked(ProjMixin, ContainsMixin, UpdateChannelsMixin,
         from .forward import _as_meg_type_evoked
         return _as_meg_type_evoked(self, ch_type=ch_type, mode=mode)
 
-    def resample(self, sfreq, npad='auto', window='boxcar'):
-        """Resample data
-
-        This function operates in-place.
-
-        Parameters
-        ----------
-        sfreq : float
-            New sample rate to use
-        npad : int | str
-            Amount to pad the start and end of the data.
-            Can also be "auto" to use a padding that will result in
-            a power-of-two size (can be much faster).
-        window : string or tuple
-            Window to use in resampling. See scipy.signal.resample.
-
-        Returns
-        -------
-        evoked : instance of mne.Evoked
-            The resampled evoked object.
-        """
-        sfreq = float(sfreq)
-        o_sfreq = self.info['sfreq']
-        self.data = resample(self.data, sfreq, o_sfreq, npad, -1, window)
-        # adjust indirectly affected variables
-        self.info['sfreq'] = sfreq
-        self.times = (np.arange(self.data.shape[1], dtype=np.float) / sfreq +
-                      self.times[0])
-        self.first = int(self.times[0] * self.info['sfreq'])
-        self.last = len(self.times) + self.first - 1
-        return self
-
     def detrend(self, order=1, picks=None):
-        """Detrend data
+        """Detrend data.
 
         This function operates in-place.
 
@@ -531,7 +514,7 @@ class Evoked(ProjMixin, ContainsMixin, UpdateChannelsMixin,
         return self
 
     def copy(self):
-        """Copy the instance of evoked
+        """Copy the instance of evoked.
 
         Returns
         -------
@@ -541,7 +524,7 @@ class Evoked(ProjMixin, ContainsMixin, UpdateChannelsMixin,
         return evoked
 
     def __neg__(self):
-        """Negate channel responses
+        """Negate channel responses.
 
         Returns
         -------
@@ -554,47 +537,9 @@ class Evoked(ProjMixin, ContainsMixin, UpdateChannelsMixin,
         out.comment = '-' + (out.comment or 'unknown')
         return out
 
-    @deprecated('ev1 + ev2 weighted summation has been deprecated and will be '
-                'removed in 0.14, use combine_evoked([ev1, ev2],'
-                'weights="nave") instead')
-    def __add__(self, evoked):
-        """Add evoked taking into account number of epochs
-
-        The addition will be performed by weighting each Evoked
-        instance by the number of averages.
-
-        See Also
-        --------
-        mne.combine_evoked
-        """
-        out = combine_evoked([self, evoked], weights='nave')
-        out.comment = self.comment + " + " + evoked.comment
-        return out
-
-    @deprecated('ev1 - ev2 weighted subtraction has been deprecated and will '
-                'be removed in 0.14, use combine_evoked([ev1, -ev2], '
-                'weights="nave") instead')
-    def __sub__(self, evoked):
-        """Subtract evoked taking into account number of epochs
-
-        The subtraction will be performed by weighting each Evoked
-        instance by the number of averages.
-
-        See Also
-        --------
-        mne.combine_evoked
-        """
-        out = combine_evoked([self, -evoked], weights='nave')
-        if self.comment is None or evoked.comment is None:
-            warn('evoked.comment expects a string but is None')
-            out.comment = 'unknown'
-        else:
-            out.comment = self.comment + " - " + evoked.comment
-        return out
-
-    def get_peak(self, ch_type=None, tmin=None, tmax=None, mode='abs',
-                 time_as_index=False):
-        """Get location and latency of peak amplitude
+    def get_peak(self, ch_type=None, tmin=None, tmax=None,
+                 mode='abs', time_as_index=False, merge_grads=False):
+        """Get location and latency of peak amplitude.
 
         Parameters
         ----------
@@ -615,6 +560,8 @@ class Evoked(ProjMixin, ContainsMixin, UpdateChannelsMixin,
             Defaults to 'abs'.
         time_as_index : bool
             Whether to return the time index instead of the latency in seconds.
+        merge_grads : bool
+            If True, compute peak from merged gradiometer data.
 
         Returns
         -------
@@ -644,6 +591,13 @@ class Evoked(ProjMixin, ContainsMixin, UpdateChannelsMixin,
                                'must not be `None`, pass a sensor type '
                                'value instead')
 
+        if merge_grads:
+            if ch_type != 'grad':
+                raise ValueError('Channel type must be grad for merge_grads')
+            elif mode == 'neg':
+                raise ValueError('Negative mode (mode=neg) does not make '
+                                 'sense with merge_grads=True')
+
         meg = eeg = misc = seeg = ecog = fnirs = False
         picks = None
         if ch_type in ('mag', 'grad'):
@@ -660,24 +614,31 @@ class Evoked(ProjMixin, ContainsMixin, UpdateChannelsMixin,
             fnirs = ch_type
 
         if ch_type is not None:
-            picks = pick_types(self.info, meg=meg, eeg=eeg, misc=misc,
-                               seeg=seeg, ecog=ecog, ref_meg=False,
-                               fnirs=fnirs)
-
+            if merge_grads:
+                picks = _pair_grad_sensors(self.info, topomap_coords=False)
+            else:
+                picks = pick_types(self.info, meg=meg, eeg=eeg, misc=misc,
+                                   seeg=seeg, ecog=ecog, ref_meg=False,
+                                   fnirs=fnirs)
         data = self.data
         ch_names = self.ch_names
+
         if picks is not None:
             data = data[picks]
             ch_names = [ch_names[k] for k in picks]
+
+        if merge_grads:
+            data = _merge_grad_data(data)
+            ch_names = [ch_name[:-1] + 'X' for ch_name in ch_names[::2]]
+
         ch_idx, time_idx = _get_peak(data, self.times, tmin,
                                      tmax, mode)
-
         return (ch_names[ch_idx],
                 time_idx if time_as_index else self.times[time_idx])
 
 
 def _check_decim(info, decim, offset):
-    """Helper to check decimation parameters"""
+    """Check decimation parameters."""
     if decim < 1 or decim != int(decim):
         raise ValueError('decim must be an integer > 0')
     decim = int(decim)
@@ -701,17 +662,17 @@ def _check_decim(info, decim, offset):
 
 
 class EvokedArray(Evoked):
-    """Evoked object from numpy array
+    """Evoked object from numpy array.
 
     Parameters
     ----------
     data : array of shape (n_channels, n_times)
-        The channels' evoked response.
+        The channels' evoked response. See notes for proper units of measure.
     info : instance of Info
         Info dictionary. Consider using ``create_info`` to populate
         this structure.
     tmin : float
-        Start time before event.
+        Start time before event. Defaults to 0.
     comment : string
         Comment on dataset. Can be the condition. Defaults to ''.
     nave : int
@@ -719,8 +680,18 @@ class EvokedArray(Evoked):
     kind : str
         Type of data, either average or standard_error. Defaults to 'average'.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
-        Defaults to raw.verbose.
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
+
+    Notes
+    -----
+    Proper units of measure:
+    * V: eeg, eog, seeg, emg, ecg, bio, ecog
+    * T: mag
+    * T/m: grad
+    * M: hbo, hbr
+    * Am: dipole
+    * AU: misc
 
     See Also
     --------
@@ -728,9 +699,8 @@ class EvokedArray(Evoked):
     """
 
     @verbose
-    def __init__(self, data, info, tmin, comment='', nave=1, kind='average',
-                 verbose=None):
-
+    def __init__(self, data, info, tmin=0., comment='', nave=1, kind='average',
+                 verbose=None):  # noqa: D102
         dtype = np.complex128 if np.any(np.iscomplex(data)) else np.float64
         data = np.asanyarray(data, dtype=dtype)
 
@@ -750,12 +720,13 @@ class EvokedArray(Evoked):
         self.last = self.first + np.shape(data)[-1] - 1
         self.times = np.arange(self.first, self.last + 1,
                                dtype=np.float) / info['sfreq']
-        self.info = info
+        self.info = info.copy()  # do not modify original info
         self.nave = nave
         self.kind = kind
         self.comment = comment
         self.picks = None
         self.verbose = verbose
+        self.preload = True
         self._projector = None
         if not isinstance(self.kind, string_types):
             raise TypeError('kind must be a string, not "%s"' % (type(kind),))
@@ -766,7 +737,7 @@ class EvokedArray(Evoked):
 
 
 def _get_entries(fid, evoked_node, allow_maxshield=False):
-    """Helper to get all evoked entries"""
+    """Get all evoked entries."""
     comments = list()
     aspect_kinds = list()
     for ev in evoked_node:
@@ -809,7 +780,7 @@ def _get_aspect(evoked, allow_maxshield):
 
 
 def _get_evoked_node(fname):
-    """Helper to get info in evoked file"""
+    """Get info in evoked file."""
     f, tree, _ = fiff_open(fname)
     with f as fid:
         _, meas = read_meas_info(fid, tree, verbose=False)
@@ -818,7 +789,7 @@ def _get_evoked_node(fname):
 
 
 def grand_average(all_evoked, interpolate_bads=True):
-    """Make grand average of a list evoked data
+    """Make grand average of a list evoked data.
 
     The function interpolates bad channels based on `interpolate_bads`
     parameter. If `interpolate_bads` is True, the grand average
@@ -868,8 +839,8 @@ def grand_average(all_evoked, interpolate_bads=True):
     return grand_average
 
 
-def combine_evoked(all_evoked, weights=None):
-    """Merge evoked data by weighted addition or subtraction
+def combine_evoked(all_evoked, weights):
+    """Merge evoked data by weighted addition or subtraction.
 
     Data should have the same channels and the same time instants.
     Subtraction can be performed by passing negative weights (e.g., [1, -1]).
@@ -892,11 +863,6 @@ def combine_evoked(all_evoked, weights=None):
     -----
     .. versionadded:: 0.9.0
     """
-    if weights is None:
-        weights = 'nave'
-        warn('In 0.13 the default is weights="nave", but in 0.14 the default '
-             'will be removed and it will have to be explicitly set',
-             DeprecationWarning)
     evoked = all_evoked[0].copy()
     if isinstance(weights, string_types):
         if weights not in ('nave', 'equal'):
@@ -950,7 +916,7 @@ def combine_evoked(all_evoked, weights=None):
 @verbose
 def read_evokeds(fname, condition=None, baseline=None, kind='average',
                  proj=True, allow_maxshield=False, verbose=None):
-    """Read evoked dataset(s)
+    """Read evoked dataset(s).
 
     Parameters
     ----------
@@ -980,7 +946,8 @@ def read_evokeds(fname, condition=None, baseline=None, kind='average',
         SSS/tSSS to remove the compensation signals that may also affect brain
         activity. Can also be "yes" to load without eliciting a warning.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -1011,7 +978,7 @@ def read_evokeds(fname, condition=None, baseline=None, kind='average',
 
 
 def _read_evoked(fname, condition=None, kind='average', allow_maxshield=False):
-    """Read evoked data from a FIF file"""
+    """Read evoked data from a FIF file."""
     if fname is None:
         raise ValueError('No evoked filename specified')
 
@@ -1019,7 +986,6 @@ def _read_evoked(fname, condition=None, kind='average', allow_maxshield=False):
     with f as fid:
         #   Read the measurement info
         info, meas = read_meas_info(fid, tree, clean_bads=True)
-        info['filename'] = fname
 
         #   Locate the data of interest
         processed = dir_tree_find(meas, FIFF.FIFFB_PROCESSED_DATA)
@@ -1185,7 +1151,7 @@ def _read_evoked(fname, condition=None, kind='average', allow_maxshield=False):
 
 
 def write_evokeds(fname, evoked):
-    """Write an evoked dataset to a file
+    """Write an evoked dataset to a file.
 
     Parameters
     ----------
@@ -1204,7 +1170,7 @@ def write_evokeds(fname, evoked):
 
 
 def _write_evokeds(fname, evoked, check=True):
-    """Helper to write evoked data"""
+    """Write evoked data."""
     if check:
         check_fname(fname, 'evoked', ('-ave.fif', '-ave.fif.gz'))
 
@@ -1260,7 +1226,7 @@ def _write_evokeds(fname, evoked, check=True):
 
 
 def _get_peak(data, times, tmin=None, tmax=None, mode='abs'):
-    """Get feature-index and time of maximum signal from 2D array
+    """Get feature-index and time of maximum signal from 2D array.
 
     Note. This is a 'getter', not a 'finder'. For non-evoked type
     data and continuous signals, please use proper peak detection algorithms.
diff --git a/mne/externals/FieldTrip.py b/mne/externals/FieldTrip.py
index 66bb1d3..c0712aa 100644
--- a/mne/externals/FieldTrip.py
+++ b/mne/externals/FieldTrip.py
@@ -309,7 +309,7 @@ class Client:
                 (chunk_type, chunk_len) = struct.unpack(
                     'II', payload[offset:offset + 8])
                 offset += 8
-                if offset + chunk_len < bufsize:
+                if offset + chunk_len > bufsize:
                     break
                 H.chunks[chunk_type] = payload[offset:offset + chunk_len]
                 offset += chunk_len
diff --git a/mne/externals/__init__.py b/mne/externals/__init__.py
index 6f70ab7..e38e645 100644
--- a/mne/externals/__init__.py
+++ b/mne/externals/__init__.py
@@ -1,5 +1,6 @@
-from . import six
-from . import jdcal
 from . import decorator
-from . import tempita
+from . import funcsigs
 from . import h5io
+from . import jdcal
+from . import six
+from . import tempita
diff --git a/mne/externals/funcsigs.py b/mne/externals/funcsigs.py
new file mode 100644
index 0000000..4e68469
--- /dev/null
+++ b/mne/externals/funcsigs.py
@@ -0,0 +1,815 @@
+# Copyright 2001-2013 Python Software Foundation; All Rights Reserved
+"""Function signature objects for callables
+
+Back port of Python 3.3's function signature tools from the inspect module,
+modified to be compatible with Python 2.7 and 3.2+.
+"""
+from __future__ import absolute_import, division, print_function
+import itertools
+import functools
+import re
+import types
+
+from collections import OrderedDict
+
+__version__ = "0.4"
+
+__all__ = ['BoundArguments', 'Parameter', 'Signature', 'signature']
+
+
+_WrapperDescriptor = type(type.__call__)
+_MethodWrapper = type(all.__call__)
+
+_NonUserDefinedCallables = (_WrapperDescriptor,
+                            _MethodWrapper,
+                            types.BuiltinFunctionType)
+
+
+def formatannotation(annotation, base_module=None):
+    if isinstance(annotation, type):
+        if annotation.__module__ in ('builtins', '__builtin__', base_module):
+            return annotation.__name__
+        return annotation.__module__+'.'+annotation.__name__
+    return repr(annotation)
+
+
+def _get_user_defined_method(cls, method_name, *nested):
+    try:
+        if cls is type:
+            return
+        meth = getattr(cls, method_name)
+        for name in nested:
+            meth = getattr(meth, name, meth)
+    except AttributeError:
+        return
+    else:
+        if not isinstance(meth, _NonUserDefinedCallables):
+            # Once '__signature__' will be added to 'C'-level
+            # callables, this check won't be necessary
+            return meth
+
+
+def signature(obj):
+    '''Get a signature object for the passed callable.'''
+
+    if not callable(obj):
+        raise TypeError('{0!r} is not a callable object'.format(obj))
+
+    if isinstance(obj, types.MethodType):
+        sig = signature(obj.__func__)
+        if obj.__self__ is None:
+            # Unbound method: the first parameter becomes positional-only
+            if sig.parameters:
+                first = sig.parameters.values()[0].replace(
+                    kind=_POSITIONAL_ONLY)
+                return sig.replace(
+                    parameters=(first,) + tuple(sig.parameters.values())[1:])
+            else:
+                return sig
+        else:
+            # In this case we skip the first parameter of the underlying
+            # function (usually `self` or `cls`).
+            return sig.replace(parameters=tuple(sig.parameters.values())[1:])
+
+    try:
+        sig = obj.__signature__
+    except AttributeError:
+        pass
+    else:
+        if sig is not None:
+            return sig
+
+    try:
+        # Was this function wrapped by a decorator?
+        wrapped = obj.__wrapped__
+    except AttributeError:
+        pass
+    else:
+        return signature(wrapped)
+
+    if isinstance(obj, types.FunctionType):
+        return Signature.from_function(obj)
+
+    if isinstance(obj, functools.partial):
+        sig = signature(obj.func)
+
+        new_params = OrderedDict(sig.parameters.items())
+
+        partial_args = obj.args or ()
+        partial_keywords = obj.keywords or {}
+        try:
+            ba = sig.bind_partial(*partial_args, **partial_keywords)
+        except TypeError as ex:
+            msg = 'partial object {0!r} has incorrect arguments'.format(obj)
+            raise ValueError(msg)
+
+        for arg_name, arg_value in ba.arguments.items():
+            param = new_params[arg_name]
+            if arg_name in partial_keywords:
+                # We set a new default value, because the following code
+                # is correct:
+                #
+                #   >>> def foo(a): print(a)
+                #   >>> print(partial(partial(foo, a=10), a=20)())
+                #   20
+                #   >>> print(partial(partial(foo, a=10), a=20)(a=30))
+                #   30
+                #
+                # So, with 'partial' objects, passing a keyword argument is
+                # like setting a new default value for the corresponding
+                # parameter
+                #
+                # We also mark this parameter with '_partial_kwarg'
+                # flag.  Later, in '_bind', the 'default' value of this
+                # parameter will be added to 'kwargs', to simulate
+                # the 'functools.partial' real call.
+                new_params[arg_name] = param.replace(default=arg_value,
+                                                     _partial_kwarg=True)
+
+            elif (param.kind not in (_VAR_KEYWORD, _VAR_POSITIONAL) and
+                            not param._partial_kwarg):
+                new_params.pop(arg_name)
+
+        return sig.replace(parameters=new_params.values())
+
+    sig = None
+    if isinstance(obj, type):
+        # obj is a class or a metaclass
+
+        # First, let's see if it has an overloaded __call__ defined
+        # in its metaclass
+        call = _get_user_defined_method(type(obj), '__call__')
+        if call is not None:
+            sig = signature(call)
+        else:
+            # Now we check if the 'obj' class has a '__new__' method
+            new = _get_user_defined_method(obj, '__new__')
+            if new is not None:
+                sig = signature(new)
+            else:
+                # Finally, we should have at least __init__ implemented
+                init = _get_user_defined_method(obj, '__init__')
+                if init is not None:
+                    sig = signature(init)
+    elif not isinstance(obj, _NonUserDefinedCallables):
+        # An object with __call__
+        # We also check that the 'obj' is not an instance of
+        # _WrapperDescriptor or _MethodWrapper to avoid
+        # infinite recursion (and even potential segfault)
+        call = _get_user_defined_method(type(obj), '__call__', 'im_func')
+        if call is not None:
+            sig = signature(call)
+
+    if sig is not None:
+        # For classes and objects we skip the first parameter of their
+        # __call__, __new__, or __init__ methods
+        return sig.replace(parameters=tuple(sig.parameters.values())[1:])
+
+    if isinstance(obj, types.BuiltinFunctionType):
+        # Raise a nicer error message for builtins
+        msg = 'no signature found for builtin function {0!r}'.format(obj)
+        raise ValueError(msg)
+
+    raise ValueError('callable {0!r} is not supported by signature'.format(obj))
+
+
+class _void(object):
+    '''A private marker - used in Parameter & Signature'''
+
+
+class _empty(object):
+    pass
+
+
+class _ParameterKind(int):
+    def __new__(self, *args, **kwargs):
+        obj = int.__new__(self, *args)
+        obj._name = kwargs['name']
+        return obj
+
+    def __str__(self):
+        return self._name
+
+    def __repr__(self):
+        return '<_ParameterKind: {0!r}>'.format(self._name)
+
+
+_POSITIONAL_ONLY        = _ParameterKind(0, name='POSITIONAL_ONLY')
+_POSITIONAL_OR_KEYWORD  = _ParameterKind(1, name='POSITIONAL_OR_KEYWORD')
+_VAR_POSITIONAL         = _ParameterKind(2, name='VAR_POSITIONAL')
+_KEYWORD_ONLY           = _ParameterKind(3, name='KEYWORD_ONLY')
+_VAR_KEYWORD            = _ParameterKind(4, name='VAR_KEYWORD')
+
+
+class Parameter(object):
+    '''Represents a parameter in a function signature.
+
+    Has the following public attributes:
+
+    * name : str
+        The name of the parameter as a string.
+    * default : object
+        The default value for the parameter if specified.  If the
+        parameter has no default value, this attribute is not set.
+    * annotation
+        The annotation for the parameter if specified.  If the
+        parameter has no annotation, this attribute is not set.
+    * kind : str
+        Describes how argument values are bound to the parameter.
+        Possible values: `Parameter.POSITIONAL_ONLY`,
+        `Parameter.POSITIONAL_OR_KEYWORD`, `Parameter.VAR_POSITIONAL`,
+        `Parameter.KEYWORD_ONLY`, `Parameter.VAR_KEYWORD`.
+    '''
+
+    __slots__ = ('_name', '_kind', '_default', '_annotation', '_partial_kwarg')
+
+    POSITIONAL_ONLY         = _POSITIONAL_ONLY
+    POSITIONAL_OR_KEYWORD   = _POSITIONAL_OR_KEYWORD
+    VAR_POSITIONAL          = _VAR_POSITIONAL
+    KEYWORD_ONLY            = _KEYWORD_ONLY
+    VAR_KEYWORD             = _VAR_KEYWORD
+
+    empty = _empty
+
+    def __init__(self, name, kind, default=_empty, annotation=_empty,
+                 _partial_kwarg=False):
+
+        if kind not in (_POSITIONAL_ONLY, _POSITIONAL_OR_KEYWORD,
+                        _VAR_POSITIONAL, _KEYWORD_ONLY, _VAR_KEYWORD):
+            raise ValueError("invalid value for 'Parameter.kind' attribute")
+        self._kind = kind
+
+        if default is not _empty:
+            if kind in (_VAR_POSITIONAL, _VAR_KEYWORD):
+                msg = '{0} parameters cannot have default values'.format(kind)
+                raise ValueError(msg)
+        self._default = default
+        self._annotation = annotation
+
+        if name is None:
+            if kind != _POSITIONAL_ONLY:
+                raise ValueError("None is not a valid name for a "
+                                 "non-positional-only parameter")
+            self._name = name
+        else:
+            name = str(name)
+            if kind != _POSITIONAL_ONLY and not re.match(r'[a-z_]\w*$', name, re.I):
+                msg = '{0!r} is not a valid parameter name'.format(name)
+                raise ValueError(msg)
+            self._name = name
+
+        self._partial_kwarg = _partial_kwarg
+
+    @property
+    def name(self):
+        return self._name
+
+    @property
+    def default(self):
+        return self._default
+
+    @property
+    def annotation(self):
+        return self._annotation
+
+    @property
+    def kind(self):
+        return self._kind
+
+    def replace(self, name=_void, kind=_void, annotation=_void,
+                default=_void, _partial_kwarg=_void):
+        '''Creates a customized copy of the Parameter.'''
+
+        if name is _void:
+            name = self._name
+
+        if kind is _void:
+            kind = self._kind
+
+        if annotation is _void:
+            annotation = self._annotation
+
+        if default is _void:
+            default = self._default
+
+        if _partial_kwarg is _void:
+            _partial_kwarg = self._partial_kwarg
+
+        return type(self)(name, kind, default=default, annotation=annotation,
+                          _partial_kwarg=_partial_kwarg)
+
+    def __str__(self):
+        kind = self.kind
+
+        formatted = self._name
+        if kind == _POSITIONAL_ONLY:
+            if formatted is None:
+                formatted = ''
+            formatted = '<{0}>'.format(formatted)
+
+        # Add annotation and default value
+        if self._annotation is not _empty:
+            formatted = '{0}:{1}'.format(formatted,
+                                       formatannotation(self._annotation))
+
+        if self._default is not _empty:
+            formatted = '{0}={1}'.format(formatted, repr(self._default))
+
+        if kind == _VAR_POSITIONAL:
+            formatted = '*' + formatted
+        elif kind == _VAR_KEYWORD:
+            formatted = '**' + formatted
+
+        return formatted
+
+    def __repr__(self):
+        return '<{0} at {1:#x} {2!r}>'.format(self.__class__.__name__,
+                                           id(self), self.name)
+
+    def __hash__(self):
+        msg = "unhashable type: '{0}'".format(self.__class__.__name__)
+        raise TypeError(msg)
+
+    def __eq__(self, other):
+        return (issubclass(other.__class__, Parameter) and
+                self._name == other._name and
+                self._kind == other._kind and
+                self._default == other._default and
+                self._annotation == other._annotation)
+
+    def __ne__(self, other):
+        return not self.__eq__(other)
+
+
+class BoundArguments(object):
+    '''Result of `Signature.bind` call.  Holds the mapping of arguments
+    to the function's parameters.
+
+    Has the following public attributes:
+
+    * arguments : OrderedDict
+        An ordered mutable mapping of parameters' names to arguments' values.
+        Does not contain arguments' default values.
+    * signature : Signature
+        The Signature object that created this instance.
+    * args : tuple
+        Tuple of positional arguments values.
+    * kwargs : dict
+        Dict of keyword arguments values.
+    '''
+
+    def __init__(self, signature, arguments):
+        self.arguments = arguments
+        self._signature = signature
+
+    @property
+    def signature(self):
+        return self._signature
+
+    @property
+    def args(self):
+        args = []
+        for param_name, param in self._signature.parameters.items():
+            if (param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY) or
+                                                    param._partial_kwarg):
+                # Keyword arguments mapped by 'functools.partial'
+                # (Parameter._partial_kwarg is True) are mapped
+                # in 'BoundArguments.kwargs', along with VAR_KEYWORD &
+                # KEYWORD_ONLY
+                break
+
+            try:
+                arg = self.arguments[param_name]
+            except KeyError:
+                # We're done here. Other arguments
+                # will be mapped in 'BoundArguments.kwargs'
+                break
+            else:
+                if param.kind == _VAR_POSITIONAL:
+                    # *args
+                    args.extend(arg)
+                else:
+                    # plain argument
+                    args.append(arg)
+
+        return tuple(args)
+
+    @property
+    def kwargs(self):
+        kwargs = {}
+        kwargs_started = False
+        for param_name, param in self._signature.parameters.items():
+            if not kwargs_started:
+                if (param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY) or
+                                                param._partial_kwarg):
+                    kwargs_started = True
+                else:
+                    if param_name not in self.arguments:
+                        kwargs_started = True
+                        continue
+
+            if not kwargs_started:
+                continue
+
+            try:
+                arg = self.arguments[param_name]
+            except KeyError:
+                pass
+            else:
+                if param.kind == _VAR_KEYWORD:
+                    # **kwargs
+                    kwargs.update(arg)
+                else:
+                    # plain keyword argument
+                    kwargs[param_name] = arg
+
+        return kwargs
+
+    def __hash__(self):
+        msg = "unhashable type: '{0}'".format(self.__class__.__name__)
+        raise TypeError(msg)
+
+    def __eq__(self, other):
+        return (issubclass(other.__class__, BoundArguments) and
+                self.signature == other.signature and
+                self.arguments == other.arguments)
+
+    def __ne__(self, other):
+        return not self.__eq__(other)
+
+
+class Signature(object):
+    '''A Signature object represents the overall signature of a function.
+    It stores a Parameter object for each parameter accepted by the
+    function, as well as information specific to the function itself.
+
+    A Signature object has the following public attributes and methods:
+
+    * parameters : OrderedDict
+        An ordered mapping of parameters' names to the corresponding
+        Parameter objects (keyword-only arguments are in the same order
+        as listed in `code.co_varnames`).
+    * return_annotation : object
+        The annotation for the return type of the function if specified.
+        If the function has no annotation for its return type, this
+        attribute is not set.
+    * bind(*args, **kwargs) -> BoundArguments
+        Creates a mapping from positional and keyword arguments to
+        parameters.
+    * bind_partial(*args, **kwargs) -> BoundArguments
+        Creates a partial mapping from positional and keyword arguments
+        to parameters (simulating 'functools.partial' behavior.)
+    '''
+
+    __slots__ = ('_return_annotation', '_parameters')
+
+    _parameter_cls = Parameter
+    _bound_arguments_cls = BoundArguments
+
+    empty = _empty
+
+    def __init__(self, parameters=None, return_annotation=_empty,
+                 __validate_parameters__=True):
+        '''Constructs Signature from the given list of Parameter
+        objects and 'return_annotation'.  All arguments are optional.
+        '''
+
+        if parameters is None:
+            params = OrderedDict()
+        else:
+            if __validate_parameters__:
+                params = OrderedDict()
+                top_kind = _POSITIONAL_ONLY
+
+                for idx, param in enumerate(parameters):
+                    kind = param.kind
+                    if kind < top_kind:
+                        msg = 'wrong parameter order: {0} before {1}'
+                        msg = msg.format(top_kind, param.kind)
+                        raise ValueError(msg)
+                    else:
+                        top_kind = kind
+
+                    name = param.name
+                    if name is None:
+                        name = str(idx)
+                        param = param.replace(name=name)
+
+                    if name in params:
+                        msg = 'duplicate parameter name: {0!r}'.format(name)
+                        raise ValueError(msg)
+                    params[name] = param
+            else:
+                params = OrderedDict(((param.name, param)
+                                                for param in parameters))
+
+        self._parameters = params
+        self._return_annotation = return_annotation
+
+    @classmethod
+    def from_function(cls, func):
+        '''Constructs Signature for the given python function'''
+
+        if not isinstance(func, types.FunctionType):
+            raise TypeError('{0!r} is not a Python function'.format(func))
+
+        Parameter = cls._parameter_cls
+
+        # Parameter information.
+        func_code = func.__code__
+        pos_count = func_code.co_argcount
+        arg_names = func_code.co_varnames
+        positional = tuple(arg_names[:pos_count])
+        keyword_only_count = getattr(func_code, 'co_kwonlyargcount', 0)
+        keyword_only = arg_names[pos_count:(pos_count + keyword_only_count)]
+        annotations = getattr(func, '__annotations__', {})
+        defaults = func.__defaults__
+        kwdefaults = getattr(func, '__kwdefaults__', None)
+
+        if defaults:
+            pos_default_count = len(defaults)
+        else:
+            pos_default_count = 0
+
+        parameters = []
+
+        # Non-keyword-only parameters w/o defaults.
+        non_default_count = pos_count - pos_default_count
+        for name in positional[:non_default_count]:
+            annotation = annotations.get(name, _empty)
+            parameters.append(Parameter(name, annotation=annotation,
+                                        kind=_POSITIONAL_OR_KEYWORD))
+
+        # ... w/ defaults.
+        for offset, name in enumerate(positional[non_default_count:]):
+            annotation = annotations.get(name, _empty)
+            parameters.append(Parameter(name, annotation=annotation,
+                                        kind=_POSITIONAL_OR_KEYWORD,
+                                        default=defaults[offset]))
+
+        # *args
+        if func_code.co_flags & 0x04:
+            name = arg_names[pos_count + keyword_only_count]
+            annotation = annotations.get(name, _empty)
+            parameters.append(Parameter(name, annotation=annotation,
+                                        kind=_VAR_POSITIONAL))
+
+        # Keyword-only parameters.
+        for name in keyword_only:
+            default = _empty
+            if kwdefaults is not None:
+                default = kwdefaults.get(name, _empty)
+
+            annotation = annotations.get(name, _empty)
+            parameters.append(Parameter(name, annotation=annotation,
+                                        kind=_KEYWORD_ONLY,
+                                        default=default))
+        # **kwargs
+        if func_code.co_flags & 0x08:
+            index = pos_count + keyword_only_count
+            if func_code.co_flags & 0x04:
+                index += 1
+
+            name = arg_names[index]
+            annotation = annotations.get(name, _empty)
+            parameters.append(Parameter(name, annotation=annotation,
+                                        kind=_VAR_KEYWORD))
+
+        return cls(parameters,
+                   return_annotation=annotations.get('return', _empty),
+                   __validate_parameters__=False)
+
+    @property
+    def parameters(self):
+        try:
+            return types.MappingProxyType(self._parameters)
+        except AttributeError:
+            return OrderedDict(self._parameters.items())
+
+    @property
+    def return_annotation(self):
+        return self._return_annotation
+
+    def replace(self, parameters=_void, return_annotation=_void):
+        '''Creates a customized copy of the Signature.
+        Pass 'parameters' and/or 'return_annotation' arguments
+        to override them in the new copy.
+        '''
+
+        if parameters is _void:
+            parameters = self.parameters.values()
+
+        if return_annotation is _void:
+            return_annotation = self._return_annotation
+
+        return type(self)(parameters,
+                          return_annotation=return_annotation)
+
+    def __hash__(self):
+        msg = "unhashable type: '{0}'".format(self.__class__.__name__)
+        raise TypeError(msg)
+
+    def __eq__(self, other):
+        if (not issubclass(type(other), Signature) or
+                    self.return_annotation != other.return_annotation or
+                    len(self.parameters) != len(other.parameters)):
+            return False
+
+        other_positions = dict((param, idx)
+                           for idx, param in enumerate(other.parameters.keys()))
+
+        for idx, (param_name, param) in enumerate(self.parameters.items()):
+            if param.kind == _KEYWORD_ONLY:
+                try:
+                    other_param = other.parameters[param_name]
+                except KeyError:
+                    return False
+                else:
+                    if param != other_param:
+                        return False
+            else:
+                try:
+                    other_idx = other_positions[param_name]
+                except KeyError:
+                    return False
+                else:
+                    if (idx != other_idx or
+                                    param != other.parameters[param_name]):
+                        return False
+
+        return True
+
+    def __ne__(self, other):
+        return not self.__eq__(other)
+
+    def _bind(self, args, kwargs, partial=False):
+        '''Private method.  Don't use directly.'''
+
+        arguments = OrderedDict()
+
+        parameters = iter(self.parameters.values())
+        parameters_ex = ()
+        arg_vals = iter(args)
+
+        if partial:
+            # Support for binding arguments to 'functools.partial' objects.
+            # See 'functools.partial' case in 'signature()' implementation
+            # for details.
+            for param_name, param in self.parameters.items():
+                if (param._partial_kwarg and param_name not in kwargs):
+                    # Simulating 'functools.partial' behavior
+                    kwargs[param_name] = param.default
+
+        while True:
+            # Let's iterate through the positional arguments and corresponding
+            # parameters
+            try:
+                arg_val = next(arg_vals)
+            except StopIteration:
+                # No more positional arguments
+                try:
+                    param = next(parameters)
+                except StopIteration:
+                    # No more parameters. That's it. Just need to check that
+                    # we have no `kwargs` after this while loop
+                    break
+                else:
+                    if param.kind == _VAR_POSITIONAL:
+                        # That's OK, just empty *args.  Let's start parsing
+                        # kwargs
+                        break
+                    elif param.name in kwargs:
+                        if param.kind == _POSITIONAL_ONLY:
+                            msg = '{arg!r} parameter is positional only, ' \
+                                  'but was passed as a keyword'
+                            msg = msg.format(arg=param.name)
+                            raise TypeError(msg)
+                        parameters_ex = (param,)
+                        break
+                    elif (param.kind == _VAR_KEYWORD or
+                                                param.default is not _empty):
+                        # That's fine too - we have a default value for this
+                        # parameter.  So, lets start parsing `kwargs`, starting
+                        # with the current parameter
+                        parameters_ex = (param,)
+                        break
+                    else:
+                        if partial:
+                            parameters_ex = (param,)
+                            break
+                        else:
+                            msg = '{arg!r} parameter lacking default value'
+                            msg = msg.format(arg=param.name)
+                            raise TypeError(msg)
+            else:
+                # We have a positional argument to process
+                try:
+                    param = next(parameters)
+                except StopIteration:
+                    raise TypeError('too many positional arguments')
+                else:
+                    if param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY):
+                        # Looks like we have no parameter for this positional
+                        # argument
+                        raise TypeError('too many positional arguments')
+
+                    if param.kind == _VAR_POSITIONAL:
+                        # We have an '*args'-like argument, let's fill it with
+                        # all positional arguments we have left and move on to
+                        # the next phase
+                        values = [arg_val]
+                        values.extend(arg_vals)
+                        arguments[param.name] = tuple(values)
+                        break
+
+                    if param.name in kwargs:
+                        raise TypeError('multiple values for argument '
+                                        '{arg!r}'.format(arg=param.name))
+
+                    arguments[param.name] = arg_val
+
+        # Now, we iterate through the remaining parameters to process
+        # keyword arguments
+        kwargs_param = None
+        for param in itertools.chain(parameters_ex, parameters):
+            if param.kind == _POSITIONAL_ONLY:
+                # This should never happen in case of a properly built
+                # Signature object (but let's have this check here
+                # to ensure correct behaviour just in case)
+                raise TypeError('{arg!r} parameter is positional only, '
+                                'but was passed as a keyword'. \
+                                format(arg=param.name))
+
+            if param.kind == _VAR_KEYWORD:
+                # Memorize that we have a '**kwargs'-like parameter
+                kwargs_param = param
+                continue
+
+            param_name = param.name
+            try:
+                arg_val = kwargs.pop(param_name)
+            except KeyError:
+                # We have no value for this parameter.  It's fine though,
+                # if it has a default value, or it is an '*args'-like
+                # parameter, left alone by the processing of positional
+                # arguments.
+                if (not partial and param.kind != _VAR_POSITIONAL and
+                                                    param.default is _empty):
+                    raise TypeError('{arg!r} parameter lacking default value'. \
+                                    format(arg=param_name))
+
+            else:
+                arguments[param_name] = arg_val
+
+        if kwargs:
+            if kwargs_param is not None:
+                # Process our '**kwargs'-like parameter
+                arguments[kwargs_param.name] = kwargs
+            else:
+                raise TypeError('too many keyword arguments')
+
+        return self._bound_arguments_cls(self, arguments)
+
+    def bind(self, *args, **kwargs):
+        '''Get a BoundArguments object, that maps the passed `args`
+        and `kwargs` to the function's signature.  Raises `TypeError`
+        if the passed arguments can not be bound.
+        '''
+        return self._bind(args, kwargs)
+
+    def bind_partial(self, *args, **kwargs):
+        '''Get a BoundArguments object, that partially maps the
+        passed `args` and `kwargs` to the function's signature.
+        Raises `TypeError` if the passed arguments can not be bound.
+        '''
+        return self._bind(args, kwargs, partial=True)
+
+    def __str__(self):
+        result = []
+        render_kw_only_separator = True
+        for idx, param in enumerate(self.parameters.values()):
+            formatted = str(param)
+
+            kind = param.kind
+            if kind == _VAR_POSITIONAL:
+                # OK, we have an '*args'-like parameter, so we won't need
+                # a '*' to separate keyword-only arguments
+                render_kw_only_separator = False
+            elif kind == _KEYWORD_ONLY and render_kw_only_separator:
+                # We have a keyword-only parameter to render and we haven't
+                # rendered an '*args'-like parameter before, so add a '*'
+                # separator to the parameters list ("foo(arg1, *, arg2)" case)
+                result.append('*')
+                # This condition should be only triggered once, so
+                # reset the flag
+                render_kw_only_separator = False
+
+            result.append(formatted)
+
+        rendered = '({0})'.format(', '.join(result))
+
+        if self.return_annotation is not _empty:
+            anno = formatannotation(self.return_annotation)
+            rendered += ' -> {0}'.format(anno)
+
+        return rendered
diff --git a/mne/externals/tempita/__init__.py b/mne/externals/tempita/__init__.py
index 41a0ce3..bdf8457 100644
--- a/mne/externals/tempita/__init__.py
+++ b/mne/externals/tempita/__init__.py
@@ -1,4 +1,1303 @@
-# The original Tempita implements all of its templating code here.
-# Moved it to _tempita.py to make the compilation portable.
+"""
+A small templating language
 
-from ._tempita import *
+This implements a small templating language.  This language implements
+if/elif/else, for/continue/break, expressions, and blocks of Python
+code.  The syntax is::
+
+  {{any expression (function calls etc)}}
+  {{any expression | filter}}
+  {{for x in y}}...{{endfor}}
+  {{if x}}x{{elif y}}y{{else}}z{{endif}}
+  {{py:x=1}}
+  {{py:
+  def foo(bar):
+      return 'baz'
+  }}
+  {{default var = default_value}}
+  {{# comment}}
+
+You use this with the ``Template`` class or the ``sub`` shortcut.
+The ``Template`` class takes the template string and the name of
+the template (for errors) and a default namespace.  Then (like
+``string.Template``) you can call the ``tmpl.substitute(**kw)``
+method to make a substitution (or ``tmpl.substitute(a_dict)``).
+
+``sub(content, **kw)`` substitutes the template immediately.  You
+can use ``__name='tmpl.html'`` to set the name of the template.
+
+If there are syntax errors ``TemplateError`` will be raised.
+"""
+
+import warnings
+import re
+import sys
+import cgi
+from ..six.moves.urllib.parse import quote as url_quote
+import os
+import tokenize
+from ..six.moves import cStringIO as StringIO
+from ._looper import looper
+from .compat3 import PY3, bytes, basestring_, next, is_unicode, coerce_text
+
+__all__ = ['TemplateError', 'Template', 'sub', 'HTMLTemplate',
+           'sub_html', 'html', 'bunch']
+
+in_re = re.compile(r'\s+in\s+')
+var_re = re.compile(r'^[a-z_][a-z0-9_]*$', re.I)
+
+
+class TemplateError(Exception):
+    """Exception raised while parsing a template
+    """
+
+    def __init__(self, message, position, name=None):
+        Exception.__init__(self, message)
+        self.position = position
+        self.name = name
+
+    def __str__(self):
+        msg = ' '.join(self.args)
+        if self.position:
+            msg = '%s at line %s column %s' % (
+                msg, self.position[0], self.position[1])
+        if self.name:
+            msg += ' in %s' % self.name
+        return msg
+
+
+class _TemplateContinue(Exception):
+    pass
+
+
+class _TemplateBreak(Exception):
+    pass
+
+
+def get_file_template(name, from_template):
+    path = os.path.join(os.path.dirname(from_template.name), name)
+    return from_template.__class__.from_filename(
+        path, namespace=from_template.namespace,
+        get_template=from_template.get_template)
+
+
+class Template(object):
+
+    default_namespace = {
+        'start_braces': '{{',
+        'end_braces': '}}',
+        'looper': looper,
+    }
+
+    default_encoding = 'utf8'
+    default_inherit = None
+
+    def __init__(self, content, name=None, namespace=None, stacklevel=None,
+                 get_template=None, default_inherit=None, line_offset=0,
+                 delimeters=None):
+        self.content = content
+
+        # set delimeters
+        if delimeters is None:
+            delimeters = (self.default_namespace['start_braces'],
+                          self.default_namespace['end_braces'])
+        else:
+            assert len(delimeters) == 2 and all(
+                [isinstance(delimeter, basestring)
+                    for delimeter in delimeters])
+            self.default_namespace = self.__class__.default_namespace.copy()
+            self.default_namespace['start_braces'] = delimeters[0]
+            self.default_namespace['end_braces'] = delimeters[1]
+        self.delimeters = delimeters
+
+        self._unicode = is_unicode(content)
+        if name is None and stacklevel is not None:
+            try:
+                caller = sys._getframe(stacklevel)
+            except ValueError:
+                pass
+            else:
+                globals = caller.f_globals
+                lineno = caller.f_lineno
+                if '__file__' in globals:
+                    name = globals['__file__']
+                    if name.endswith('.pyc') or name.endswith('.pyo'):
+                        name = name[:-1]
+                elif '__name__' in globals:
+                    name = globals['__name__']
+                else:
+                    name = '<string>'
+                if lineno:
+                    name += ':%s' % lineno
+        self.name = name
+        self._parsed = parse(
+            content, name=name, line_offset=line_offset,
+            delimeters=self.delimeters)
+        if namespace is None:
+            namespace = {}
+        self.namespace = namespace
+        self.get_template = get_template
+        if default_inherit is not None:
+            self.default_inherit = default_inherit
+
+    def from_filename(cls, filename, namespace=None, encoding=None,
+                      default_inherit=None, get_template=get_file_template):
+        f = open(filename, 'rb')
+        c = f.read()
+        f.close()
+        if encoding:
+            c = c.decode(encoding)
+        return cls(content=c, name=filename, namespace=namespace,
+                   default_inherit=default_inherit, get_template=get_template)
+
+    from_filename = classmethod(from_filename)
+
+    def __repr__(self):
+        return '<%s %s name=%r>' % (
+            self.__class__.__name__,
+            hex(id(self))[2:], self.name)
+
+    def substitute(self, *args, **kw):
+        if args:
+            if kw:
+                raise TypeError(
+                    "You can only give positional *or* keyword arguments")
+            if len(args) > 1:
+                raise TypeError(
+                    "You can only give one positional argument")
+            if not hasattr(args[0], 'items'):
+                raise TypeError(
+                    ("If you pass in a single argument, you must pass in a ",
+                     "dict-like object (with a .items() method); you gave %r")
+                    % (args[0],))
+            kw = args[0]
+        ns = kw
+        ns['__template_name__'] = self.name
+        if self.namespace:
+            ns.update(self.namespace)
+        result, defs, inherit = self._interpret(ns)
+        if not inherit:
+            inherit = self.default_inherit
+        if inherit:
+            result = self._interpret_inherit(result, defs, inherit, ns)
+        return result
+
+    def _interpret(self, ns):
+        # __traceback_hide__ = True
+        parts = []
+        defs = {}
+        self._interpret_codes(self._parsed, ns, out=parts, defs=defs)
+        if '__inherit__' in defs:
+            inherit = defs.pop('__inherit__')
+        else:
+            inherit = None
+        return ''.join(parts), defs, inherit
+
+    def _interpret_inherit(self, body, defs, inherit_template, ns):
+        # __traceback_hide__ = True
+        if not self.get_template:
+            raise TemplateError(
+                'You cannot use inheritance without passing in get_template',
+                position=None, name=self.name)
+        templ = self.get_template(inherit_template, self)
+        self_ = TemplateObject(self.name)
+        for name, value in defs.iteritems():
+            setattr(self_, name, value)
+        self_.body = body
+        ns = ns.copy()
+        ns['self'] = self_
+        return templ.substitute(ns)
+
+    def _interpret_codes(self, codes, ns, out, defs):
+        # __traceback_hide__ = True
+        for item in codes:
+            if isinstance(item, basestring_):
+                out.append(item)
+            else:
+                self._interpret_code(item, ns, out, defs)
+
+    def _interpret_code(self, code, ns, out, defs):
+        # __traceback_hide__ = True
+        name, pos = code[0], code[1]
+        if name == 'py':
+            self._exec(code[2], ns, pos)
+        elif name == 'continue':
+            raise _TemplateContinue()
+        elif name == 'break':
+            raise _TemplateBreak()
+        elif name == 'for':
+            vars, expr, content = code[2], code[3], code[4]
+            expr = self._eval(expr, ns, pos)
+            self._interpret_for(vars, expr, content, ns, out, defs)
+        elif name == 'cond':
+            parts = code[2:]
+            self._interpret_if(parts, ns, out, defs)
+        elif name == 'expr':
+            parts = code[2].split('|')
+            base = self._eval(parts[0], ns, pos)
+            for part in parts[1:]:
+                func = self._eval(part, ns, pos)
+                base = func(base)
+            out.append(self._repr(base, pos))
+        elif name == 'default':
+            var, expr = code[2], code[3]
+            if var not in ns:
+                result = self._eval(expr, ns, pos)
+                ns[var] = result
+        elif name == 'inherit':
+            expr = code[2]
+            value = self._eval(expr, ns, pos)
+            defs['__inherit__'] = value
+        elif name == 'def':
+            name = code[2]
+            signature = code[3]
+            parts = code[4]
+            ns[name] = defs[name] = TemplateDef(
+                self, name, signature, body=parts, ns=ns, pos=pos)
+        elif name == 'comment':
+            return
+        else:
+            assert 0, "Unknown code: %r" % name
+
+    def _interpret_for(self, vars, expr, content, ns, out, defs):
+        # __traceback_hide__ = True
+        for item in expr:
+            if len(vars) == 1:
+                ns[vars[0]] = item
+            else:
+                if len(vars) != len(item):
+                    raise ValueError(
+                        'Need %i items to unpack (got %i items)'
+                        % (len(vars), len(item)))
+                for name, value in zip(vars, item):
+                    ns[name] = value
+            try:
+                self._interpret_codes(content, ns, out, defs)
+            except _TemplateContinue:
+                continue
+            except _TemplateBreak:
+                break
+
+    def _interpret_if(self, parts, ns, out, defs):
+        # __traceback_hide__ = True
+        # @@: if/else/else gets through
+        for part in parts:
+            assert not isinstance(part, basestring_)
+            name, pos = part[0], part[1]
+            if name == 'else':
+                result = True
+            else:
+                result = self._eval(part[2], ns, pos)
+            if result:
+                self._interpret_codes(part[3], ns, out, defs)
+                break
+
+    def _eval(self, code, ns, pos):
+        # __traceback_hide__ = True
+        try:
+            try:
+                value = eval(code, self.default_namespace, ns)
+            except SyntaxError as e:
+                raise SyntaxError(
+                    'invalid syntax in expression: %s' % code)
+            return value
+        except:
+            exc_info = sys.exc_info()
+            e = exc_info[1]
+            if getattr(e, 'args', None):
+                arg0 = e.args[0]
+            else:
+                arg0 = coerce_text(e)
+            e.args = (self._add_line_info(arg0, pos),)
+            raise (exc_info[1], e, exc_info[2])
+
+    def _exec(self, code, ns, pos):
+        # __traceback_hide__ = True
+        try:
+            exec(code, self.default_namespace, ns)
+        except:
+            exc_info = sys.exc_info()
+            e = exc_info[1]
+            if e.args:
+                e.args = (self._add_line_info(e.args[0], pos),)
+            else:
+                e.args = (self._add_line_info(None, pos),)
+            raise(exc_info[1], e, exc_info[2])
+
+    def _repr(self, value, pos):
+        # __traceback_hide__ = True
+        try:
+            if value is None:
+                return ''
+            if self._unicode:
+                try:
+                    value = str(value)
+                    if not is_unicode(value):
+                        value = value.decode('utf-8')
+                except UnicodeDecodeError:
+                    value = bytes(value)
+            else:
+                if not isinstance(value, basestring_):
+                    value = coerce_text(value)
+                if (is_unicode(value) and self.default_encoding):
+                    value = value.encode(self.default_encoding)
+        except:
+            exc_info = sys.exc_info()
+            e = exc_info[1]
+            e.args = (self._add_line_info(e.args[0], pos),)
+            # raise(exc_info[1], e, exc_info[2])
+            raise(e)
+        else:
+            if self._unicode and isinstance(value, bytes):
+                if not self.default_encoding:
+                    raise UnicodeDecodeError(
+                        'Cannot decode bytes value %r into unicode '
+                        '(no default_encoding provided)' % value)
+                try:
+                    value = value.decode(self.default_encoding)
+                except UnicodeDecodeError as e:
+                    raise UnicodeDecodeError(
+                        e.encoding,
+                        e.object,
+                        e.start,
+                        e.end,
+                        e.reason + ' in string %r' % value)
+            elif not self._unicode and is_unicode(value):
+                if not self.default_encoding:
+                    raise UnicodeEncodeError(
+                        'Cannot encode unicode value %r into bytes '
+                        '(no default_encoding provided)' % value)
+                value = value.encode(self.default_encoding)
+            return value
+
+    def _add_line_info(self, msg, pos):
+        msg = "%s at line %s column %s" % (
+            msg, pos[0], pos[1])
+        if self.name:
+            msg += " in file %s" % self.name
+        return msg
+
+
+def sub(content, delimeters=None, **kw):
+    name = kw.get('__name')
+    tmpl = Template(content, name=name, delimeters=delimeters)
+    return tmpl.substitute(kw)
+
+
+def paste_script_template_renderer(content, vars, filename=None):
+    tmpl = Template(content, name=filename)
+    return tmpl.substitute(vars)
+
+
+class bunch(dict):
+
+    def __init__(self, **kw):
+        for name, value in kw.iteritems():
+            setattr(self, name, value)
+
+    def __setattr__(self, name, value):
+        self[name] = value
+
+    def __getattr__(self, name):
+        try:
+            return self[name]
+        except KeyError:
+            raise AttributeError(name)
+
+    def __getitem__(self, key):
+        if 'default' in self:
+            try:
+                return dict.__getitem__(self, key)
+            except KeyError:
+                return dict.__getitem__(self, 'default')
+        else:
+            return dict.__getitem__(self, key)
+
+    def __repr__(self):
+        items = [
+            (k, v) for k, v in self.iteritems()]
+        items.sort()
+        return '<%s %s>' % (
+            self.__class__.__name__,
+            ' '.join(['%s=%r' % (k, v) for k, v in items]))
+
+############################################################
+## HTML Templating
+############################################################
+
+
+class html(object):
+
+    def __init__(self, value):
+        self.value = value
+
+    def __str__(self):
+        return self.value
+
+    def __html__(self):
+        return self.value
+
+    def __repr__(self):
+        return '<%s %r>' % (
+            self.__class__.__name__, self.value)
+
+
+def html_quote(value, force=True):
+    if not force and hasattr(value, '__html__'):
+        return value.__html__()
+    if value is None:
+        return ''
+    if not isinstance(value, basestring_):
+        value = coerce_text(value)
+    if sys.version >= "3" and isinstance(value, bytes):
+        value = cgi.escape(value.decode('latin1'), 1)
+        value = value.encode('latin1')
+    else:
+        with warnings.catch_warnings(record=True):  # annoying
+            value = cgi.escape(value, 1)
+    if sys.version < "3":
+        if is_unicode(value):
+            value = value.encode('ascii', 'xmlcharrefreplace')
+    return value
+
+
+def url(v):
+    v = coerce_text(v)
+    if is_unicode(v):
+        v = v.encode('utf8')
+    return url_quote(v)
+
+
+def attr(**kw):
+    kw = list(kw.iteritems())
+    kw.sort()
+    parts = []
+    for name, value in kw:
+        if value is None:
+            continue
+        if name.endswith('_'):
+            name = name[:-1]
+        parts.append('%s="%s"' % (html_quote(name), html_quote(value)))
+    return html(' '.join(parts))
+
+
+class HTMLTemplate(Template):
+
+    default_namespace = Template.default_namespace.copy()
+    default_namespace.update(dict(
+        html=html,
+        attr=attr,
+        url=url,
+        html_quote=html_quote))
+
+    def _repr(self, value, pos):
+        if hasattr(value, '__html__'):
+            value = value.__html__()
+            quote = False
+        else:
+            quote = True
+        plain = Template._repr(self, value, pos)
+        if quote:
+            return html_quote(plain)
+        else:
+            return plain
+
+
+def sub_html(content, **kw):
+    name = kw.get('__name')
+    tmpl = HTMLTemplate(content, name=name)
+    return tmpl.substitute(kw)
+
+
+class TemplateDef(object):
+    def __init__(self, template, func_name, func_signature,
+                 body, ns, pos, bound_self=None):
+        self._template = template
+        self._func_name = func_name
+        self._func_signature = func_signature
+        self._body = body
+        self._ns = ns
+        self._pos = pos
+        self._bound_self = bound_self
+
+    def __repr__(self):
+        return '<mne.externals.tempita function %s(%s) at %s:%s>' % (
+            self._func_name, self._func_signature,
+            self._template.name, self._pos)
+
+    def __str__(self):
+        return self()
+
+    def __call__(self, *args, **kw):
+        values = self._parse_signature(args, kw)
+        ns = self._ns.copy()
+        ns.update(values)
+        if self._bound_self is not None:
+            ns['self'] = self._bound_self
+        out = []
+        subdefs = {}
+        self._template._interpret_codes(self._body, ns, out, subdefs)
+        return ''.join(out)
+
+    def __get__(self, obj, type=None):
+        if obj is None:
+            return self
+        return self.__class__(
+            self._template, self._func_name, self._func_signature,
+            self._body, self._ns, self._pos, bound_self=obj)
+
+    def _parse_signature(self, args, kw):
+        values = {}
+        sig_args, var_args, var_kw, defaults = self._func_signature
+        extra_kw = {}
+        for name, value in kw.iteritems():
+            if not var_kw and name not in sig_args:
+                raise TypeError(
+                    'Unexpected argument %s' % name)
+            if name in sig_args:
+                values[sig_args] = value
+            else:
+                extra_kw[name] = value
+        args = list(args)
+        sig_args = list(sig_args)
+        while args:
+            while sig_args and sig_args[0] in values:
+                sig_args.pop(0)
+            if sig_args:
+                name = sig_args.pop(0)
+                values[name] = args.pop(0)
+            elif var_args:
+                values[var_args] = tuple(args)
+                break
+            else:
+                raise TypeError(
+                    'Extra position arguments: %s'
+                    % ', '.join(repr(v) for v in args))
+        for name, value_expr in defaults.iteritems():
+            if name not in values:
+                values[name] = self._template._eval(
+                    value_expr, self._ns, self._pos)
+        for name in sig_args:
+            if name not in values:
+                raise TypeError(
+                    'Missing argument: %s' % name)
+        if var_kw:
+            values[var_kw] = extra_kw
+        return values
+
+
+class TemplateObject(object):
+
+    def __init__(self, name):
+        self.__name = name
+        self.get = TemplateObjectGetter(self)
+
+    def __repr__(self):
+        return '<%s %s>' % (self.__class__.__name__, self.__name)
+
+
+class TemplateObjectGetter(object):
+
+    def __init__(self, template_obj):
+        self.__template_obj = template_obj
+
+    def __getattr__(self, attr):
+        return getattr(self.__template_obj, attr, Empty)
+
+    def __repr__(self):
+        return '<%s around %r>' % (
+            self.__class__.__name__, self.__template_obj)
+
+
+class _Empty(object):
+    def __call__(self, *args, **kw):
+        return self
+
+    def __str__(self):
+        return ''
+
+    def __repr__(self):
+        return 'Empty'
+
+    def __unicode__(self):
+        if PY3:
+            return str('')
+        else:
+            return unicode('')
+
+    def __iter__(self):
+        return iter(())
+
+    def __bool__(self):
+        return False
+
+    if sys.version < "3":
+        __nonzero__ = __bool__
+
+Empty = _Empty()
+del _Empty
+
+############################################################
+## Lexing and Parsing
+############################################################
+
+
+def lex(s, name=None, trim_whitespace=True, line_offset=0, delimeters=None):
+    if delimeters is None:
+        delimeters = (Template.default_namespace['start_braces'],
+                      Template.default_namespace['end_braces'])
+    in_expr = False
+    chunks = []
+    last = 0
+    last_pos = (line_offset + 1, 1)
+    token_re = re.compile(r'%s|%s' % (re.escape(delimeters[0]),
+                                      re.escape(delimeters[1])))
+    for match in token_re.finditer(s):
+        expr = match.group(0)
+        pos = find_position(s, match.end(), last, last_pos)
+        if expr == delimeters[0] and in_expr:
+            raise TemplateError('%s inside expression' % delimeters[0],
+                                position=pos,
+                                name=name)
+        elif expr == delimeters[1] and not in_expr:
+            raise TemplateError('%s outside expression' % delimeters[1],
+                                position=pos,
+                                name=name)
+        if expr == delimeters[0]:
+            part = s[last:match.start()]
+            if part:
+                chunks.append(part)
+            in_expr = True
+        else:
+            chunks.append((s[last:match.start()], last_pos))
+            in_expr = False
+        last = match.end()
+        last_pos = pos
+    if in_expr:
+        raise TemplateError('No %s to finish last expression' % delimeters[1],
+                            name=name, position=last_pos)
+    part = s[last:]
+    if part:
+        chunks.append(part)
+    if trim_whitespace:
+        chunks = trim_lex(chunks)
+    return chunks
+
+lex.__doc__ = """
+Lex a string into chunks:
+
+    >>> lex('hey')
+    ['hey']
+    >>> lex('hey {{you}}')
+    ['hey ', ('you', (1, 7))]
+    >>> lex('hey {{')
+    Traceback (most recent call last):
+        ...
+    mne.externals.tempita.TemplateError: No }} to finish last expression at line 1 column 7
+    >>> lex('hey }}')
+    Traceback (most recent call last):
+        ...
+    mne.externals.tempita.TemplateError: }} outside expression at line 1 column 7
+    >>> lex('hey {{ {{')
+    Traceback (most recent call last):
+        ...
+    mne.externals.tempita.TemplateError: {{ inside expression at line 1 column 10
+
+""" if PY3 else """
+Lex a string into chunks:
+
+    >>> lex('hey')
+    ['hey']
+    >>> lex('hey {{you}}')
+    ['hey ', ('you', (1, 7))]
+    >>> lex('hey {{')
+    Traceback (most recent call last):
+        ...
+    TemplateError: No }} to finish last expression at line 1 column 7
+    >>> lex('hey }}')
+    Traceback (most recent call last):
+        ...
+    TemplateError: }} outside expression at line 1 column 7
+    >>> lex('hey {{ {{')
+    Traceback (most recent call last):
+        ...
+    TemplateError: {{ inside expression at line 1 column 10
+
+"""
+
+statement_re = re.compile(r'^(?:if |elif |for |def |inherit |default |py:)')
+single_statements = ['else', 'endif', 'endfor', 'enddef', 'continue', 'break']
+trail_whitespace_re = re.compile(r'\n\r?[\t ]*$')
+lead_whitespace_re = re.compile(r'^[\t ]*\n')
+
+
+def trim_lex(tokens):
+    last_trim = None
+    for i in range(len(tokens)):
+        current = tokens[i]
+        if isinstance(tokens[i], basestring_):
+            # we don't trim this
+            continue
+        item = current[0]
+        if not statement_re.search(item) and item not in single_statements:
+            continue
+        if not i:
+            prev = ''
+        else:
+            prev = tokens[i - 1]
+        if i + 1 >= len(tokens):
+            next_chunk = ''
+        else:
+            next_chunk = tokens[i + 1]
+        if (not
+                isinstance(next_chunk, basestring_)
+                or not isinstance(prev, basestring_)):
+            continue
+        prev_ok = not prev or trail_whitespace_re.search(prev)
+        if i == 1 and not prev.strip():
+            prev_ok = True
+        if last_trim is not None and last_trim + 2 == i and not prev.strip():
+            prev_ok = 'last'
+        if (prev_ok
+            and (not next_chunk or lead_whitespace_re.search(next_chunk)
+                 or (i == len(tokens) - 2 and not next_chunk.strip()))):
+            if prev:
+                if ((i == 1 and not prev.strip()) or prev_ok == 'last'):
+                    tokens[i - 1] = ''
+                else:
+                    m = trail_whitespace_re.search(prev)
+                    # +1 to leave the leading \n on:
+                    prev = prev[:m.start() + 1]
+                    tokens[i - 1] = prev
+            if next_chunk:
+                last_trim = i
+                if i == len(tokens) - 2 and not next_chunk.strip():
+                    tokens[i + 1] = ''
+                else:
+                    m = lead_whitespace_re.search(next_chunk)
+                    next_chunk = next_chunk[m.end():]
+                    tokens[i + 1] = next_chunk
+    return tokens
+
+trim_lex.__doc__ = r"""
+    Takes a lexed set of tokens, and removes whitespace when there is
+    a directive on a line by itself:
+
+       >>> tokens = lex('{{if x}}\nx\n{{endif}}\ny', trim_whitespace=False)
+       >>> tokens
+       [('if x', (1, 3)), '\nx\n', ('endif', (3, 3)), '\ny']
+       >>> trim_lex(tokens)
+       [('if x', (1, 3)), 'x\n', ('endif', (3, 3)), 'y']
+    """ if PY3 else r"""
+    Takes a lexed set of tokens, and removes whitespace when there is
+    a directive on a line by itself:
+
+       >>> tokens = lex('{{if x}}\nx\n{{endif}}\ny', trim_whitespace=False)
+       >>> tokens
+       [('if x', (1, 3)), '\nx\n', ('endif', (3, 3)), '\ny']
+       >>> trim_lex(tokens)
+       [('if x', (1, 3)), 'x\n', ('endif', (3, 3)), 'y']
+    """
+
+
+def find_position(string, index, last_index, last_pos):
+    """
+    Given a string and index, return (line, column)
+    """
+    lines = string.count('\n', last_index, index)
+    if lines > 0:
+        column = index - string.rfind('\n', last_index, index)
+    else:
+        column = last_pos[1] + (index - last_index)
+    return (last_pos[0] + lines, column)
+
+
+def parse(s, name=None, line_offset=0, delimeters=None):
+
+    if delimeters is None:
+        delimeters = (Template.default_namespace['start_braces'],
+                      Template.default_namespace['end_braces'])
+    tokens = lex(s, name=name, line_offset=line_offset, delimeters=delimeters)
+    result = []
+    while tokens:
+        next_chunk, tokens = parse_expr(tokens, name)
+        result.append(next_chunk)
+    return result
+
+parse.__doc__ = r"""
+    Parses a string into a kind of AST
+
+        >>> parse('{{x}}')
+        [('expr', (1, 3), 'x')]
+        >>> parse('foo')
+        ['foo']
+        >>> parse('{{if x}}test{{endif}}')
+        [('cond', (1, 3), ('if', (1, 3), 'x', ['test']))]
+        >>> parse(
+        ...    'series->{{for x in y}}x={{x}}{{endfor}}'
+        ... )  #doctest: +NORMALIZE_WHITESPACE
+        ['series->',
+            ('for', (1, 11), ('x',), 'y', ['x=', ('expr', (1, 27), 'x')])]
+        >>> parse('{{for x, y in z:}}{{continue}}{{endfor}}')
+        [('for', (1, 3), ('x', 'y'), 'z', [('continue', (1, 21))])]
+        >>> parse('{{py:x=1}}')
+        [('py', (1, 3), 'x=1')]
+        >>> parse(
+        ...    '{{if x}}a{{elif y}}b{{else}}c{{endif}}'
+        ... )  #doctest: +NORMALIZE_WHITESPACE
+        [('cond', (1, 3), ('if', (1, 3), 'x', ['a']),
+            ('elif', (1, 12), 'y', ['b']), ('else', (1, 23), None, ['c']))]
+
+    Some exceptions::
+
+        >>> parse('{{continue}}')
+        Traceback (most recent call last):
+            ...
+        mne.externals.tempita.TemplateError: continue outside of for loop at line 1 column 3
+        >>> parse('{{if x}}foo')
+        Traceback (most recent call last):
+            ...
+        mne.externals.tempita.TemplateError: No {{endif}} at line 1 column 3
+        >>> parse('{{else}}')
+        Traceback (most recent call last):
+            ...
+        mne.externals.tempita.TemplateError: else outside of an if block at line 1 column 3
+        >>> parse('{{if x}}{{for x in y}}{{endif}}{{endfor}}')
+        Traceback (most recent call last):
+            ...
+        mne.externals.tempita.TemplateError: Unexpected endif at line 1 column 25
+        >>> parse('{{if}}{{endif}}')
+        Traceback (most recent call last):
+            ...
+        mne.externals.tempita.TemplateError: if with no expression at line 1 column 3
+        >>> parse('{{for x y}}{{endfor}}')
+        Traceback (most recent call last):
+            ...
+        mne.externals.tempita.TemplateError: Bad for (no "in") in 'x y' at line 1 column 3
+        >>> parse('{{py:x=1\ny=2}}')  #doctest: +NORMALIZE_WHITESPACE
+        Traceback (most recent call last):
+            ...
+        mne.externals.tempita.TemplateError: Multi-line py blocks must start
+            with a newline at line 1 column 3
+    """ if PY3 else r"""
+    Parses a string into a kind of AST
+
+        >>> parse('{{x}}')
+        [('expr', (1, 3), 'x')]
+        >>> parse('foo')
+        ['foo']
+        >>> parse('{{if x}}test{{endif}}')
+        [('cond', (1, 3), ('if', (1, 3), 'x', ['test']))]
+        >>> parse(
+        ...    'series->{{for x in y}}x={{x}}{{endfor}}'
+        ... )  #doctest: +NORMALIZE_WHITESPACE
+        ['series->',
+            ('for', (1, 11), ('x',), 'y', ['x=', ('expr', (1, 27), 'x')])]
+        >>> parse('{{for x, y in z:}}{{continue}}{{endfor}}')
+        [('for', (1, 3), ('x', 'y'), 'z', [('continue', (1, 21))])]
+        >>> parse('{{py:x=1}}')
+        [('py', (1, 3), 'x=1')]
+        >>> parse(
+        ...    '{{if x}}a{{elif y}}b{{else}}c{{endif}}'
+        ... )  #doctest: +NORMALIZE_WHITESPACE
+        [('cond', (1, 3), ('if', (1, 3), 'x', ['a']),
+            ('elif', (1, 12), 'y', ['b']), ('else', (1, 23), None, ['c']))]
+
+    Some exceptions::
+
+        >>> parse('{{continue}}')
+        Traceback (most recent call last):
+            ...
+        TemplateError: continue outside of for loop at line 1 column 3
+        >>> parse('{{if x}}foo')
+        Traceback (most recent call last):
+            ...
+        TemplateError: No {{endif}} at line 1 column 3
+        >>> parse('{{else}}')
+        Traceback (most recent call last):
+            ...
+        TemplateError: else outside of an if block at line 1 column 3
+        >>> parse('{{if x}}{{for x in y}}{{endif}}{{endfor}}')
+        Traceback (most recent call last):
+            ...
+        TemplateError: Unexpected endif at line 1 column 25
+        >>> parse('{{if}}{{endif}}')
+        Traceback (most recent call last):
+            ...
+        TemplateError: if with no expression at line 1 column 3
+        >>> parse('{{for x y}}{{endfor}}')
+        Traceback (most recent call last):
+            ...
+        TemplateError: Bad for (no "in") in 'x y' at line 1 column 3
+        >>> parse('{{py:x=1\ny=2}}')  #doctest: +NORMALIZE_WHITESPACE
+        Traceback (most recent call last):
+            ...
+        TemplateError: Multi-line py blocks must start
+            with a newline at line 1 column 3
+    """
+
+
+def parse_expr(tokens, name, context=()):
+    if isinstance(tokens[0], basestring_):
+        return tokens[0], tokens[1:]
+    expr, pos = tokens[0]
+    expr = expr.strip()
+    if expr.startswith('py:'):
+        expr = expr[3:].lstrip(' \t')
+        if expr.startswith('\n') or expr.startswith('\r'):
+            expr = expr.lstrip('\r\n')
+            if '\r' in expr:
+                expr = expr.replace('\r\n', '\n')
+                expr = expr.replace('\r', '')
+            expr += '\n'
+        else:
+            if '\n' in expr:
+                raise TemplateError(
+                    'Multi-line py blocks must start with a newline',
+                    position=pos, name=name)
+        return ('py', pos, expr), tokens[1:]
+    elif expr in ('continue', 'break'):
+        if 'for' not in context:
+            raise TemplateError(
+                'continue outside of for loop',
+                position=pos, name=name)
+        return (expr, pos), tokens[1:]
+    elif expr.startswith('if '):
+        return parse_cond(tokens, name, context)
+    elif (expr.startswith('elif ')
+          or expr == 'else'):
+        raise TemplateError(
+            '%s outside of an if block' % expr.split()[0],
+            position=pos, name=name)
+    elif expr in ('if', 'elif', 'for'):
+        raise TemplateError(
+            '%s with no expression' % expr,
+            position=pos, name=name)
+    elif expr in ('endif', 'endfor', 'enddef'):
+        raise TemplateError(
+            'Unexpected %s' % expr,
+            position=pos, name=name)
+    elif expr.startswith('for '):
+        return parse_for(tokens, name, context)
+    elif expr.startswith('default '):
+        return parse_default(tokens, name, context)
+    elif expr.startswith('inherit '):
+        return parse_inherit(tokens, name, context)
+    elif expr.startswith('def '):
+        return parse_def(tokens, name, context)
+    elif expr.startswith('#'):
+        return ('comment', pos, tokens[0][0]), tokens[1:]
+    return ('expr', pos, tokens[0][0]), tokens[1:]
+
+
+def parse_cond(tokens, name, context):
+    start = tokens[0][1]
+    pieces = []
+    context = context + ('if',)
+    while 1:
+        if not tokens:
+            raise TemplateError(
+                'Missing {{endif}}',
+                position=start, name=name)
+        if (isinstance(tokens[0], tuple) and tokens[0][0] == 'endif'):
+            return ('cond', start) + tuple(pieces), tokens[1:]
+        next_chunk, tokens = parse_one_cond(tokens, name, context)
+        pieces.append(next_chunk)
+
+
+def parse_one_cond(tokens, name, context):
+    (first, pos), tokens = tokens[0], tokens[1:]
+    content = []
+    if first.endswith(':'):
+        first = first[:-1]
+    if first.startswith('if '):
+        part = ('if', pos, first[3:].lstrip(), content)
+    elif first.startswith('elif '):
+        part = ('elif', pos, first[5:].lstrip(), content)
+    elif first == 'else':
+        part = ('else', pos, None, content)
+    else:
+        assert 0, "Unexpected token %r at %s" % (first, pos)
+    while 1:
+        if not tokens:
+            raise TemplateError(
+                'No {{endif}}',
+                position=pos, name=name)
+        if (isinstance(tokens[0], tuple)
+            and (tokens[0][0] == 'endif'
+                 or tokens[0][0].startswith('elif ')
+                 or tokens[0][0] == 'else')):
+            return part, tokens
+        next_chunk, tokens = parse_expr(tokens, name, context)
+        content.append(next_chunk)
+
+
+def parse_for(tokens, name, context):
+    first, pos = tokens[0]
+    tokens = tokens[1:]
+    context = ('for',) + context
+    content = []
+    assert first.startswith('for ')
+    if first.endswith(':'):
+        first = first[:-1]
+    first = first[3:].strip()
+    match = in_re.search(first)
+    if not match:
+        raise TemplateError(
+            'Bad for (no "in") in %r' % first,
+            position=pos, name=name)
+    vars = first[:match.start()]
+    if '(' in vars:
+        raise TemplateError(
+            'You cannot have () in the variable section of a for loop (%r)'
+            % vars, position=pos, name=name)
+    vars = tuple([
+        v.strip() for v in first[:match.start()].split(',')
+        if v.strip()])
+    expr = first[match.end():]
+    while 1:
+        if not tokens:
+            raise TemplateError(
+                'No {{endfor}}',
+                position=pos, name=name)
+        if (isinstance(tokens[0], tuple) and tokens[0][0] == 'endfor'):
+            return ('for', pos, vars, expr, content), tokens[1:]
+        next_chunk, tokens = parse_expr(tokens, name, context)
+        content.append(next_chunk)
+
+
+def parse_default(tokens, name, context):
+    first, pos = tokens[0]
+    assert first.startswith('default ')
+    first = first.split(None, 1)[1]
+    parts = first.split('=', 1)
+    if len(parts) == 1:
+        raise TemplateError(
+            "Expression must be {{default var=value}}; no = found in %r" %
+            first, position=pos, name=name)
+    var = parts[0].strip()
+    if ',' in var:
+        raise TemplateError(
+            "{{default x, y = ...}} is not supported",
+            position=pos, name=name)
+    if not var_re.search(var):
+        raise TemplateError(
+            "Not a valid variable name for {{default}}: %r"
+            % var, position=pos, name=name)
+    expr = parts[1].strip()
+    return ('default', pos, var, expr), tokens[1:]
+
+
+def parse_inherit(tokens, name, context):
+    first, pos = tokens[0]
+    assert first.startswith('inherit ')
+    expr = first.split(None, 1)[1]
+    return ('inherit', pos, expr), tokens[1:]
+
+
+def parse_def(tokens, name, context):
+    first, start = tokens[0]
+    tokens = tokens[1:]
+    assert first.startswith('def ')
+    first = first.split(None, 1)[1]
+    if first.endswith(':'):
+        first = first[:-1]
+    if '(' not in first:
+        func_name = first
+        sig = ((), None, None, {})
+    elif not first.endswith(')'):
+        raise TemplateError("Function definition doesn't end with ): %s" %
+                            first, position=start, name=name)
+    else:
+        first = first[:-1]
+        func_name, sig_text = first.split('(', 1)
+        sig = parse_signature(sig_text, name, start)
+    context = context + ('def',)
+    content = []
+    while 1:
+        if not tokens:
+            raise TemplateError(
+                'Missing {{enddef}}',
+                position=start, name=name)
+        if (isinstance(tokens[0], tuple) and tokens[0][0] == 'enddef'):
+            return ('def', start, func_name, sig, content), tokens[1:]
+        next_chunk, tokens = parse_expr(tokens, name, context)
+        content.append(next_chunk)
+
+
+def parse_signature(sig_text, name, pos):
+    tokens = tokenize.generate_tokens(StringIO(sig_text).readline)
+    sig_args = []
+    var_arg = None
+    var_kw = None
+    defaults = {}
+
+    def get_token(pos=False):
+        try:
+            tok_type, tok_string, (srow, scol), (erow, ecol), line = next(
+                tokens)
+        except StopIteration:
+            return tokenize.ENDMARKER, ''
+        if pos:
+            return tok_type, tok_string, (srow, scol), (erow, ecol)
+        else:
+            return tok_type, tok_string
+    while 1:
+        var_arg_type = None
+        tok_type, tok_string = get_token()
+        if tok_type == tokenize.ENDMARKER:
+            break
+        if tok_type == tokenize.OP and (
+                tok_string == '*' or tok_string == '**'):
+            var_arg_type = tok_string
+            tok_type, tok_string = get_token()
+        if tok_type != tokenize.NAME:
+            raise TemplateError('Invalid signature: (%s)' % sig_text,
+                                position=pos, name=name)
+        var_name = tok_string
+        tok_type, tok_string = get_token()
+        if tok_type == tokenize.ENDMARKER or (
+                tok_type == tokenize.OP and tok_string == ','):
+            if var_arg_type == '*':
+                var_arg = var_name
+            elif var_arg_type == '**':
+                var_kw = var_name
+            else:
+                sig_args.append(var_name)
+            if tok_type == tokenize.ENDMARKER:
+                break
+            continue
+        if var_arg_type is not None:
+            raise TemplateError('Invalid signature: (%s)' % sig_text,
+                                position=pos, name=name)
+        if tok_type == tokenize.OP and tok_string == '=':
+            nest_type = None
+            unnest_type = None
+            nest_count = 0
+            start_pos = end_pos = None
+            parts = []
+            while 1:
+                tok_type, tok_string, s, e = get_token(True)
+                if start_pos is None:
+                    start_pos = s
+                end_pos = e
+                if tok_type == tokenize.ENDMARKER and nest_count:
+                    raise TemplateError('Invalid signature: (%s)' % sig_text,
+                                        position=pos, name=name)
+                if (not nest_count and
+                    (tok_type == tokenize.ENDMARKER or
+                        (tok_type == tokenize.OP and tok_string == ','))):
+                    default_expr = isolate_expression(
+                        sig_text, start_pos, end_pos)
+                    defaults[var_name] = default_expr
+                    sig_args.append(var_name)
+                    break
+                parts.append((tok_type, tok_string))
+                if nest_count \
+                        and tok_type == tokenize.OP \
+                        and tok_string == nest_type:
+                    nest_count += 1
+                elif nest_count \
+                        and tok_type == tokenize.OP \
+                        and tok_string == unnest_type:
+                    nest_count -= 1
+                    if not nest_count:
+                        nest_type = unnest_type = None
+                elif not nest_count \
+                        and tok_type == tokenize.OP \
+                        and tok_string in ('(', '[', '{'):
+                    nest_type = tok_string
+                    nest_count = 1
+                    unnest_type = {'(': ')', '[': ']', '{': '}'}[nest_type]
+    return sig_args, var_arg, var_kw, defaults
+
+
+def isolate_expression(string, start_pos, end_pos):
+    srow, scol = start_pos
+    srow -= 1
+    erow, ecol = end_pos
+    erow -= 1
+    lines = string.splitlines(True)
+    if srow == erow:
+        return lines[srow][scol:ecol]
+    parts = [lines[srow][scol:]]
+    parts.extend(lines[srow + 1:erow])
+    if erow < len(lines):
+        # It'll sometimes give (end_row_past_finish, 0)
+        parts.append(lines[erow][:ecol])
+    return ''.join(parts)
+
+_fill_command_usage = """\
+%prog [OPTIONS] TEMPLATE arg=value
+
+Use py:arg=value to set a Python value; otherwise all values are
+strings.
+"""
+
+
+def fill_command(args=None):
+    import sys
+    import optparse
+    import pkg_resources
+    import os
+    if args is None:
+        args = sys.argv[1:]
+    dist = pkg_resources.get_distribution('Paste')
+    parser = optparse.OptionParser(
+        version=coerce_text(dist),
+        usage=_fill_command_usage)
+    parser.add_option(
+        '-o', '--output',
+        dest='output',
+        metavar="FILENAME",
+        help="File to write output to (default stdout)")
+    parser.add_option(
+        '--html',
+        dest='use_html',
+        action='store_true',
+        help="Use HTML style filling (including automatic HTML quoting)")
+    parser.add_option(
+        '--env',
+        dest='use_env',
+        action='store_true',
+        help="Put the environment in as top-level variables")
+    options, args = parser.parse_args(args)
+    if len(args) < 1:
+        print('You must give a template filename')
+        sys.exit(2)
+    template_name = args[0]
+    args = args[1:]
+    vars = {}
+    if options.use_env:
+        vars.update(os.environ)
+    for value in args:
+        if '=' not in value:
+            print('Bad argument: %r' % value)
+            sys.exit(2)
+        name, value = value.split('=', 1)
+        if name.startswith('py:'):
+            name = name[:3]
+            value = eval(value)
+        vars[name] = value
+    if template_name == '-':
+        template_content = sys.stdin.read()
+        template_name = '<stdin>'
+    else:
+        f = open(template_name, 'rb')
+        template_content = f.read()
+        f.close()
+    if options.use_html:
+        TemplateClass = HTMLTemplate
+    else:
+        TemplateClass = Template
+    template = TemplateClass(template_content, name=template_name)
+    result = template.substitute(vars)
+    if options.output:
+        f = open(options.output, 'wb')
+        f.write(result)
+        f.close()
+    else:
+        sys.stdout.write(result)
+
+if __name__ == '__main__':
+    fill_command()
diff --git a/mne/externals/tempita/_looper.py b/mne/externals/tempita/_looper.py
index 4413a5b..4b480b4 100644
--- a/mne/externals/tempita/_looper.py
+++ b/mne/externals/tempita/_looper.py
@@ -7,9 +7,9 @@ These can be awkward to manage in a normal Python loop, but using the
 looper you can get a better sense of the context.  Use like::
 
     >>> for loop, item in looper(['a', 'b', 'c']):
-    ...     print loop.number, item
+    ...     print("%d %s" % (loop.number, item))
     ...     if not loop.last:
-    ...         print '---'
+    ...         print('---')
     1 a
     ---
     2 b
diff --git a/mne/externals/tempita/_tempita.py b/mne/externals/tempita/_tempita.py
deleted file mode 100644
index cb5c7ac..0000000
--- a/mne/externals/tempita/_tempita.py
+++ /dev/null
@@ -1,1182 +0,0 @@
-"""
-A small templating language
-
-This implements a small templating language.  This language implements
-if/elif/else, for/continue/break, expressions, and blocks of Python
-code.  The syntax is::
-
-  {{any expression (function calls etc)}}
-  {{any expression | filter}}
-  {{for x in y}}...{{endfor}}
-  {{if x}}x{{elif y}}y{{else}}z{{endif}}
-  {{py:x=1}}
-  {{py:
-  def foo(bar):
-      return 'baz'
-  }}
-  {{default var = default_value}}
-  {{# comment}}
-
-You use this with the ``Template`` class or the ``sub`` shortcut.
-The ``Template`` class takes the template string and the name of
-the template (for errors) and a default namespace.  Then (like
-``string.Template``) you can call the ``tmpl.substitute(**kw)``
-method to make a substitution (or ``tmpl.substitute(a_dict)``).
-
-``sub(content, **kw)`` substitutes the template immediately.  You
-can use ``__name='tmpl.html'`` to set the name of the template.
-
-If there are syntax errors ``TemplateError`` will be raised.
-"""
-
-from __future__ import absolute_import
-
-import re
-import sys
-import cgi
-try:
-    from urllib import quote as url_quote
-except ImportError:  # Py3
-    from urllib.parse import quote as url_quote
-import os
-import tokenize
-from io import StringIO
-
-from ._looper import looper
-from .compat3 import bytes, unicode_, basestring_, next, is_unicode, coerce_text
-
-__all__ = ['TemplateError', 'Template', 'sub', 'HTMLTemplate',
-           'sub_html', 'html', 'bunch']
-
-in_re = re.compile(r'\s+in\s+')
-var_re = re.compile(r'^[a-z_][a-z0-9_]*$', re.I)
-
-
-class TemplateError(Exception):
-    """Exception raised while parsing a template
-    """
-
-    def __init__(self, message, position, name=None):
-        Exception.__init__(self, message)
-        self.position = position
-        self.name = name
-
-    def __str__(self):
-        msg = ' '.join(self.args)
-        if self.position:
-            msg = '%s at line %s column %s' % (
-                msg, self.position[0], self.position[1])
-        if self.name:
-            msg += ' in %s' % self.name
-        return msg
-
-
-class _TemplateContinue(Exception):
-    pass
-
-
-class _TemplateBreak(Exception):
-    pass
-
-
-def get_file_template(name, from_template):
-    path = os.path.join(os.path.dirname(from_template.name), name)
-    return from_template.__class__.from_filename(
-        path, namespace=from_template.namespace,
-        get_template=from_template.get_template)
-
-
-class Template(object):
-
-    default_namespace = {
-        'start_braces': '{{',
-        'end_braces': '}}',
-        'looper': looper,
-        }
-
-    default_encoding = 'utf8'
-    default_inherit = None
-
-    def __init__(self, content, name=None, namespace=None, stacklevel=None,
-                 get_template=None, default_inherit=None, line_offset=0,
-                 delimeters=None):
-        self.content = content
-
-        # set delimeters
-        if delimeters is None:
-            delimeters = (self.default_namespace['start_braces'],
-                          self.default_namespace['end_braces'])
-        else:
-            #assert len(delimeters) == 2 and all([isinstance(delimeter, basestring)
-            #                                     for delimeter in delimeters])
-            self.default_namespace = self.__class__.default_namespace.copy()
-            self.default_namespace['start_braces'] = delimeters[0]
-            self.default_namespace['end_braces'] = delimeters[1]
-        self.delimeters = delimeters
-        
-        self._unicode = is_unicode(content)
-        if name is None and stacklevel is not None:
-            try:
-                caller = sys._getframe(stacklevel)
-            except ValueError:
-                pass
-            else:
-                globals = caller.f_globals
-                lineno = caller.f_lineno
-                if '__file__' in globals:
-                    name = globals['__file__']
-                    if name.endswith('.pyc') or name.endswith('.pyo'):
-                        name = name[:-1]
-                elif '__name__' in globals:
-                    name = globals['__name__']
-                else:
-                    name = '<string>'
-                if lineno:
-                    name += ':%s' % lineno
-        self.name = name
-        self._parsed = parse(content, name=name, line_offset=line_offset, delimeters=self.delimeters)
-        if namespace is None:
-            namespace = {}
-        self.namespace = namespace
-        self.get_template = get_template
-        if default_inherit is not None:
-            self.default_inherit = default_inherit
-
-    def from_filename(cls, filename, namespace=None, encoding=None,
-                      default_inherit=None, get_template=get_file_template):
-        f = open(filename, 'rb')
-        c = f.read()
-        f.close()
-        if encoding:
-            c = c.decode(encoding)
-        return cls(content=c, name=filename, namespace=namespace,
-                   default_inherit=default_inherit, get_template=get_template)
-
-    from_filename = classmethod(from_filename)
-
-    def __repr__(self):
-        return '<%s %s name=%r>' % (
-            self.__class__.__name__,
-            hex(id(self))[2:], self.name)
-
-    def substitute(self, *args, **kw):
-        if args:
-            if kw:
-                raise TypeError(
-                    "You can only give positional *or* keyword arguments")
-            if len(args) > 1:
-                raise TypeError(
-                    "You can only give one positional argument")
-            if not hasattr(args[0], 'items'):
-                raise TypeError(
-                    "If you pass in a single argument, you must pass in a dictionary-like object (with a .items() method); you gave %r"
-                    % (args[0],))
-            kw = args[0]
-        ns = kw
-        ns['__template_name__'] = self.name
-        if self.namespace:
-            ns.update(self.namespace)
-        result, defs, inherit = self._interpret(ns)
-        if not inherit:
-            inherit = self.default_inherit
-        if inherit:
-            result = self._interpret_inherit(result, defs, inherit, ns)
-        return result
-
-    def _interpret(self, ns):
-        __traceback_hide__ = True
-        parts = []
-        defs = {}
-        self._interpret_codes(self._parsed, ns, out=parts, defs=defs)
-        if '__inherit__' in defs:
-            inherit = defs.pop('__inherit__')
-        else:
-            inherit = None
-        return ''.join(parts), defs, inherit
-
-    def _interpret_inherit(self, body, defs, inherit_template, ns):
-        __traceback_hide__ = True
-        if not self.get_template:
-            raise TemplateError(
-                'You cannot use inheritance without passing in get_template',
-                position=None, name=self.name)
-        templ = self.get_template(inherit_template, self)
-        self_ = TemplateObject(self.name)
-        for name, value in defs.items():
-            setattr(self_, name, value)
-        self_.body = body
-        ns = ns.copy()
-        ns['self'] = self_
-        return templ.substitute(ns)
-
-    def _interpret_codes(self, codes, ns, out, defs):
-        __traceback_hide__ = True
-        for item in codes:
-            if isinstance(item, basestring_):
-                out.append(item)
-            else:
-                self._interpret_code(item, ns, out, defs)
-
-    def _interpret_code(self, code, ns, out, defs):
-        __traceback_hide__ = True
-        name, pos = code[0], code[1]
-        if name == 'py':
-            self._exec(code[2], ns, pos)
-        elif name == 'continue':
-            raise _TemplateContinue()
-        elif name == 'break':
-            raise _TemplateBreak()
-        elif name == 'for':
-            vars, expr, content = code[2], code[3], code[4]
-            expr = self._eval(expr, ns, pos)
-            self._interpret_for(vars, expr, content, ns, out, defs)
-        elif name == 'cond':
-            parts = code[2:]
-            self._interpret_if(parts, ns, out, defs)
-        elif name == 'expr':
-            parts = code[2].split('|')
-            base = self._eval(parts[0], ns, pos)
-            for part in parts[1:]:
-                func = self._eval(part, ns, pos)
-                base = func(base)
-            out.append(self._repr(base, pos))
-        elif name == 'default':
-            var, expr = code[2], code[3]
-            if var not in ns:
-                result = self._eval(expr, ns, pos)
-                ns[var] = result
-        elif name == 'inherit':
-            expr = code[2]
-            value = self._eval(expr, ns, pos)
-            defs['__inherit__'] = value
-        elif name == 'def':
-            name = code[2]
-            signature = code[3]
-            parts = code[4]
-            ns[name] = defs[name] = TemplateDef(self, name, signature, body=parts, ns=ns,
-                                                pos=pos)
-        elif name == 'comment':
-            return
-        else:
-            assert 0, "Unknown code: %r" % name
-
-    def _interpret_for(self, vars, expr, content, ns, out, defs):
-        __traceback_hide__ = True
-        for item in expr:
-            if len(vars) == 1:
-                ns[vars[0]] = item
-            else:
-                if len(vars) != len(item):
-                    raise ValueError(
-                        'Need %i items to unpack (got %i items)'
-                        % (len(vars), len(item)))
-                for name, value in zip(vars, item):
-                    ns[name] = value
-            try:
-                self._interpret_codes(content, ns, out, defs)
-            except _TemplateContinue:
-                continue
-            except _TemplateBreak:
-                break
-
-    def _interpret_if(self, parts, ns, out, defs):
-        __traceback_hide__ = True
-        # @@: if/else/else gets through
-        for part in parts:
-            assert not isinstance(part, basestring_)
-            name, pos = part[0], part[1]
-            if name == 'else':
-                result = True
-            else:
-                result = self._eval(part[2], ns, pos)
-            if result:
-                self._interpret_codes(part[3], ns, out, defs)
-                break
-
-    def _eval(self, code, ns, pos):
-        __traceback_hide__ = True
-        try:
-            try:
-                value = eval(code, self.default_namespace, ns)
-            except SyntaxError as e:
-                raise SyntaxError(
-                    'invalid syntax in expression: %s' % code)
-            return value
-        except Exception as e:
-            if getattr(e, 'args', None):
-                arg0 = e.args[0]
-            else:
-                arg0 = coerce_text(e)
-            e.args = (self._add_line_info(arg0, pos),)
-            raise
-
-    def _exec(self, code, ns, pos):
-        __traceback_hide__ = True
-        try:
-            exec(code, self.default_namespace, ns)
-        except Exception as e:
-            if e.args:
-                e.args = (self._add_line_info(e.args[0], pos),)
-            else:
-                e.args = (self._add_line_info(None, pos),)
-            raise
-
-    def _repr(self, value, pos):
-        __traceback_hide__ = True
-        try:
-            if value is None:
-                return ''
-            if self._unicode:
-                try:
-                    value = unicode_(value)
-                except UnicodeDecodeError:
-                    value = bytes(value)
-            else:
-                if not isinstance(value, basestring_):
-                    value = coerce_text(value)
-                if (is_unicode(value)
-                    and self.default_encoding):
-                    value = value.encode(self.default_encoding)
-        except Exception as e:
-            e.args = (self._add_line_info(e.args[0], pos),)
-            raise
-        else:
-            if self._unicode and isinstance(value, bytes):
-                if not self.default_encoding:
-                    raise UnicodeDecodeError(
-                        'Cannot decode bytes value %r into unicode '
-                        '(no default_encoding provided)' % value)
-                try:
-                    value = value.decode(self.default_encoding)
-                except UnicodeDecodeError as e:
-                    raise UnicodeDecodeError(
-                        e.encoding,
-                        e.object,
-                        e.start,
-                        e.end,
-                        e.reason + ' in string %r' % value)
-            elif not self._unicode and is_unicode(value):
-                if not self.default_encoding:
-                    raise UnicodeEncodeError(
-                        'Cannot encode unicode value %r into bytes '
-                        '(no default_encoding provided)' % value)
-                value = value.encode(self.default_encoding)
-            return value
-
-    def _add_line_info(self, msg, pos):
-        msg = "%s at line %s column %s" % (
-            msg, pos[0], pos[1])
-        if self.name:
-            msg += " in file %s" % self.name
-        return msg
-
-
-def sub(content, delimeters=None, **kw):
-    name = kw.get('__name')
-    tmpl = Template(content, name=name, delimeters=delimeters)
-    return tmpl.substitute(kw)
-
-
-def paste_script_template_renderer(content, vars, filename=None):
-    tmpl = Template(content, name=filename)
-    return tmpl.substitute(vars)
-
-
-class bunch(dict):
-
-    def __init__(self, **kw):
-        for name, value in kw.items():
-            setattr(self, name, value)
-
-    def __setattr__(self, name, value):
-        self[name] = value
-
-    def __getattr__(self, name):
-        try:
-            return self[name]
-        except KeyError:
-            raise AttributeError(name)
-
-    def __getitem__(self, key):
-        if 'default' in self:
-            try:
-                return dict.__getitem__(self, key)
-            except KeyError:
-                return dict.__getitem__(self, 'default')
-        else:
-            return dict.__getitem__(self, key)
-
-    def __repr__(self):
-        return '<%s %s>' % (
-            self.__class__.__name__,
-            ' '.join(['%s=%r' % (k, v) for k, v in sorted(self.items())]))
-
-############################################################
-## HTML Templating
-############################################################
-
-
-class html(object):
-
-    def __init__(self, value):
-        self.value = value
-
-    def __str__(self):
-        return self.value
-
-    def __html__(self):
-        return self.value
-
-    def __repr__(self):
-        return '<%s %r>' % (
-            self.__class__.__name__, self.value)
-
-
-def html_quote(value, force=True):
-    if not force and hasattr(value, '__html__'):
-        return value.__html__()
-    if value is None:
-        return ''
-    if not isinstance(value, basestring_):
-        value = coerce_text(value)
-    if sys.version >= "3" and isinstance(value, bytes):
-        value = cgi.escape(value.decode('latin1'), 1)
-        value = value.encode('latin1')
-    else:
-        value = cgi.escape(value, 1)
-    if sys.version < "3":
-        if is_unicode(value):
-            value = value.encode('ascii', 'xmlcharrefreplace')
-    return value
-
-
-def url(v):
-    v = coerce_text(v)
-    if is_unicode(v):
-        v = v.encode('utf8')
-    return url_quote(v)
-
-
-def attr(**kw):
-    parts = []
-    for name, value in sorted(kw.items()):
-        if value is None:
-            continue
-        if name.endswith('_'):
-            name = name[:-1]
-        parts.append('%s="%s"' % (html_quote(name), html_quote(value)))
-    return html(' '.join(parts))
-
-
-class HTMLTemplate(Template):
-
-    default_namespace = Template.default_namespace.copy()
-    default_namespace.update(dict(
-        html=html,
-        attr=attr,
-        url=url,
-        html_quote=html_quote,
-        ))
-
-    def _repr(self, value, pos):
-        if hasattr(value, '__html__'):
-            value = value.__html__()
-            quote = False
-        else:
-            quote = True
-        plain = Template._repr(self, value, pos)
-        if quote:
-            return html_quote(plain)
-        else:
-            return plain
-
-
-def sub_html(content, **kw):
-    name = kw.get('__name')
-    tmpl = HTMLTemplate(content, name=name)
-    return tmpl.substitute(kw)
-
-
-class TemplateDef(object):
-    def __init__(self, template, func_name, func_signature,
-                 body, ns, pos, bound_self=None):
-        self._template = template
-        self._func_name = func_name
-        self._func_signature = func_signature
-        self._body = body
-        self._ns = ns
-        self._pos = pos
-        self._bound_self = bound_self
-
-    def __repr__(self):
-        return '<tempita function %s(%s) at %s:%s>' % (
-            self._func_name, self._func_signature,
-            self._template.name, self._pos)
-
-    def __str__(self):
-        return self()
-
-    def __call__(self, *args, **kw):
-        values = self._parse_signature(args, kw)
-        ns = self._ns.copy()
-        ns.update(values)
-        if self._bound_self is not None:
-            ns['self'] = self._bound_self
-        out = []
-        subdefs = {}
-        self._template._interpret_codes(self._body, ns, out, subdefs)
-        return ''.join(out)
-
-    def __get__(self, obj, type=None):
-        if obj is None:
-            return self
-        return self.__class__(
-            self._template, self._func_name, self._func_signature,
-            self._body, self._ns, self._pos, bound_self=obj)
-
-    def _parse_signature(self, args, kw):
-        values = {}
-        sig_args, var_args, var_kw, defaults = self._func_signature
-        extra_kw = {}
-        for name, value in kw.items():
-            if not var_kw and name not in sig_args:
-                raise TypeError(
-                    'Unexpected argument %s' % name)
-            if name in sig_args:
-                values[sig_args] = value
-            else:
-                extra_kw[name] = value
-        args = list(args)
-        sig_args = list(sig_args)
-        while args:
-            while sig_args and sig_args[0] in values:
-                sig_args.pop(0)
-            if sig_args:
-                name = sig_args.pop(0)
-                values[name] = args.pop(0)
-            elif var_args:
-                values[var_args] = tuple(args)
-                break
-            else:
-                raise TypeError(
-                    'Extra position arguments: %s'
-                    % ', '.join([repr(v) for v in args]))
-        for name, value_expr in defaults.items():
-            if name not in values:
-                values[name] = self._template._eval(
-                    value_expr, self._ns, self._pos)
-        for name in sig_args:
-            if name not in values:
-                raise TypeError(
-                    'Missing argument: %s' % name)
-        if var_kw:
-            values[var_kw] = extra_kw
-        return values
-
-
-class TemplateObject(object):
-
-    def __init__(self, name):
-        self.__name = name
-        self.get = TemplateObjectGetter(self)
-
-    def __repr__(self):
-        return '<%s %s>' % (self.__class__.__name__, self.__name)
-
-
-class TemplateObjectGetter(object):
-
-    def __init__(self, template_obj):
-        self.__template_obj = template_obj
-
-    def __getattr__(self, attr):
-        return getattr(self.__template_obj, attr, Empty)
-
-    def __repr__(self):
-        return '<%s around %r>' % (self.__class__.__name__, self.__template_obj)
-
-
-class _Empty(object):
-    def __call__(self, *args, **kw):
-        return self
-
-    def __str__(self):
-        return ''
-
-    def __repr__(self):
-        return 'Empty'
-
-    def __unicode__(self):
-        return u''
-
-    def __iter__(self):
-        return iter(())
-
-    def __bool__(self):
-        return False
-
-    if sys.version < "3":
-        __nonzero__ = __bool__
-
-Empty = _Empty()
-del _Empty
-
-############################################################
-## Lexing and Parsing
-############################################################
-
-
-def lex(s, name=None, trim_whitespace=True, line_offset=0, delimeters=None):
-    """
-    Lex a string into chunks:
-
-        >>> lex('hey')
-        ['hey']
-        >>> lex('hey {{you}}')
-        ['hey ', ('you', (1, 7))]
-        >>> lex('hey {{')
-        Traceback (most recent call last):
-            ...
-        TemplateError: No }} to finish last expression at line 1 column 7
-        >>> lex('hey }}')
-        Traceback (most recent call last):
-            ...
-        TemplateError: }} outside expression at line 1 column 7
-        >>> lex('hey {{ {{')
-        Traceback (most recent call last):
-            ...
-        TemplateError: {{ inside expression at line 1 column 10
-
-    """
-    if delimeters is None:
-        delimeters = ( Template.default_namespace['start_braces'],
-                       Template.default_namespace['end_braces'] )
-    in_expr = False
-    chunks = []
-    last = 0
-    last_pos = (line_offset + 1, 1)
-
-    token_re = re.compile(r'%s|%s' % (re.escape(delimeters[0]),
-                                      re.escape(delimeters[1])))
-    for match in token_re.finditer(s):
-        expr = match.group(0)
-        pos = find_position(s, match.end(), last, last_pos)
-        if expr == delimeters[0] and in_expr:
-            raise TemplateError('%s inside expression' % delimeters[0],
-                                position=pos,
-                                name=name)
-        elif expr == delimeters[1] and not in_expr:
-            raise TemplateError('%s outside expression' % delimeters[1],
-                                position=pos,
-                                name=name)
-        if expr == delimeters[0]:
-            part = s[last:match.start()]
-            if part:
-                chunks.append(part)
-            in_expr = True
-        else:
-            chunks.append((s[last:match.start()], last_pos))
-            in_expr = False
-        last = match.end()
-        last_pos = pos
-    if in_expr:
-        raise TemplateError('No %s to finish last expression' % delimeters[1],
-                            name=name, position=last_pos)
-    part = s[last:]
-    if part:
-        chunks.append(part)
-    if trim_whitespace:
-        chunks = trim_lex(chunks)
-    return chunks
-
-statement_re = re.compile(r'^(?:if |elif |for |def |inherit |default |py:)')
-single_statements = ['else', 'endif', 'endfor', 'enddef', 'continue', 'break']
-trail_whitespace_re = re.compile(r'\n\r?[\t ]*$')
-lead_whitespace_re = re.compile(r'^[\t ]*\n')
-
-
-def trim_lex(tokens):
-    r"""
-    Takes a lexed set of tokens, and removes whitespace when there is
-    a directive on a line by itself:
-
-       >>> tokens = lex('{{if x}}\nx\n{{endif}}\ny', trim_whitespace=False)
-       >>> tokens
-       [('if x', (1, 3)), '\nx\n', ('endif', (3, 3)), '\ny']
-       >>> trim_lex(tokens)
-       [('if x', (1, 3)), 'x\n', ('endif', (3, 3)), 'y']
-    """
-    last_trim = None
-    for i, current in enumerate(tokens):
-        if isinstance(current, basestring_):
-            # we don't trim this
-            continue
-        item = current[0]
-        if not statement_re.search(item) and item not in single_statements:
-            continue
-        if not i:
-            prev = ''
-        else:
-            prev = tokens[i - 1]
-        if i + 1 >= len(tokens):
-            next_chunk = ''
-        else:
-            next_chunk = tokens[i + 1]
-        if (not isinstance(next_chunk, basestring_)
-            or not isinstance(prev, basestring_)):
-            continue
-        prev_ok = not prev or trail_whitespace_re.search(prev)
-        if i == 1 and not prev.strip():
-            prev_ok = True
-        if last_trim is not None and last_trim + 2 == i and not prev.strip():
-            prev_ok = 'last'
-        if (prev_ok
-            and (not next_chunk or lead_whitespace_re.search(next_chunk)
-                 or (i == len(tokens) - 2 and not next_chunk.strip()))):
-            if prev:
-                if ((i == 1 and not prev.strip())
-                    or prev_ok == 'last'):
-                    tokens[i - 1] = ''
-                else:
-                    m = trail_whitespace_re.search(prev)
-                    # +1 to leave the leading \n on:
-                    prev = prev[:m.start() + 1]
-                    tokens[i - 1] = prev
-            if next_chunk:
-                last_trim = i
-                if i == len(tokens) - 2 and not next_chunk.strip():
-                    tokens[i + 1] = ''
-                else:
-                    m = lead_whitespace_re.search(next_chunk)
-                    next_chunk = next_chunk[m.end():]
-                    tokens[i + 1] = next_chunk
-    return tokens
-
-
-def find_position(string, index, last_index, last_pos):
-    """Given a string and index, return (line, column)"""
-    lines = string.count('\n', last_index, index)
-    if lines > 0:
-        column = index - string.rfind('\n', last_index, index)
-    else:
-        column = last_pos[1] + (index - last_index)
-    return (last_pos[0] + lines, column)
-
-
-def parse(s, name=None, line_offset=0, delimeters=None):
-    r"""
-    Parses a string into a kind of AST
-
-        >>> parse('{{x}}')
-        [('expr', (1, 3), 'x')]
-        >>> parse('foo')
-        ['foo']
-        >>> parse('{{if x}}test{{endif}}')
-        [('cond', (1, 3), ('if', (1, 3), 'x', ['test']))]
-        >>> parse('series->{{for x in y}}x={{x}}{{endfor}}')
-        ['series->', ('for', (1, 11), ('x',), 'y', ['x=', ('expr', (1, 27), 'x')])]
-        >>> parse('{{for x, y in z:}}{{continue}}{{endfor}}')
-        [('for', (1, 3), ('x', 'y'), 'z', [('continue', (1, 21))])]
-        >>> parse('{{py:x=1}}')
-        [('py', (1, 3), 'x=1')]
-        >>> parse('{{if x}}a{{elif y}}b{{else}}c{{endif}}')
-        [('cond', (1, 3), ('if', (1, 3), 'x', ['a']), ('elif', (1, 12), 'y', ['b']), ('else', (1, 23), None, ['c']))]
-
-    Some exceptions::
-
-        >>> parse('{{continue}}')
-        Traceback (most recent call last):
-            ...
-        TemplateError: continue outside of for loop at line 1 column 3
-        >>> parse('{{if x}}foo')
-        Traceback (most recent call last):
-            ...
-        TemplateError: No {{endif}} at line 1 column 3
-        >>> parse('{{else}}')
-        Traceback (most recent call last):
-            ...
-        TemplateError: else outside of an if block at line 1 column 3
-        >>> parse('{{if x}}{{for x in y}}{{endif}}{{endfor}}')
-        Traceback (most recent call last):
-            ...
-        TemplateError: Unexpected endif at line 1 column 25
-        >>> parse('{{if}}{{endif}}')
-        Traceback (most recent call last):
-            ...
-        TemplateError: if with no expression at line 1 column 3
-        >>> parse('{{for x y}}{{endfor}}')
-        Traceback (most recent call last):
-            ...
-        TemplateError: Bad for (no "in") in 'x y' at line 1 column 3
-        >>> parse('{{py:x=1\ny=2}}')
-        Traceback (most recent call last):
-            ...
-        TemplateError: Multi-line py blocks must start with a newline at line 1 column 3
-    """
-    if delimeters is None:
-        delimeters = ( Template.default_namespace['start_braces'],
-                       Template.default_namespace['end_braces'] )
-    tokens = lex(s, name=name, line_offset=line_offset, delimeters=delimeters)
-    result = []
-    while tokens:
-        next_chunk, tokens = parse_expr(tokens, name)
-        result.append(next_chunk)
-    return result
-
-
-def parse_expr(tokens, name, context=()):
-    if isinstance(tokens[0], basestring_):
-        return tokens[0], tokens[1:]
-    expr, pos = tokens[0]
-    expr = expr.strip()
-    if expr.startswith('py:'):
-        expr = expr[3:].lstrip(' \t')
-        if expr.startswith('\n') or expr.startswith('\r'):
-            expr = expr.lstrip('\r\n')
-            if '\r' in expr:
-                expr = expr.replace('\r\n', '\n')
-                expr = expr.replace('\r', '')
-            expr += '\n'
-        else:
-            if '\n' in expr:
-                raise TemplateError(
-                    'Multi-line py blocks must start with a newline',
-                    position=pos, name=name)
-        return ('py', pos, expr), tokens[1:]
-    elif expr in ('continue', 'break'):
-        if 'for' not in context:
-            raise TemplateError(
-                'continue outside of for loop',
-                position=pos, name=name)
-        return (expr, pos), tokens[1:]
-    elif expr.startswith('if '):
-        return parse_cond(tokens, name, context)
-    elif (expr.startswith('elif ')
-          or expr == 'else'):
-        raise TemplateError(
-            '%s outside of an if block' % expr.split()[0],
-            position=pos, name=name)
-    elif expr in ('if', 'elif', 'for'):
-        raise TemplateError(
-            '%s with no expression' % expr,
-            position=pos, name=name)
-    elif expr in ('endif', 'endfor', 'enddef'):
-        raise TemplateError(
-            'Unexpected %s' % expr,
-            position=pos, name=name)
-    elif expr.startswith('for '):
-        return parse_for(tokens, name, context)
-    elif expr.startswith('default '):
-        return parse_default(tokens, name, context)
-    elif expr.startswith('inherit '):
-        return parse_inherit(tokens, name, context)
-    elif expr.startswith('def '):
-        return parse_def(tokens, name, context)
-    elif expr.startswith('#'):
-        return ('comment', pos, tokens[0][0]), tokens[1:]
-    return ('expr', pos, tokens[0][0]), tokens[1:]
-
-
-def parse_cond(tokens, name, context):
-    start = tokens[0][1]
-    pieces = []
-    context = context + ('if',)
-    while 1:
-        if not tokens:
-            raise TemplateError(
-                'Missing {{endif}}',
-                position=start, name=name)
-        if (isinstance(tokens[0], tuple)
-            and tokens[0][0] == 'endif'):
-            return ('cond', start) + tuple(pieces), tokens[1:]
-        next_chunk, tokens = parse_one_cond(tokens, name, context)
-        pieces.append(next_chunk)
-
-
-def parse_one_cond(tokens, name, context):
-    (first, pos), tokens = tokens[0], tokens[1:]
-    content = []
-    if first.endswith(':'):
-        first = first[:-1]
-    if first.startswith('if '):
-        part = ('if', pos, first[3:].lstrip(), content)
-    elif first.startswith('elif '):
-        part = ('elif', pos, first[5:].lstrip(), content)
-    elif first == 'else':
-        part = ('else', pos, None, content)
-    else:
-        assert 0, "Unexpected token %r at %s" % (first, pos)
-    while 1:
-        if not tokens:
-            raise TemplateError(
-                'No {{endif}}',
-                position=pos, name=name)
-        if (isinstance(tokens[0], tuple)
-            and (tokens[0][0] == 'endif'
-                 or tokens[0][0].startswith('elif ')
-                 or tokens[0][0] == 'else')):
-            return part, tokens
-        next_chunk, tokens = parse_expr(tokens, name, context)
-        content.append(next_chunk)
-
-
-def parse_for(tokens, name, context):
-    first, pos = tokens[0]
-    tokens = tokens[1:]
-    context = ('for',) + context
-    content = []
-    assert first.startswith('for ')
-    if first.endswith(':'):
-        first = first[:-1]
-    first = first[3:].strip()
-    match = in_re.search(first)
-    if not match:
-        raise TemplateError(
-            'Bad for (no "in") in %r' % first,
-            position=pos, name=name)
-    vars = first[:match.start()]
-    if '(' in vars:
-        raise TemplateError(
-            'You cannot have () in the variable section of a for loop (%r)'
-            % vars, position=pos, name=name)
-    vars = tuple([
-        v.strip() for v in first[:match.start()].split(',')
-        if v.strip()])
-    expr = first[match.end():]
-    while 1:
-        if not tokens:
-            raise TemplateError(
-                'No {{endfor}}',
-                position=pos, name=name)
-        if (isinstance(tokens[0], tuple)
-            and tokens[0][0] == 'endfor'):
-            return ('for', pos, vars, expr, content), tokens[1:]
-        next_chunk, tokens = parse_expr(tokens, name, context)
-        content.append(next_chunk)
-
-
-def parse_default(tokens, name, context):
-    first, pos = tokens[0]
-    assert first.startswith('default ')
-    first = first.split(None, 1)[1]
-    parts = first.split('=', 1)
-    if len(parts) == 1:
-        raise TemplateError(
-            "Expression must be {{default var=value}}; no = found in %r" % first,
-            position=pos, name=name)
-    var = parts[0].strip()
-    if ',' in var:
-        raise TemplateError(
-            "{{default x, y = ...}} is not supported",
-            position=pos, name=name)
-    if not var_re.search(var):
-        raise TemplateError(
-            "Not a valid variable name for {{default}}: %r"
-            % var, position=pos, name=name)
-    expr = parts[1].strip()
-    return ('default', pos, var, expr), tokens[1:]
-
-
-def parse_inherit(tokens, name, context):
-    first, pos = tokens[0]
-    assert first.startswith('inherit ')
-    expr = first.split(None, 1)[1]
-    return ('inherit', pos, expr), tokens[1:]
-
-
-def parse_def(tokens, name, context):
-    first, start = tokens[0]
-    tokens = tokens[1:]
-    assert first.startswith('def ')
-    first = first.split(None, 1)[1]
-    if first.endswith(':'):
-        first = first[:-1]
-    if '(' not in first:
-        func_name = first
-        sig = ((), None, None, {})
-    elif not first.endswith(')'):
-        raise TemplateError("Function definition doesn't end with ): %s" % first,
-                            position=start, name=name)
-    else:
-        first = first[:-1]
-        func_name, sig_text = first.split('(', 1)
-        sig = parse_signature(sig_text, name, start)
-    context = context + ('def',)
-    content = []
-    while 1:
-        if not tokens:
-            raise TemplateError(
-                'Missing {{enddef}}',
-                position=start, name=name)
-        if (isinstance(tokens[0], tuple)
-            and tokens[0][0] == 'enddef'):
-            return ('def', start, func_name, sig, content), tokens[1:]
-        next_chunk, tokens = parse_expr(tokens, name, context)
-        content.append(next_chunk)
-
-
-def parse_signature(sig_text, name, pos):
-    tokens = tokenize.generate_tokens(StringIO(sig_text).readline)
-    sig_args = []
-    var_arg = None
-    var_kw = None
-    defaults = {}
-
-    def get_token(pos=False):
-        try:
-            tok_type, tok_string, (srow, scol), (erow, ecol), line = next(tokens)
-        except StopIteration:
-            return tokenize.ENDMARKER, ''
-        if pos:
-            return tok_type, tok_string, (srow, scol), (erow, ecol)
-        else:
-            return tok_type, tok_string
-    while 1:
-        var_arg_type = None
-        tok_type, tok_string = get_token()
-        if tok_type == tokenize.ENDMARKER:
-            break
-        if tok_type == tokenize.OP and (tok_string == '*' or tok_string == '**'):
-            var_arg_type = tok_string
-            tok_type, tok_string = get_token()
-        if tok_type != tokenize.NAME:
-            raise TemplateError('Invalid signature: (%s)' % sig_text,
-                                position=pos, name=name)
-        var_name = tok_string
-        tok_type, tok_string = get_token()
-        if tok_type == tokenize.ENDMARKER or (tok_type == tokenize.OP and tok_string == ','):
-            if var_arg_type == '*':
-                var_arg = var_name
-            elif var_arg_type == '**':
-                var_kw = var_name
-            else:
-                sig_args.append(var_name)
-            if tok_type == tokenize.ENDMARKER:
-                break
-            continue
-        if var_arg_type is not None:
-            raise TemplateError('Invalid signature: (%s)' % sig_text,
-                                position=pos, name=name)
-        if tok_type == tokenize.OP and tok_string == '=':
-            nest_type = None
-            unnest_type = None
-            nest_count = 0
-            start_pos = end_pos = None
-            parts = []
-            while 1:
-                tok_type, tok_string, s, e = get_token(True)
-                if start_pos is None:
-                    start_pos = s
-                end_pos = e
-                if tok_type == tokenize.ENDMARKER and nest_count:
-                    raise TemplateError('Invalid signature: (%s)' % sig_text,
-                                        position=pos, name=name)
-                if (not nest_count and
-                    (tok_type == tokenize.ENDMARKER or (tok_type == tokenize.OP and tok_string == ','))):
-                    default_expr = isolate_expression(sig_text, start_pos, end_pos)
-                    defaults[var_name] = default_expr
-                    sig_args.append(var_name)
-                    break
-                parts.append((tok_type, tok_string))
-                if nest_count and tok_type == tokenize.OP and tok_string == nest_type:
-                    nest_count += 1
-                elif nest_count and tok_type == tokenize.OP and tok_string == unnest_type:
-                    nest_count -= 1
-                    if not nest_count:
-                        nest_type = unnest_type = None
-                elif not nest_count and tok_type == tokenize.OP and tok_string in ('(', '[', '{'):
-                    nest_type = tok_string
-                    nest_count = 1
-                    unnest_type = {'(': ')', '[': ']', '{': '}'}[nest_type]
-    return sig_args, var_arg, var_kw, defaults
-
-
-def isolate_expression(string, start_pos, end_pos):
-    srow, scol = start_pos
-    srow -= 1
-    erow, ecol = end_pos
-    erow -= 1
-    lines = string.splitlines(True)
-    if srow == erow:
-        return lines[srow][scol:ecol]
-    parts = [lines[srow][scol:]]
-    parts.extend(lines[srow+1:erow])
-    if erow < len(lines):
-        # It'll sometimes give (end_row_past_finish, 0)
-        parts.append(lines[erow][:ecol])
-    return ''.join(parts)
-
-_fill_command_usage = """\
-%prog [OPTIONS] TEMPLATE arg=value
-
-Use py:arg=value to set a Python value; otherwise all values are
-strings.
-"""
-
-
-def fill_command(args=None):
-    import sys
-    import optparse
-    import pkg_resources
-    import os
-    if args is None:
-        args = sys.argv[1:]
-    dist = pkg_resources.get_distribution('Paste')
-    parser = optparse.OptionParser(
-        version=coerce_text(dist),
-        usage=_fill_command_usage)
-    parser.add_option(
-        '-o', '--output',
-        dest='output',
-        metavar="FILENAME",
-        help="File to write output to (default stdout)")
-    parser.add_option(
-        '--html',
-        dest='use_html',
-        action='store_true',
-        help="Use HTML style filling (including automatic HTML quoting)")
-    parser.add_option(
-        '--env',
-        dest='use_env',
-        action='store_true',
-        help="Put the environment in as top-level variables")
-    options, args = parser.parse_args(args)
-    if len(args) < 1:
-        print('You must give a template filename')
-        sys.exit(2)
-    template_name = args[0]
-    args = args[1:]
-    vars = {}
-    if options.use_env:
-        vars.update(os.environ)
-    for value in args:
-        if '=' not in value:
-            print('Bad argument: %r' % value)
-            sys.exit(2)
-        name, value = value.split('=', 1)
-        if name.startswith('py:'):
-            name = name[:3]
-            value = eval(value)
-        vars[name] = value
-    if template_name == '-':
-        template_content = sys.stdin.read()
-        template_name = '<stdin>'
-    else:
-        f = open(template_name, 'rb')
-        template_content = f.read()
-        f.close()
-    if options.use_html:
-        TemplateClass = HTMLTemplate
-    else:
-        TemplateClass = Template
-    template = TemplateClass(template_content, name=template_name)
-    result = template.substitute(vars)
-    if options.output:
-        f = open(options.output, 'wb')
-        f.write(result)
-        f.close()
-    else:
-        sys.stdout.write(result)
-
-if __name__ == '__main__':
-    fill_command()
diff --git a/mne/externals/tempita/compat3.py b/mne/externals/tempita/compat3.py
index 9905530..d49412b 100644
--- a/mne/externals/tempita/compat3.py
+++ b/mne/externals/tempita/compat3.py
@@ -1,11 +1,12 @@
 import sys
 
-__all__ = ['b', 'basestring_', 'bytes', 'unicode_', 'next', 'is_unicode']
+__all__ = ['PY3', 'b', 'basestring_', 'bytes', 'next', 'is_unicode']
 
-if sys.version < "3":
+PY3 = True if sys.version_info[0] == 3 else False
+
+if sys.version_info[0] < 3:
     b = bytes = str
     basestring_ = basestring
-    unicode_ = unicode
 else:
 
     def b(s):
@@ -14,29 +15,26 @@ else:
         return bytes(s)
     basestring_ = (bytes, str)
     bytes = bytes
-    unicode_ = str
 text = str
 
-if sys.version < "3":
+if sys.version_info[0] < 3:
 
     def next(obj):
         return obj.next()
 else:
     next = next
 
-if sys.version < "3":
 
-    def is_unicode(obj):
+def is_unicode(obj):
+    if sys.version_info[0] < 3:
         return isinstance(obj, unicode)
-else:
-
-    def is_unicode(obj):
+    else:
         return isinstance(obj, str)
 
 
 def coerce_text(v):
     if not isinstance(v, basestring_):
-        if sys.version < "3":
+        if sys.version_info[0] < 3:
             attr = '__unicode__'
         else:
             attr = '__str__'
diff --git a/mne/filter.py b/mne/filter.py
index cbd0717..febae65 100644
--- a/mne/filter.py
+++ b/mne/filter.py
@@ -1,4 +1,4 @@
-"""IIR and FIR filtering functions"""
+"""IIR and FIR filtering and resampling functions."""
 
 from copy import deepcopy
 from functools import partial
@@ -9,18 +9,18 @@ from scipy.fftpack import fft, ifftshift, fftfreq, ifft
 from .cuda import (setup_cuda_fft_multiply_repeated, fft_multiply_repeated,
                    setup_cuda_fft_resample, fft_resample, _smart_pad)
 from .externals.six import string_types, integer_types
-from .fixes import get_sosfiltfilt
+from .fixes import get_sosfiltfilt, minimum_phase
 from .parallel import parallel_func, check_n_jobs
 from .time_frequency.multitaper import dpss_windows, _mt_spectra
-from .utils import logger, verbose, sum_squared, check_version, warn
+from .utils import (logger, verbose, sum_squared, check_version, warn,
+                    _check_preload)
 
-
-# These values are *double* what is given in Ifeachor and Jervis.
-_length_factors = dict(hann=6.2, hamming=6.6, blackman=11.0)
+# These values from Ifeachor and Jervis.
+_length_factors = dict(hann=3.1, hamming=3.3, blackman=5.0)
 
 
 def is_power2(num):
-    """Test if number is a power of 2
+    """Test if number is a power of 2.
 
     Parameters
     ----------
@@ -102,7 +102,7 @@ def next_fast_len(target):
             # (quotient = ceil(target / p35))
             quotient = -(-target // p35)
 
-            p2 = 2 ** (quotient - 1).bit_length()
+            p2 = 2 ** int(quotient - 1).bit_length()
 
             N = p2 * p35
             if N == target:
@@ -123,11 +123,9 @@ def next_fast_len(target):
 
 
 def _overlap_add_filter(x, h, n_fft=None, phase='zero', picks=None,
-                        n_jobs=1):
+                        n_jobs=1, copy=True, pad='reflect_limited'):
     """Filter the signal x using h with overlap-add FFTs.
 
-    .. warning:: This operates on the data in-place.
-
     Parameters
     ----------
     x : array, shape (n_signals, n_times)
@@ -141,7 +139,8 @@ def _overlap_add_filter(x, h, n_fft=None, phase='zero', picks=None,
         If 'zero', the delay for the filter is compensated (and it must be
         an odd-length symmetric filter). If 'linear', the response is
         uncompensated. If 'zero-double', the filter is applied in the
-        forward and reverse directions.
+        forward and reverse directions. If 'minimum', a minimum-phase
+        filter will be used.
     picks : array-like of int | None
         Indices of channels to filter. If None all channels will be
         filtered. Only supported for 2D (n_channels, n_times) and 3D
@@ -149,12 +148,20 @@ def _overlap_add_filter(x, h, n_fft=None, phase='zero', picks=None,
     n_jobs : int | str
         Number of jobs to run in parallel. Can be 'cuda' if scikits.cuda
         is installed properly and CUDA is initialized.
+    copy : bool
+        If True, a copy of x, filtered, is returned. Otherwise, it operates
+        on x in place.
+    pad : str
+        Padding type for ``_smart_pad``.
 
     Returns
     -------
     xf : array, shape (n_signals, n_times)
         x filtered.
     """
+    n_jobs = check_n_jobs(n_jobs, allow_cuda=True)
+    # set up array for filtering, reshape to 2D, operate on last axis
+    x, orig_shape, picks = _prep_for_filtering(x, copy, picks)
     # Extend the signal by mirroring the edges to reduce transient filter
     # response
     _check_zero_phase_length(len(h), phase)
@@ -202,25 +209,26 @@ def _overlap_add_filter(x, h, n_fft=None, phase='zero', picks=None,
     if n_jobs == 1:
         for p in picks:
             x[p] = _1d_overlap_filter(x[p], h_fft, len(h), n_edge, phase,
-                                      cuda_dict)
+                                      cuda_dict, pad)
     else:
         parallel, p_fun, _ = parallel_func(_1d_overlap_filter, n_jobs)
         data_new = parallel(p_fun(x[p], h_fft, len(h), n_edge, phase,
-                                  cuda_dict) for p in picks)
+                                  cuda_dict, pad) for p in picks)
         for pp, p in enumerate(picks):
             x[p] = data_new[pp]
 
+    x.shape = orig_shape
     return x
 
 
-def _1d_overlap_filter(x, h_fft, n_h, n_edge, phase, cuda_dict):
-    """Do one-dimensional overlap-add FFT FIR filtering"""
+def _1d_overlap_filter(x, h_fft, n_h, n_edge, phase, cuda_dict, pad):
+    """Do one-dimensional overlap-add FFT FIR filtering."""
     # pad to reduce ringing
     if cuda_dict['use_cuda']:
         n_fft = cuda_dict['x'].size  # account for CUDA's modification of h_fft
     else:
         n_fft = len(h_fft)
-    x_ext = _smart_pad(x, np.array([n_edge, n_edge]))
+    x_ext = _smart_pad(x, (n_edge, n_edge), pad)
     n_x = len(x_ext)
     x_filtered = np.zeros_like(x_ext)
 
@@ -250,19 +258,19 @@ def _1d_overlap_filter(x, h_fft, n_h, n_edge, phase, cuda_dict):
 
 
 def _filter_attenuation(h, freq, gain):
-    """Compute minimum attenuation at stop frequency"""
+    """Compute minimum attenuation at stop frequency."""
     from scipy.signal import freqz
     _, filt_resp = freqz(h.ravel(), worN=np.pi * freq)
     filt_resp = np.abs(filt_resp)  # use amplitude response
     filt_resp[np.where(gain == 1)] = 0
     idx = np.argmax(filt_resp)
-    att_db = -20 * np.log10(filt_resp[idx])
+    att_db = -20 * np.log10(np.maximum(filt_resp[idx], 1e-20))
     att_freq = freq[idx]
     return att_db, att_freq
 
 
 def _prep_for_filtering(x, copy, picks=None):
-    """Set up array as 2D for filtering ease"""
+    """Set up array as 2D for filtering ease."""
     if x.dtype != np.float64:
         raise TypeError("Arrays passed for filtering must have a dtype of "
                         "np.float64")
@@ -289,8 +297,45 @@ def _prep_for_filtering(x, copy, picks=None):
     return x, orig_shape, picks
 
 
-def _fir_filter(x, Fs, freq, gain, filter_length, picks=None, n_jobs=1,
-                copy=True, phase='zero', fir_window='hamming'):
+def _firwin_design(N, freq, gain, window, sfreq):
+    """Construct a FIR filter using firwin."""
+    from scipy.signal import firwin
+    assert freq[0] == 0
+    assert len(freq) > 1
+    assert len(freq) == len(gain)
+    h = np.zeros(N)
+    prev_freq = freq[-1]
+    prev_gain = gain[-1]
+    if gain[-1] == 1:
+        h[N // 2] = 1  # start with "all up"
+    assert prev_gain in (0, 1)
+    for this_freq, this_gain in zip(freq[::-1][1:], gain[::-1][1:]):
+        assert this_gain in (0, 1)
+        if this_gain != prev_gain:
+            # Get the correct N to satistify the requested transition bandwidth
+            transition = (prev_freq - this_freq) / 2.
+            this_N = int(round(_length_factors[window] / transition))
+            this_N += (1 - this_N % 2)  # make it odd
+            if this_N > N:
+                raise ValueError('The requested filter length %s is too short '
+                                 'for the requested %0.2f Hz transition band, '
+                                 'which requires %s samples'
+                                 % (N, transition * sfreq / 2., this_N))
+            # Construct a lowpass
+            this_h = firwin(this_N, (prev_freq + this_freq) / 2.,
+                            window=window, pass_zero=True, nyq=freq[-1])
+            offset = (N - this_N) // 2
+            if this_gain == 0:
+                h[offset:N - offset] -= this_h
+            else:
+                h[offset:N - offset] += this_h
+        prev_gain = this_gain
+        prev_freq = this_freq
+    return h
+
+
+def _construct_fir_filter(sfreq, freq, gain, filter_length, phase, fir_window,
+                          fir_design):
     """Filter signal using gain control points in the frequency domain.
 
     The filter impulse response is constructed from a Hann window (window
@@ -301,70 +346,67 @@ def _fir_filter(x, Fs, freq, gain, filter_length, picks=None, n_jobs=1,
 
     Parameters
     ----------
-    x : array
-        Signal to filter.
     Fs : float
         Sampling rate in Hz.
     freq : 1d array
         Frequency sampling points in Hz.
     gain : 1d array
         Filter gain at frequency sampling points.
+        Must be all 0 and 1 for fir_design=="firwin".
     filter_length : int
         Length of the filter to use. Must be odd length if phase == "zero".
-    picks : array-like of int | None
-        Indices of channels to filter. If None all channels will be
-        filtered. Only supported for 2D (n_channels, n_times) and 3D
-        (n_epochs, n_channels, n_times) data.
-    n_jobs : int | str
-        Number of jobs to run in parallel. Can be 'cuda' if scikits.cuda
-        is installed properly and CUDA is initialized.
-    copy : bool
-        If True, a copy of x, filtered, is returned. Otherwise, it operates
-        on x in place.
     phase : str
         If 'zero', the delay for the filter is compensated (and it must be
         an odd-length symmetric filter). If 'linear', the response is
         uncompensated. If 'zero-double', the filter is applied in the
-        forward and reverse directions.
+        forward and reverse directions. If 'minimum', a minimum-phase
+        filter will be used.
     fir_window : str
         The window to use in FIR design, can be "hamming" (default),
         "hann", or "blackman".
+    fir_design : str
+        Can be "firwin2" or "firwin".
 
     Returns
     -------
     xf : array
         x filtered.
     """
-    from scipy.signal import firwin2
-    # set up array for filtering, reshape to 2D, operate on last axis
-    x, orig_shape, picks = _prep_for_filtering(x, copy, picks)
+    assert freq[0] == 0
+    if fir_design == 'firwin2':
+        from scipy.signal import firwin2 as fir_design
+    else:
+        assert fir_design == 'firwin'
+        fir_design = partial(_firwin_design, sfreq=sfreq)
 
     # issue a warning if attenuation is less than this
-    min_att_db = 20
+    min_att_db = 12 if phase == 'minimum' else 20
 
     # normalize frequencies
-    freq = np.array(freq) / (Fs / 2.)
+    freq = np.array(freq) / (sfreq / 2.)
     if freq[0] != 0 or freq[-1] != 1:
         raise ValueError('freq must start at 0 and end an Nyquist (%s), got %s'
-                         % (Fs / 2., freq))
+                         % (sfreq / 2., freq))
     gain = np.array(gain)
-    n_jobs = check_n_jobs(n_jobs, allow_cuda=True)
 
     # Use overlap-add filter with a fixed length
     N = _check_zero_phase_length(filter_length, phase, gain[-1])
     # construct symmetric (linear phase) filter
-    h = firwin2(N, freq, gain, window=fir_window)
+    if phase == 'minimum':
+        h = fir_design(N * 2 - 1, freq, gain, window=fir_window)
+        h = minimum_phase(h)
+    else:
+        h = fir_design(N, freq, gain, window=fir_window)
+    assert h.size == N
     att_db, att_freq = _filter_attenuation(h, freq, gain)
     if phase == 'zero-double':
         att_db += 6
     if att_db < min_att_db:
-        att_freq *= Fs / 2.
+        att_freq *= sfreq / 2.
         warn('Attenuation at stop frequency %0.1fHz is only %0.1fdB. '
              'Increase filter_length for higher attenuation.'
              % (att_freq, att_db))
-    x = _overlap_add_filter(x, h, phase=phase, picks=picks, n_jobs=n_jobs)
-    x.shape = orig_shape
-    return x
+    return h
 
 
 def _check_zero_phase_length(N, phase, gain_nyq=0):
@@ -379,7 +421,7 @@ def _check_zero_phase_length(N, phase, gain_nyq=0):
 
 
 def _check_coefficients(system):
-    """Check for filter stability"""
+    """Check for filter stability."""
     if isinstance(system, tuple):
         from scipy.signal import tf2zpk
         z, p, k = tf2zpk(*system)
@@ -393,7 +435,7 @@ def _check_coefficients(system):
 
 
 def _filtfilt(x, iir_params, picks, n_jobs, copy):
-    """Helper to more easily call filtfilt"""
+    """Call filtfilt."""
     # set up array for filtering, reshape to 2D, operate on last axis
     from scipy.signal import filtfilt
     padlen = min(iir_params['padlen'], len(x))
@@ -420,7 +462,7 @@ def _filtfilt(x, iir_params, picks, n_jobs, copy):
 
 
 def estimate_ringing_samples(system, max_try=100000):
-    """Estimate filter ringing
+    """Estimate filter ringing.
 
     Parameters
     ----------
@@ -472,7 +514,7 @@ def estimate_ringing_samples(system, max_try=100000):
 
 def construct_iir_filter(iir_params, f_pass=None, f_stop=None, sfreq=None,
                          btype=None, return_copy=True):
-    """Use IIR parameters to get filtering coefficients
+    """Use IIR parameters to get filtering coefficients.
 
     This function works like a wrapper for iirdesign and iirfilter in
     scipy.signal to make filter coefficients for IIR filtering. It also
@@ -585,7 +627,7 @@ def construct_iir_filter(iir_params, f_pass=None, f_stop=None, sfreq=None,
 
     For more information, see the tutorials :ref:`tut_background_filtering`
     and :ref:`tut_artifacts_filter`.
-    """  # noqa
+    """  # noqa: E501
     from scipy.signal import iirfilter, iirdesign
     known_filters = ('bessel', 'butter', 'butterworth', 'cauer', 'cheby1',
                      'cheby2', 'chebyshev1', 'chebyshev2', 'chebyshevi',
@@ -655,9 +697,9 @@ def construct_iir_filter(iir_params, f_pass=None, f_stop=None, sfreq=None,
     return iir_params
 
 
-def _check_method(method, iir_params, extra_types):
-    """Helper to parse method arguments"""
-    allowed_types = ['iir', 'fir', 'fft'] + extra_types
+def _check_method(method, iir_params, extra_types=()):
+    """Parse method arguments."""
+    allowed_types = ['iir', 'fir', 'fft'] + list(extra_types)
     if not isinstance(method, string_types):
         raise TypeError('method must be a string')
     if method not in allowed_types:
@@ -670,9 +712,8 @@ def _check_method(method, iir_params, extra_types):
             iir_params = dict()
         if len(iir_params) == 0 or (len(iir_params) == 1 and
                                     'output' in iir_params):
-            # XXX update this after deprecation of ba
             iir_params = dict(order=4, ftype='butter',
-                              output=iir_params.get('output', 'ba'))
+                              output=iir_params.get('output', 'sos'))
     elif iir_params is not None:
         raise ValueError('iir_params must be None if method != "iir"')
     return iir_params, method
@@ -682,7 +723,8 @@ def _check_method(method, iir_params, extra_types):
 def filter_data(data, sfreq, l_freq, h_freq, picks=None, filter_length='auto',
                 l_trans_bandwidth='auto', h_trans_bandwidth='auto', n_jobs=1,
                 method='fir', iir_params=None, copy=True, phase='zero',
-                fir_window='hamming', verbose=None):
+                fir_window='hamming', fir_design=None, pad='reflect_limited',
+                verbose=None):
     """Filter a subset of channels.
 
     Applies a zero-phase low-pass, high-pass, band-pass, or band-stop
@@ -719,15 +761,17 @@ def filter_data(data, sfreq, l_freq, h_freq, picks=None, filter_length='auto',
     filter_length : str | int
         Length of the FIR filter to use (if applicable):
 
-            * int: specified length in samples.
-            * 'auto' (default in 0.14): the filter length is chosen based
-              on the size of the transition regions (6.6 times the reciprocal
-              of the shortest transition band for fir_window='hamming').
-            * str: (default in 0.13 is "10s") a human-readable time in
-              units of "s" or "ms" (e.g., "10s" or "5500ms") will be
-              converted to that number of samples if ``phase="zero"``, or
-              the shortest power-of-two length at least that duration for
-              ``phase="zero-double"``.
+        * 'auto' (default): the filter length is chosen based
+          on the size of the transition regions (6.6 times the reciprocal
+          of the shortest transition band for fir_window='hamming'
+          and fir_design="firwin2", and half that for "firwin").
+        * str: a human-readable time in
+          units of "s" or "ms" (e.g., "10s" or "5500ms") will be
+          converted to that number of samples if ``phase="zero"``, or
+          the shortest power-of-two length at least that duration for
+          ``phase="zero-double"``.
+        * int: specified length in samples. For fir_design="firwin",
+          this should not be used.
 
     l_trans_bandwidth : float | str
         Width of the transition band at the low cut-off frequency in Hz
@@ -763,19 +807,35 @@ def filter_data(data, sfreq, l_freq, h_freq, picks=None, filter_length='auto',
         By default, a symmetric linear-phase FIR filter is constructed.
         If ``phase='zero'`` (default), the delay of this filter
         is compensated for. If ``phase=='zero-double'``, then this filter
-        is applied twice, once forward, and once backward.
+        is applied twice, once forward, and once backward. If 'minimum',
+        then a minimum-phase, causal filter will be used.
 
         .. versionadded:: 0.13
-
     fir_window : str
         The window to use in FIR design, can be "hamming" (default),
         "hann" (default in 0.13), or "blackman".
 
         .. versionadded:: 0.13
+    fir_design : str
+        Can be "firwin" (default in 0.16) to use
+        :func:`scipy.signal.firwin`, or "firwin2" (default in 0.15 and
+        before) to use :func:`scipy.signal.firwin2`. "firwin" uses a
+        time-domain design technique that generally gives improved
+        attenuation using fewer samples than "firwin2".
+
+        ..versionadded:: 0.15
+    pad : str
+        The type of padding to use. Supports all :func:`numpy.pad` ``mode``
+        options. Can also be "reflect_limited" (default), which pads with a
+        reflected version of each vector mirrored on the first and last
+        values of the vector, followed by zeros.
+        Only used for ``method='fir'``.
 
+        .. versionadded:: 0.15
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
-        Defaults to self.verbose.
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more). Defaults to
+        self.verbose.
 
     Returns
     -------
@@ -784,111 +844,85 @@ def filter_data(data, sfreq, l_freq, h_freq, picks=None, filter_length='auto',
 
     See Also
     --------
-    mne.filter.construct_iir_filter
+    construct_iir_filter
+    create_filter
     mne.io.Raw.filter
-    band_pass_filter
-    band_stop_filter
-    high_pass_filter
-    low_pass_filter
     notch_filter
     resample
 
     Notes
     -----
     For more information, see the tutorials :ref:`tut_background_filtering`
-    and :ref:`tut_artifacts_filter`.
+    and :ref:`tut_artifacts_filter`, and :func:`mne.filter.create_filter`.
     """
     if not isinstance(data, np.ndarray):
         raise ValueError('data must be an array')
-    sfreq = float(sfreq)
-    if sfreq < 0:
-        raise ValueError('sfreq must be positive')
-    if h_freq is not None:
-        h_freq = float(h_freq)
-        if h_freq > (sfreq / 2.):
-            raise ValueError('h_freq (%s) must be less than the Nyquist '
-                             'frequency %s' % (h_freq, sfreq / 2.))
-    if l_freq is not None:
-        l_freq = float(l_freq)
-        if l_freq == 0:
-            l_freq = None
-    if l_freq is None and h_freq is not None:
-        data = low_pass_filter(
-            data, sfreq, h_freq, filter_length=filter_length,
-            trans_bandwidth=h_trans_bandwidth, method=method,
-            iir_params=iir_params, picks=picks, n_jobs=n_jobs,
-            copy=copy, phase=phase, fir_window=fir_window)
-    if l_freq is not None and h_freq is None:
-        data = high_pass_filter(
-            data, sfreq, l_freq, filter_length=filter_length,
-            trans_bandwidth=l_trans_bandwidth, method=method,
-            iir_params=iir_params, picks=picks, n_jobs=n_jobs, copy=copy,
-            phase=phase, fir_window=fir_window)
-    if l_freq is not None and h_freq is not None:
-        if l_freq < h_freq:
-            data = band_pass_filter(
-                data, sfreq, l_freq, h_freq,
-                filter_length=filter_length,
-                l_trans_bandwidth=l_trans_bandwidth,
-                h_trans_bandwidth=h_trans_bandwidth,
-                method=method, iir_params=iir_params, picks=picks,
-                n_jobs=n_jobs, copy=copy, phase=phase, fir_window=fir_window)
-        else:
-            logger.info('Band-stop filtering from %0.2g - %0.2g Hz'
-                        % (h_freq, l_freq))
-            data = band_stop_filter(
-                data, sfreq, h_freq, l_freq,
-                filter_length=filter_length,
-                l_trans_bandwidth=h_trans_bandwidth,
-                h_trans_bandwidth=l_trans_bandwidth, method=method,
-                iir_params=iir_params, picks=picks, n_jobs=n_jobs,
-                copy=copy, phase=phase, fir_window=fir_window)
+    iir_params, method = _check_method(method, iir_params)
+    filt = create_filter(
+        data, sfreq, l_freq, h_freq, filter_length, l_trans_bandwidth,
+        h_trans_bandwidth, method, iir_params, phase, fir_window, fir_design)
+    if method in ('fir', 'fft'):
+        data = _overlap_add_filter(data, filt, None, phase, picks, n_jobs,
+                                   copy, pad)
+    else:
+        data = _filtfilt(data, filt, picks, n_jobs, copy)
     return data
 
 
 @verbose
-def band_pass_filter(x, Fs, Fp1, Fp2, filter_length='',
-                     l_trans_bandwidth=None, h_trans_bandwidth=None,
-                     method='fir', iir_params=None, picks=None, n_jobs=1,
-                     copy=True, phase='', fir_window='', verbose=None):
-    """Bandpass filter for the signal x.
+def create_filter(data, sfreq, l_freq, h_freq, filter_length='auto',
+                  l_trans_bandwidth='auto', h_trans_bandwidth='auto',
+                  method='fir', iir_params=None, phase='zero',
+                  fir_window='hamming', fir_design=None, verbose=None):
+    r"""Create a FIR or IIR filter.
 
-    Applies a zero-phase bandpass filter to the signal x, operating on the
-    last dimension.
+    ``l_freq`` and ``h_freq`` are the frequencies below which and above
+    which, respectively, to filter out of the data. Thus the uses are:
+
+        * ``l_freq < h_freq``: band-pass filter
+        * ``l_freq > h_freq``: band-stop filter
+        * ``l_freq is not None and h_freq is None``: high-pass filter
+        * ``l_freq is None and h_freq is not None``: low-pass filter
 
     Parameters
     ----------
-    x : array
-        Signal to filter.
-    Fs : float
-        Sampling rate in Hz.
-    Fp1 : float
-        Low cut-off frequency in Hz.
-    Fp2 : float
-        High cut-off frequency in Hz.
+    data : ndarray, shape (..., n_times)
+        The data that will be filtered. This is used for sanity checking
+        only.
+    sfreq : float
+        The sample frequency in Hz.
+    l_freq : float | None
+        Low cut-off frequency in Hz. If None the data are only low-passed.
+    h_freq : float | None
+        High cut-off frequency in Hz. If None the data are only
+        high-passed.
     filter_length : str | int
         Length of the FIR filter to use (if applicable):
 
-            * int: specified length in samples.
-            * 'auto' (default in 0.14): the filter length is chosen based
-              on the size of the transition regions (6.6 times the reciprocal
-              of the shortest transition band for fir_window='hamming').
-            * str: (default in 0.13 is "10s") a human-readable time in
-              units of "s" or "ms" (e.g., "10s" or "5500ms") will be
-              converted to that number of samples if ``phase="zero"``, or
-              the shortest power-of-two length at least that duration for
-              ``phase="zero-double"``.
+        * 'auto' (default): the filter length is chosen based
+          on the size of the transition regions (6.6 times the reciprocal
+          of the shortest transition band for fir_window='hamming'
+          and fir_design="firwin2", and half that for "firwin").
+        * str: a human-readable time in
+          units of "s" or "ms" (e.g., "10s" or "5500ms") will be
+          converted to that number of samples if ``phase="zero"``, or
+          the shortest power-of-two length at least that duration for
+          ``phase="zero-double"``.
+        * int: specified length in samples. For fir_design="firwin",
+          this should not be used.
 
     l_trans_bandwidth : float | str
         Width of the transition band at the low cut-off frequency in Hz
-        Can be "auto" (default in 0.14) to use a multiple of ``l_freq``::
+        (high pass or cutoff 1 in bandpass). Can be "auto"
+        (default) to use a multiple of ``l_freq``::
 
             min(max(l_freq * 0.25, 2), l_freq)
 
         Only used for ``method='fir'``.
     h_trans_bandwidth : float | str
         Width of the transition band at the high cut-off frequency in Hz
-        Can be "auto" (default in 0.14) to use a multiple of ``h_freq``::
+        (low pass or cutoff 2 in bandpass). Can be "auto"
+        (default in 0.14) to use a multiple of ``h_freq``::
 
             min(max(h_freq * 0.25, 2.), info['sfreq'] / 2. - h_freq)
 
@@ -900,51 +934,49 @@ def band_pass_filter(x, Fs, Fp1, Fp2, filter_length='',
         Dictionary of parameters to use for IIR filtering.
         See mne.filter.construct_iir_filter for details. If iir_params
         is None and method="iir", 4th order Butterworth will be used.
-    picks : array-like of int | None
-        Indices of channels to filter. If None all channels will be
-        filtered. Only supported for 2D (n_channels, n_times) and 3D
-        (n_epochs, n_channels, n_times) data.
-    n_jobs : int | str
-        Number of jobs to run in parallel. Can be 'cuda' if scikits.cuda
-        is installed properly, CUDA is initialized, and method='fir'.
-    copy : bool
-        If True, a copy of x, filtered, is returned. Otherwise, it operates
-        on x in place.
     phase : str
         Phase of the filter, only used if ``method='fir'``.
         By default, a symmetric linear-phase FIR filter is constructed.
-        If ``phase='zero'`` (default in 0.14), the delay of this filter
-        is compensated for. If ``phase=='zero-double'`` (default in 0.13
-        and before), then this filter is applied twice, once forward, and
-        once backward.
+        If ``phase='zero'`` (default), the delay of this filter
+        is compensated for. If ``phase=='zero-double'``, then this filter
+        is applied twice, once forward, and once backward. If 'minimum',
+        then a minimum-phase, causal filter will be used.
 
         .. versionadded:: 0.13
-
     fir_window : str
-        The window to use in FIR design, can be "hamming" (default in
-        0.14), "hann" (default in 0.13), or "blackman".
+        The window to use in FIR design, can be "hamming" (default),
+        "hann", or "blackman".
 
         .. versionadded:: 0.13
-
+    fir_design : str
+        Can be "firwin" (default in 0.16) to use
+        :func:`scipy.signal.firwin`, or "firwin2" (default in 0.15 and
+        before) to use :func:`scipy.signal.firwin2`. "firwin" uses a
+        time-domain design technique that generally gives improved
+        attenuation using fewer samples than "firwin2".
+
+        ..versionadded:: 0.15
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more). Defaults to
+        self.verbose.
 
     Returns
     -------
-    xf : array
-        x filtered.
+    filt : array or dict
+        Will be an array of FIR coefficients for method='fir', and dict
+        with IIR parameters for method='iir'.
 
     See Also
     --------
     filter_data
-    band_stop_filter
-    high_pass_filter
-    low_pass_filter
-    notch_filter
-    resample
 
     Notes
     -----
+    The -6 dB point for all filters is in the middle of the transition band.
+
+    **Band-pass filter**
+
     The frequency response is (approximately) given by::
 
        1-|               ----------
@@ -961,131 +993,9 @@ def band_pass_filter(x, Fs, Fp1, Fp2, filter_length='',
         * Fs1 = Fp1 - l_trans_bandwidth in Hz
         * Fs2 = Fp2 + h_trans_bandwidth in Hz
 
-    """
-    iir_params, method = _check_method(method, iir_params, [])
-    logger.info('Band-pass filtering from %0.2g - %0.2g Hz' % (Fp1, Fp2))
-    x, Fs, Fp1, Fp2, Fs1, Fs2, filter_length, phase, fir_window = \
-        _triage_filter_params(
-            x, Fs, Fp1, Fp2, l_trans_bandwidth, h_trans_bandwidth,
-            filter_length, method, phase, fir_window)
-    if method == 'fir':
-        freq = [Fs1, Fp1, Fp2, Fs2]
-        gain = [0, 1, 1, 0]
-        if Fs1 != 0:
-            freq = [0.] + freq
-            gain = [0.] + gain
-        if Fs2 != Fs / 2:
-            freq += [Fs / 2.]
-            gain += [0.]
-        xf = _fir_filter(x, Fs, freq, gain, filter_length, picks, n_jobs, copy,
-                         phase, fir_window)
-    else:
-        iir_params = construct_iir_filter(iir_params, [Fp1, Fp2],
-                                          [Fs1, Fs2], Fs, 'bandpass')
-        xf = _filtfilt(x, iir_params, picks, n_jobs, copy)
-
-    return xf
-
-
- at verbose
-def band_stop_filter(x, Fs, Fp1, Fp2, filter_length='',
-                     l_trans_bandwidth=None, h_trans_bandwidth=None,
-                     method='fir', iir_params=None, picks=None, n_jobs=1,
-                     copy=True, phase='', fir_window='', verbose=None):
-    """Bandstop filter for the signal x.
-
-    Applies a zero-phase bandstop filter to the signal x, operating on the
-    last dimension.
-
-    Parameters
-    ----------
-    x : array
-        Signal to filter.
-    Fs : float
-        Sampling rate in Hz.
-    Fp1 : float | array of float
-        Low cut-off frequency in Hz.
-    Fp2 : float | array of float
-        High cut-off frequency in Hz.
-    filter_length : str | int
-        Length of the FIR filter to use (if applicable):
-
-            * int: specified length in samples.
-            * 'auto' (default in 0.14): the filter length is chosen based
-              on the size of the transition regions (6.6 times the reciprocal
-              of the shortest transition band for fir_window='hamming').
-            * str: (default in 0.13 is "10s") a human-readable time in
-              units of "s" or "ms" (e.g., "10s" or "5500ms") will be
-              converted to that number of samples if ``phase="zero"``, or
-              the shortest power-of-two length at least that duration for
-              ``phase="zero-double"``.
-
-    l_trans_bandwidth : float | str
-        Width of the transition band at the low cut-off frequency in Hz
-        Can be "auto" (default in 0.14) to use a multiple of ``l_freq``::
-
-            min(max(l_freq * 0.25, 2), l_freq)
-
-        Only used for ``method='fir'``.
-    h_trans_bandwidth : float | str
-        Width of the transition band at the high cut-off frequency in Hz
-        Can be "auto" (default in 0.14) to use a multiple of ``h_freq``::
-
-            min(max(h_freq * 0.25, 2.), info['sfreq'] / 2. - h_freq)
-
-        Only used for ``method='fir'``.
-    method : str
-        'fir' will use overlap-add FIR filtering, 'iir' will use IIR
-        forward-backward filtering (via filtfilt).
-    iir_params : dict | None
-        Dictionary of parameters to use for IIR filtering.
-        See mne.filter.construct_iir_filter for details. If iir_params
-        is None and method="iir", 4th order Butterworth will be used.
-    picks : array-like of int | None
-        Indices of channels to filter. If None all channels will be
-        filtered. Only supported for 2D (n_channels, n_times) and 3D
-        (n_epochs, n_channels, n_times) data.
-    n_jobs : int | str
-        Number of jobs to run in parallel. Can be 'cuda' if scikits.cuda
-        is installed properly, CUDA is initialized, and method='fir'.
-    copy : bool
-        If True, a copy of x, filtered, is returned. Otherwise, it operates
-        on x in place.
-    phase : str
-        Phase of the filter, only used if ``method='fir'``.
-        By default, a symmetric linear-phase FIR filter is constructed.
-        If ``phase='zero'`` (default in 0.14), the delay of this filter
-        is compensated for. If ``phase=='zero-double'`` (default in 0.13
-        and before), then this filter is applied twice, once forward, and
-        once backward.
-
-        .. versionadded:: 0.13
-
-    fir_window : str
-        The window to use in FIR design, can be "hamming" (default in
-        0.14), "hann" (default in 0.13), or "blackman".
-
-        .. versionadded:: 0.13
-
-    verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
 
-    Returns
-    -------
-    xf : array
-        x filtered.
+    **Band-stop filter**
 
-    See Also
-    --------
-    filter_data
-    band_pass_filter
-    high_pass_filter
-    low_pass_filter
-    notch_filter
-    resample
-
-    Notes
-    -----
     The frequency response is (approximately) given by::
 
         1-|---------                   ----------
@@ -1101,133 +1011,9 @@ def band_stop_filter(x, Fs, Fp1, Fp2, filter_length='',
     ``Fs2 = Fp2 - h_trans_bandwidth``.
 
     Multiple stop bands can be specified using arrays.
-    """
-    iir_params, method = _check_method(method, iir_params, [])
-    Fp1 = np.array(Fp1, float).ravel()
-    Fp2 = np.array(Fp2, float).ravel()
-    if len(Fp1) != len(Fp2):
-        raise ValueError('Fp1 and Fp2 must be the same length')
-    # Note: order of outputs is intentionally switched here!
-    x, Fs, Fs1, Fs2, Fp1, Fp2, filter_length, phase, fir_window = \
-        _triage_filter_params(
-            x, Fs, Fp1, Fp2, l_trans_bandwidth, h_trans_bandwidth,
-            filter_length, method, phase, fir_window,
-            bands='arr', reverse=True)
-    if method == 'fir':
-        freq = np.r_[Fp1, Fs1, Fs2, Fp2]
-        gain = np.r_[np.ones_like(Fp1), np.zeros_like(Fs1),
-                     np.zeros_like(Fs2), np.ones_like(Fp2)]
-        order = np.argsort(freq)
-        freq = freq[order]
-        gain = gain[order]
-        if freq[0] != 0:
-            freq = np.r_[[0.], freq]
-            gain = np.r_[[1.], gain]
-        if freq[-1] != Fs / 2.:
-            freq = np.r_[freq, [Fs / 2.]]
-            gain = np.r_[gain, [1.]]
-        if np.any(np.abs(np.diff(gain, 2)) > 1):
-            raise ValueError('Stop bands are not sufficiently separated.')
-        xf = _fir_filter(x, Fs, freq, gain, filter_length, picks, n_jobs, copy,
-                         phase, fir_window)
-    else:
-        for fp_1, fp_2, fs_1, fs_2 in zip(Fp1, Fp2, Fs1, Fs2):
-            iir_params_new = construct_iir_filter(iir_params, [fp_1, fp_2],
-                                                  [fs_1, fs_2], Fs, 'bandstop')
-            xf = _filtfilt(x, iir_params_new, picks, n_jobs, copy)
-
-    return xf
-
-
- at verbose
-def low_pass_filter(x, Fs, Fp, filter_length='', trans_bandwidth=None,
-                    method='fir', iir_params=None, picks=None, n_jobs=1,
-                    copy=True, phase='', fir_window='', verbose=None):
-    """Lowpass filter for the signal x.
 
-    Applies a zero-phase lowpass filter to the signal x, operating on the
-    last dimension.
+    **Low-pass filter**
 
-    Parameters
-    ----------
-    x : array
-        Signal to filter.
-    Fs : float
-        Sampling rate in Hz.
-    Fp : float
-        Cut-off frequency in Hz.
-    filter_length : str | int
-        Length of the FIR filter to use (if applicable):
-
-            * int: specified length in samples.
-            * 'auto' (default in 0.14): the filter length is chosen based
-              on the size of the transition regions (6.6 times the reciprocal
-              of the shortest transition band for fir_window='hamming').
-            * str: (default in 0.13 is "10s") a human-readable time in
-              units of "s" or "ms" (e.g., "10s" or "5500ms") will be
-              converted to that number of samples if ``phase="zero"``, or
-              the shortest power-of-two length at least that duration for
-              ``phase="zero-double"``.
-
-    trans_bandwidth : float | str
-        Width of the transition band in Hz. Can be "auto"
-        (default in 0.14) to use a multiple of ``l_freq``::
-
-            min(max(l_freq * 0.25, 2), l_freq)
-
-        Only used for ``method='fir'``.
-    method : str
-        'fir' will use overlap-add FIR filtering, 'iir' will use IIR
-        forward-backward filtering (via filtfilt).
-    iir_params : dict | None
-        Dictionary of parameters to use for IIR filtering.
-        See mne.filter.construct_iir_filter for details. If iir_params
-        is None and method="iir", 4th order Butterworth will be used.
-    picks : array-like of int | None
-        Indices of channels to filter. If None all channels will be
-        filtered. Only supported for 2D (n_channels, n_times) and 3D
-        (n_epochs, n_channels, n_times) data.
-    n_jobs : int | str
-        Number of jobs to run in parallel. Can be 'cuda' if scikits.cuda
-        is installed properly, CUDA is initialized, and method='fir'.
-    copy : bool
-        If True, a copy of x, filtered, is returned. Otherwise, it operates
-        on x in place.
-    phase : str
-        Phase of the filter, only used if ``method='fir'``.
-        By default, a symmetric linear-phase FIR filter is constructed.
-        If ``phase='zero'`` (default in 0.14), the delay of this filter
-        is compensated for. If ``phase=='zero-double'`` (default in 0.13
-        and before), then this filter is applied twice, once forward, and
-        once backward.
-
-        .. versionadded:: 0.13
-
-    fir_window : str
-        The window to use in FIR design, can be "hamming" (default in
-        0.14), "hann" (default in 0.13), or "blackman".
-
-        .. versionadded:: 0.13
-
-    verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
-
-    Returns
-    -------
-    xf : array
-        x filtered.
-
-    See Also
-    --------
-    filter_data
-    band_pass_filter
-    band_stop_filter
-    high_pass_filter
-    notch_filter
-    resample
-
-    Notes
-    -----
     The frequency response is (approximately) given by::
 
         1-|------------------------
@@ -1240,117 +1026,9 @@ def low_pass_filter(x, Fs, Fp, filter_length='', trans_bandwidth=None,
           0                      Fp  Fstop           Nyq
 
     Where ``Fstop = Fp + trans_bandwidth``.
-    """
-    iir_params, method = _check_method(method, iir_params, [])
-    logger.info('Low-pass filtering at %0.2g Hz' % (Fp,))
-    x, Fs, _, Fp, _, Fstop, filter_length, phase, fir_window = \
-        _triage_filter_params(
-            x, Fs, None, Fp, None, trans_bandwidth, filter_length, method,
-            phase, fir_window)
-    if method == 'fir':
-        freq = [0, Fp, Fstop]
-        gain = [1, 1, 0]
-        if Fstop != Fs / 2.:
-            freq += [Fs / 2.]
-            gain += [0]
-        xf = _fir_filter(x, Fs, freq, gain, filter_length, picks, n_jobs, copy,
-                         phase, fir_window)
-    else:
-        iir_params = construct_iir_filter(iir_params, Fp, Fstop, Fs, 'low')
-        xf = _filtfilt(x, iir_params, picks, n_jobs, copy)
-
-    return xf
-
-
- at verbose
-def high_pass_filter(x, Fs, Fp, filter_length='', trans_bandwidth=None,
-                     method='fir', iir_params=None, picks=None, n_jobs=1,
-                     copy=True, phase='', fir_window='', verbose=None):
-    """Highpass filter for the signal x.
-
-    Applies a zero-phase highpass filter to the signal x, operating on the
-    last dimension.
-
-    Parameters
-    ----------
-    x : array
-        Signal to filter.
-    Fs : float
-        Sampling rate in Hz.
-    Fp : float
-        Cut-off frequency in Hz.
-    filter_length : str | int
-        Length of the FIR filter to use (if applicable):
-
-            * int: specified length in samples.
-            * 'auto' (default in 0.14): the filter length is chosen based
-              on the size of the transition regions (6.6 times the reciprocal
-              of the shortest transition band for fir_window='hamming').
-            * str: (default in 0.13 is "10s") a human-readable time in
-              units of "s" or "ms" (e.g., "10s" or "5500ms") will be
-              converted to that number of samples if ``phase="zero"``, or
-              the shortest power-of-two length at least that duration for
-              ``phase="zero-double"``.
-
-    trans_bandwidth : float | str
-        Width of the transition band in Hz. Can be "auto"
-        (default in 0.14) to use a multiple of ``h_freq``::
-
-            min(max(h_freq * 0.25, 2.), info['sfreq'] / 2. - h_freq)
-
-        Only used for ``method='fir'``.
-    method : str
-        'fir' will use overlap-add FIR filtering, 'iir' will use IIR
-        forward-backward filtering (via filtfilt).
-    iir_params : dict | None
-        Dictionary of parameters to use for IIR filtering.
-        See mne.filter.construct_iir_filter for details. If iir_params
-        is None and method="iir", 4th order Butterworth will be used.
-    picks : array-like of int | None
-        Indices of channels to filter. If None all channels will be
-        filtered. Only supported for 2D (n_channels, n_times) and 3D
-        (n_epochs, n_channels, n_times) data.
-    n_jobs : int | str
-        Number of jobs to run in parallel. Can be 'cuda' if scikits.cuda
-        is installed properly, CUDA is initialized, and method='fir'.
-    copy : bool
-        If True, a copy of x, filtered, is returned. Otherwise, it operates
-        on x in place.
-    phase : str
-        Phase of the filter, only used if ``method='fir'``.
-        By default, a symmetric linear-phase FIR filter is constructed.
-        If ``phase='zero'`` (default in 0.14), the delay of this filter
-        is compensated for. If ``phase=='zero-double'`` (default in 0.13
-        and before), then this filter is applied twice, once forward, and
-        once backward.
-
-        .. versionadded:: 0.13
-
-    fir_window : str
-        The window to use in FIR design, can be "hamming" (default in
-        0.14), "hann" (default in 0.13), or "blackman".
-
-        .. versionadded:: 0.13
-
-    verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
 
-    Returns
-    -------
-    xf : array
-        x filtered.
+    **High-pass filter**
 
-    See Also
-    --------
-    filter_data
-    band_pass_filter
-    band_stop_filter
-    low_pass_filter
-    notch_filter
-    resample
-
-    Notes
-    -----
     The frequency response is (approximately) given by::
 
         1-|             -----------------------
@@ -1363,33 +1041,134 @@ def high_pass_filter(x, Fs, Fp, filter_length='', trans_bandwidth=None,
           0      Fstop  Fp                   Nyq
 
     Where ``Fstop = Fp - trans_bandwidth``.
+
+    .. versionadded:: 0.14
     """
-    iir_params, method = _check_method(method, iir_params, [])
-    logger.info('High-pass filtering at %0.2g Hz' % (Fp,))
-    x, Fs, Fp, _, Fstop, _, filter_length, phase, fir_window = \
-        _triage_filter_params(
-            x, Fs, Fp, None, trans_bandwidth, None, filter_length, method,
-            phase, fir_window)
+    sfreq = float(sfreq)
+    if sfreq < 0:
+        raise ValueError('sfreq must be positive')
+    if h_freq is not None:
+        h_freq = np.array(h_freq, float).ravel()
+        if (h_freq > (sfreq / 2.)).any():
+            raise ValueError('h_freq (%s) must be less than the Nyquist '
+                             'frequency %s' % (h_freq, sfreq / 2.))
+    if l_freq is not None:
+        l_freq = np.array(l_freq, float).ravel()
+        if (l_freq == 0).all():
+            l_freq = None
+    iir_params, method = _check_method(method, iir_params)
+    if l_freq is None and h_freq is None:
+        data, sfreq, _, _, _, _, filter_length, phase, fir_window, \
+            fir_design = _triage_filter_params(
+                data, sfreq, None, None, None, None,
+                filter_length, method, phase, fir_window, fir_design)
+        if method == 'iir':
+            out = dict() if iir_params is None else deepcopy(iir_params)
+            out.update(b=np.array([1.]), a=np.array([1.]))
+        else:
+            freq = [0, sfreq / 2.]
+            gain = [1., 1.]
+    if l_freq is None and h_freq is not None:
+        logger.info('Setting up low-pass filter at %0.2g Hz' % (h_freq,))
+        data, sfreq, _, f_p, _, f_s, filter_length, phase, fir_window, \
+            fir_design = _triage_filter_params(
+                data, sfreq, None, h_freq, None, h_trans_bandwidth,
+                filter_length, method, phase, fir_window, fir_design)
+        if method == 'iir':
+            out = construct_iir_filter(iir_params, f_p, f_s, sfreq, 'low')
+        else:  # 'fir'
+            freq = [0, f_p, f_s]
+            gain = [1, 1, 0]
+            if f_s != sfreq / 2.:
+                freq += [sfreq / 2.]
+                gain += [0]
+    elif l_freq is not None and h_freq is None:
+        logger.info('Setting up high-pass filter at %0.2g Hz' % (l_freq,))
+        data, sfreq, pass_, _, stop, _, filter_length, phase, fir_window, \
+            fir_design = _triage_filter_params(
+                data, sfreq, l_freq, None, l_trans_bandwidth, None,
+                filter_length, method, phase, fir_window, fir_design)
+        if method == 'iir':
+            out = construct_iir_filter(iir_params, pass_, stop, sfreq,
+                                       'high')
+        else:  # 'fir'
+            freq = [stop, pass_, sfreq / 2.]
+            gain = [0, 1, 1]
+            if stop != 0:
+                freq = [0] + freq
+                gain = [0] + gain
+    elif l_freq is not None and h_freq is not None:
+        if (l_freq < h_freq).any():
+            logger.info('Setting up band-pass filter from %0.2g - %0.2g Hz'
+                        % (l_freq, h_freq))
+            data, sfreq, f_p1, f_p2, f_s1, f_s2, filter_length, phase, \
+                fir_window, fir_design = _triage_filter_params(
+                    data, sfreq, l_freq, h_freq, l_trans_bandwidth,
+                    h_trans_bandwidth, filter_length, method, phase,
+                    fir_window, fir_design)
+            if method == 'iir':
+                out = construct_iir_filter(iir_params, [f_p1, f_p2],
+                                           [f_s1, f_s2], sfreq, 'bandpass')
+            else:  # 'fir'
+                freq = [f_s1, f_p1, f_p2, f_s2]
+                gain = [0, 1, 1, 0]
+                if f_s2 != sfreq / 2.:
+                    freq += [sfreq / 2.]
+                    gain += [0]
+                if f_s1 != 0:
+                    freq = [0] + freq
+                    gain = [0] + gain
+        else:
+            # This could possibly be removed after 0.14 release, but might
+            # as well leave it in to sanity check notch_filter
+            if len(l_freq) != len(h_freq):
+                raise ValueError('l_freq and h_freq must be the same length')
+            msg = 'Setting up band-stop filter'
+            if len(l_freq) == 1:
+                msg += ' from %0.2g - %0.2g Hz' % (h_freq, l_freq)
+            logger.info(msg)
+            # Note: order of outputs is intentionally switched here!
+            data, sfreq, f_s1, f_s2, f_p1, f_p2, filter_length, phase, \
+                fir_window, fir_design = _triage_filter_params(
+                    data, sfreq, h_freq, l_freq, h_trans_bandwidth,
+                    l_trans_bandwidth, filter_length, method, phase,
+                    fir_window, fir_design, bands='arr', reverse=True)
+            if method == 'iir':
+                if len(f_p1) != 1:
+                    raise ValueError('Multiple stop-bands can only be used '
+                                     'with FIR filtering')
+                out = construct_iir_filter(iir_params, [f_p1[0], f_p2[0]],
+                                           [f_s1[0], f_s2[0]], sfreq,
+                                           'bandstop')
+            else:  # 'fir'
+                freq = np.r_[f_p1, f_s1, f_s2, f_p2]
+                gain = np.r_[np.ones_like(f_p1), np.zeros_like(f_s1),
+                             np.zeros_like(f_s2), np.ones_like(f_p2)]
+                order = np.argsort(freq)
+                freq = freq[order]
+                gain = gain[order]
+                if freq[0] != 0:
+                    freq = np.r_[[0.], freq]
+                    gain = np.r_[[1.], gain]
+                if freq[-1] != sfreq / 2.:
+                    freq = np.r_[freq, [sfreq / 2.]]
+                    gain = np.r_[gain, [1.]]
+                if np.any(np.abs(np.diff(gain, 2)) > 1):
+                    raise ValueError('Stop bands are not sufficiently '
+                                     'separated.')
     if method == 'fir':
-        freq = [Fstop, Fp, Fs / 2.]
-        gain = [0, 1, 1]
-        if Fstop != 0:
-            freq = [0] + freq
-            gain = [0] + gain
-        xf = _fir_filter(x, Fs, freq, gain, filter_length, picks, n_jobs, copy,
-                         phase, fir_window)
-    else:
-        iir_params = construct_iir_filter(iir_params, Fp, Fstop, Fs, 'high')
-        xf = _filtfilt(x, iir_params, picks, n_jobs, copy)
-    return xf
+        out = _construct_fir_filter(sfreq, freq, gain, filter_length, phase,
+                                    fir_window, fir_design)
+    return out
 
 
 @verbose
-def notch_filter(x, Fs, freqs, filter_length='', notch_widths=None,
+def notch_filter(x, Fs, freqs, filter_length='auto', notch_widths=None,
                  trans_bandwidth=1, method='fir', iir_params=None,
                  mt_bandwidth=None, p_value=0.05, picks=None, n_jobs=1,
-                 copy=True, phase='', fir_window='', verbose=None):
-    """Notch filter for the signal x.
+                 copy=True, phase='zero', fir_window='hamming',
+                 fir_design=None, pad='reflect_limited', verbose=None):
+    r"""Notch filter for the signal x.
 
     Applies a zero-phase notch filter to the signal x, operating on the last
     dimension.
@@ -1407,15 +1186,17 @@ def notch_filter(x, Fs, freqs, filter_length='', notch_widths=None,
     filter_length : str | int
         Length of the FIR filter to use (if applicable):
 
-            * int: specified length in samples.
-            * 'auto' (default in 0.14): the filter length is chosen based
-              on the size of the transition regions (6.6 times the reciprocal
-              of the shortest transition band for fir_window='hamming').
-            * str: (default in 0.13 is "10s") a human-readable time in
-              units of "s" or "ms" (e.g., "10s" or "5500ms") will be
-              converted to that number of samples if ``phase="zero"``, or
-              the shortest power-of-two length at least that duration for
-              ``phase="zero-double"``.
+        * 'auto' (default): the filter length is chosen based
+          on the size of the transition regions (6.6 times the reciprocal
+          of the shortest transition band for fir_window='hamming'
+          and fir_design="firwin2", and half that for "firwin").
+        * str: a human-readable time in
+          units of "s" or "ms" (e.g., "10s" or "5500ms") will be
+          converted to that number of samples if ``phase="zero"``, or
+          the shortest power-of-two length at least that duration for
+          ``phase="zero-double"``.
+        * int: specified length in samples. For fir_design="firwin",
+          this should not be used.
 
     notch_widths : float | array of float | None
         Width of the stop band (centred at each freq in freqs) in Hz.
@@ -1454,21 +1235,36 @@ def notch_filter(x, Fs, freqs, filter_length='', notch_widths=None,
     phase : str
         Phase of the filter, only used if ``method='fir'``.
         By default, a symmetric linear-phase FIR filter is constructed.
-        If ``phase='zero'`` (default in 0.14), the delay of this filter
-        is compensated for. If ``phase=='zero-double'`` (default in 0.13
-        and before), then this filter is applied twice, once forward, and
-        once backward.
+        If ``phase='zero'`` (default), the delay of this filter
+        is compensated for. If ``phase=='zero-double'``, then this filter
+        is applied twice, once forward, and once backward. If 'minimum',
+        then a minimum-phase, causal filter will be used.
 
         .. versionadded:: 0.13
-
     fir_window : str
-        The window to use in FIR design, can be "hamming" (default in
-        0.14), "hann" (default in 0.13), or "blackman".
+        The window to use in FIR design, can be "hamming" (default),
+        "hann" (default in 0.13), or "blackman".
 
         .. versionadded:: 0.13
+    fir_design : str
+        Can be "firwin" (default in 0.16) to use
+        :func:`scipy.signal.firwin`, or "firwin2" (default in 0.15 and
+        before) to use :func:`scipy.signal.firwin2`. "firwin" uses a
+        time-domain design technique that generally gives improved
+        attenuation using fewer samples than "firwin2".
+
+        ..versionadded:: 0.15
+    pad : str
+        The type of padding to use. Supports all :func:`numpy.pad` ``mode``
+        options. Can also be "reflect_limited" (default), which pads with a
+        reflected version of each vector mirrored on the first and last
+        values of the vector, followed by zeros.
+        Only used for ``method='fir'``.
 
+        .. versionadded:: 0.15
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -1478,10 +1274,6 @@ def notch_filter(x, Fs, freqs, filter_length='', notch_widths=None,
     See Also
     --------
     filter_data
-    band_pass_filter
-    band_stop_filter
-    high_pass_filter
-    low_pass_filter
     resample
 
     Notes
@@ -1529,16 +1321,16 @@ def notch_filter(x, Fs, freqs, filter_length='', notch_widths=None,
                 raise ValueError('notch_widths must be None, scalar, or the '
                                  'same length as freqs')
 
-    if method in ['fir', 'iir']:
+    if method in ('fir', 'iir'):
         # Speed this up by computing the fourier coefficients once
         tb_2 = trans_bandwidth / 2.0
         lows = [freq - nw / 2.0 - tb_2
                 for freq, nw in zip(freqs, notch_widths)]
         highs = [freq + nw / 2.0 + tb_2
                  for freq, nw in zip(freqs, notch_widths)]
-        xf = band_stop_filter(x, Fs, lows, highs, filter_length, tb_2, tb_2,
-                              method, iir_params, picks, n_jobs, copy,
-                              phase=phase, fir_window=fir_window)
+        xf = filter_data(x, Fs, highs, lows, picks, filter_length, tb_2, tb_2,
+                         n_jobs, method, iir_params, copy, phase, fir_window,
+                         fir_design, pad=pad)
     elif method == 'spectrum_fit':
         xf = _mt_spectrum_proc(x, Fs, freqs, notch_widths, mt_bandwidth,
                                p_value, picks, n_jobs, copy)
@@ -1548,7 +1340,7 @@ def notch_filter(x, Fs, freqs, filter_length='', notch_widths=None,
 
 def _mt_spectrum_proc(x, sfreq, line_freqs, notch_widths, mt_bandwidth,
                       p_value, picks, n_jobs, copy):
-    """Helper to more easily call _mt_spectrum_remove"""
+    """Call _mt_spectrum_remove."""
     from scipy import stats
     # set up array for filtering, reshape to 2D, operate on last axis
     n_jobs = check_n_jobs(n_jobs)
@@ -1611,7 +1403,7 @@ def _mt_spectrum_proc(x, sfreq, line_freqs, notch_widths, mt_bandwidth,
 
 def _mt_spectrum_remove(x, sfreq, line_freqs, notch_widths,
                         window_fun, threshold):
-    """Use MT-spectrum to remove line frequencies
+    """Use MT-spectrum to remove line frequencies.
 
     Based on Chronux. If line_freqs is specified, all freqs within notch_width
     of each line_freq is set to zero.
@@ -1685,9 +1477,9 @@ def _mt_spectrum_remove(x, sfreq, line_freqs, notch_widths,
 
 
 @verbose
-def resample(x, up, down, npad=100, axis=-1, window='boxcar', n_jobs=1,
-             verbose=None):
-    """Resample the array x
+def resample(x, up=1., down=1., npad=100, axis=-1, window='boxcar', n_jobs=1,
+             pad='reflect_limited', verbose=None):
+    """Resample an array.
 
     Operates along the last dimension of the array.
 
@@ -1709,8 +1501,16 @@ def resample(x, up, down, npad=100, axis=-1, window='boxcar', n_jobs=1,
     n_jobs : int | str
         Number of jobs to run in parallel. Can be 'cuda' if scikits.cuda
         is installed properly and CUDA is initialized.
+    pad : str
+        The type of padding to use. Supports all :func:`numpy.pad` ``mode``
+        options. Can also be "reflect_limited" (default), which pads with a
+        reflected version of each vector mirrored on the first and last
+        values of the vector, followed by zeros.
+
+        .. versionadded:: 0.15
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -1801,10 +1601,10 @@ def resample(x, up, down, npad=100, axis=-1, window='boxcar', n_jobs=1,
         y = np.zeros((len(x_flat), new_len - to_removes.sum()), dtype=x.dtype)
         for xi, x_ in enumerate(x_flat):
             y[xi] = fft_resample(x_, W, new_len, npads, to_removes,
-                                 cuda_dict)
+                                 cuda_dict, pad)
     else:
         parallel, p_fun, _ = parallel_func(fft_resample, n_jobs)
-        y = parallel(p_fun(x_, W, new_len, npads, to_removes, cuda_dict)
+        y = parallel(p_fun(x_, W, new_len, npads, to_removes, cuda_dict, pad)
                      for x_ in x_flat)
         y = np.array(y)
 
@@ -1915,27 +1715,26 @@ def detrend(x, order=1, axis=-1):
 def _triage_filter_params(x, sfreq, l_freq, h_freq,
                           l_trans_bandwidth, h_trans_bandwidth,
                           filter_length, method, phase, fir_window,
-                          bands='scalar', reverse=False):
-    """Helper to validate and automate filter parameter selection"""
-    dep = list()
+                          fir_design, bands='scalar', reverse=False):
+    """Validate and automate filter parameter selection."""
     if not isinstance(phase, string_types) or phase not in \
-            ('linear', 'zero', 'zero-double', ''):
-        raise ValueError('phase must be "linear", "zero", or "zero-double", '
-                         'got "%s"' % (phase,))
+            ('linear', 'zero', 'zero-double', 'minimum', ''):
+        raise ValueError('phase must be "linear", "zero", "zero-double", '
+                         'or "minimum", got "%s"' % (phase,))
     if not isinstance(fir_window, string_types) or fir_window not in \
             ('hann', 'hamming', 'blackman', ''):
         raise ValueError('fir_window must be "hamming", "hann", or "blackman",'
                          'got "%s"' % (fir_window,))
-    if phase == '':
-        if method == 'fir':
-            dep += ['phase in 0.13 is "zero-double" but will change to '
-                    '"zero" in 0.14']
-        phase = 'zero-double'
-    if fir_window == '':
-        if method == 'fir':
-            dep += ['fir_window in 0.13 is "hann" but will change to '
-                    '"hamming" in 0.14']
-        fir_window = 'hann'
+    if fir_design is None:
+        fir_design = 'firwin2'
+        if method != 'iir':
+            warn('fir_design defaults to "firwin2" in 0.15 but will change to '
+                 '"firwin" in 0.16, set it explicitly to avoid this warning.',
+                 DeprecationWarning)
+    if not isinstance(fir_design, string_types) or \
+            fir_design not in ('firwin', 'firwin2'):
+        raise ValueError('fir_design must be "firwin" or "firwin2", got %s'
+                         % (fir_design,))
 
     def float_array(c):
         return np.array(c, float).ravel()
@@ -1971,10 +1770,6 @@ def _triage_filter_params(x, sfreq, l_freq, h_freq,
                                                l_freq)
                 logger.info('l_trans_bandwidth chosen to be %0.1f Hz'
                             % (l_trans_bandwidth,))
-            elif l_trans_bandwidth is None:
-                dep += ['lower transition bandwidth in 0.13 is 0.5 Hz but '
-                        'will change to "auto" in 0.14']
-                l_trans_bandwidth = 0.5
             l_trans_bandwidth = cast(l_trans_bandwidth)
             if np.any(l_trans_bandwidth <= 0):
                 raise ValueError('l_trans_bandwidth must be positive, got %s'
@@ -1997,10 +1792,6 @@ def _triage_filter_params(x, sfreq, l_freq, h_freq,
                                                sfreq / 2. - h_freq)
                 logger.info('h_trans_bandwidth chosen to be %0.1f Hz'
                             % (h_trans_bandwidth))
-            elif h_trans_bandwidth is None:
-                dep += ['upper transition bandwidth in 0.13 is 0.5 Hz but '
-                        'will change to "auto" in 0.14']
-                h_trans_bandwidth = 0.5
             h_trans_bandwidth = cast(h_trans_bandwidth)
             if np.any(h_trans_bandwidth <= 0):
                 raise ValueError('h_trans_bandwidth must be positive, got %s'
@@ -2013,56 +1804,49 @@ def _triage_filter_params(x, sfreq, l_freq, h_freq,
                 raise ValueError('Effective band-stop frequency (%s) is too '
                                  'high (maximum based on Nyquist is %s)'
                                  % (h_stop, sfreq / 2.))
-    if isinstance(filter_length, string_types):
-        filter_length = filter_length.lower()
-        if filter_length == '':
-            if method == 'fir':
-                dep += ['The default filter length in 0.13 is "10s" but will '
-                        'change to "auto" in 0.14']
-            filter_length = '10s'
-        if filter_length == 'auto':
-            h_check = h_trans_bandwidth if h_freq is not None else np.inf
-            l_check = l_trans_bandwidth if l_freq is not None else np.inf
-            filter_length = max(int(round(
-                _length_factors[fir_window] * sfreq /
-                float(min(h_check, l_check)))), 1)
+        if isinstance(filter_length, string_types):
+            filter_length = filter_length.lower()
+            if filter_length == 'auto':
+                h_check = h_trans_bandwidth if h_freq is not None else np.inf
+                l_check = l_trans_bandwidth if l_freq is not None else np.inf
+                mult_fact = 2. if fir_design == 'firwin2' else 1.
+                filter_length = max(int(round(
+                    _length_factors[fir_window] * sfreq * mult_fact /
+                    float(min(h_check, l_check)))), 1)
+            else:
+                err_msg = ('filter_length, if a string, must be a '
+                           'human-readable time, e.g. "10s", or "auto", not '
+                           '"%s"' % filter_length)
+                if filter_length.lower().endswith('ms'):
+                    mult_fact = 1e-3
+                    filter_length = filter_length[:-2]
+                elif filter_length[-1].lower() == 's':
+                    mult_fact = 1
+                    filter_length = filter_length[:-1]
+                else:
+                    raise ValueError(err_msg)
+                # now get the number
+                try:
+                    filter_length = float(filter_length)
+                except ValueError:
+                    raise ValueError(err_msg)
+                if phase == 'zero-double':  # old mode
+                    filter_length = 2 ** int(np.ceil(np.log2(
+                        filter_length * mult_fact * sfreq)))
+                else:
+                    filter_length = max(int(np.ceil(filter_length * mult_fact *
+                                                    sfreq)), 1)
+            if fir_design == 'firwin':
+                filter_length += (filter_length - 1) % 2
             logger.info('Filter length of %s samples (%0.3f sec) selected'
                         % (filter_length, filter_length / sfreq))
-        else:
-            err_msg = ('filter_length, if a string, must be a human-readable '
-                       'time, e.g. "10s", or "auto", not "%s"' % filter_length)
-            if filter_length.lower().endswith('ms'):
-                mult_fact = 1e-3
-                filter_length = filter_length[:-2]
-            elif filter_length[-1].lower() == 's':
-                mult_fact = 1
-                filter_length = filter_length[:-1]
-            else:
-                raise ValueError(err_msg)
-            # now get the number
-            try:
-                filter_length = float(filter_length)
-            except ValueError:
-                raise ValueError(err_msg)
-            if phase == 'zero-double':  # old mode
-                filter_length = 2 ** int(np.ceil(np.log2(
-                    filter_length * mult_fact * sfreq)))
-            else:
-                filter_length = max(int(np.ceil(filter_length * mult_fact *
-                                                sfreq)), 1)
-    elif filter_length is None:
-        filter_length = len_x
-        if phase == 'zero':
-            filter_length -= (filter_length % 2 == 0)
-        dep += ['filter_length=None has been deprecated, set the filter '
-                'length using an integer or string']
-    elif not isinstance(filter_length, integer_types):
-        raise ValueError('filter_length must be a str, int, or None, got %s'
-                         % (type(filter_length),))
-    if phase == 'zero':
-        filter_length += (filter_length % 2 == 0)
+        elif not isinstance(filter_length, integer_types):
+            raise ValueError('filter_length must be a str, int, or None, got '
+                             '%s' % (type(filter_length),))
     if method != 'fir':
         filter_length = len_x
+    if phase == 'zero' and method == 'fir':
+        filter_length += (filter_length % 2 == 0)
     if filter_length <= 0:
         raise ValueError('filter_length must be positive, got %s'
                          % (filter_length,))
@@ -2071,18 +1855,16 @@ def _triage_filter_params(x, sfreq, l_freq, h_freq,
              'distortion is likely. Reduce filter length or filter a '
              'longer signal.' % (filter_length, len_x))
     logger.debug('Using filter length: %s' % filter_length)
-    if len(dep) > 0:
-        warn(('Multiple deprecated filter parameters were used:\n'
-              if len(dep) > 1 else '') + '\n'.join(dep), DeprecationWarning)
     return (x, sfreq, l_freq, h_freq, l_stop, h_stop, filter_length, phase,
-            fir_window)
+            fir_window, fir_design)
 
 
 class FilterMixin(object):
-    """Object for Epoch/Evoked filtering"""
+    """Object for Epoch/Evoked filtering."""
 
-    def savgol_filter(self, h_freq, copy=False):
-        """Filter the data using Savitzky-Golay polynomial method
+    @verbose
+    def savgol_filter(self, h_freq, copy=False, verbose=None):
+        """Filter the data using Savitzky-Golay polynomial method.
 
         Parameters
         ----------
@@ -2093,8 +1875,11 @@ class FilterMixin(object):
             This parameter is thus used to determine the length of the
             window over which a 5th-order polynomial smoothing is used.
         copy : bool
-            If True, a copy of the object, filtered, is returned.
-            If False (default), it operates on the object in place.
+            Deprecated. Use ``inst.copy()`` instead.
+        verbose : bool, str, int, or None
+            If not None, override default verbose level (see
+            :func:`mne.verbose` and :ref:`Logging documentation <tut_logging>`
+            for more). Defaults to self.verbose.
 
         Returns
         -------
@@ -2128,19 +1913,12 @@ class FilterMixin(object):
         .. [1] Savitzky, A., Golay, M.J.E. (1964). "Smoothing and
                Differentiation of Data by Simplified Least Squares
                Procedures". Analytical Chemistry 36 (8): 1627-39.
-        """  # noqa
-        from .evoked import Evoked
-        from .epochs import _BaseEpochs
+        """  # noqa: E501
+        if copy:
+            warn('copy is deprecated and will be removed in 0.16, '
+                 'use inst.copy() instead', DeprecationWarning)
         inst = self.copy() if copy else self
-        if isinstance(inst, Evoked):
-            data = inst.data
-            axis = 1
-        elif isinstance(inst, _BaseEpochs):
-            if not inst.preload:
-                raise RuntimeError('data must be preloaded to filter')
-            data = inst._data
-            axis = 2
-
+        _check_preload(self, 'inst.savgol_filter')
         h_freq = float(h_freq)
         if h_freq >= inst.info['sfreq'] / 2.:
             raise ValueError('h_freq must be less than half the sample rate')
@@ -2151,16 +1929,210 @@ class FilterMixin(object):
         from scipy.signal import savgol_filter
         window_length = (int(np.round(inst.info['sfreq'] /
                                       h_freq)) // 2) * 2 + 1
-        data[...] = savgol_filter(data, axis=axis, polyorder=5,
-                                  window_length=window_length)
+        logger.info('Using savgol length %d' % window_length)
+        inst._data[:] = savgol_filter(inst._data, axis=-1, polyorder=5,
+                                      window_length=window_length)
         return inst
 
+    @verbose
+    def filter(self, l_freq, h_freq, picks=None, filter_length='auto',
+               l_trans_bandwidth='auto', h_trans_bandwidth='auto', n_jobs=1,
+               method='fir', iir_params=None, phase='zero',
+               fir_window='hamming', fir_design=None,
+               pad='edge', verbose=None):
+        """Filter a subset of channels.
+
+        Applies a zero-phase low-pass, high-pass, band-pass, or band-stop
+        filter to the channels selected by ``picks``.
+        The data are modified inplace.
+
+        The object has to have the data loaded e.g. with ``preload=True``
+        or ``self.load_data()``.
+
+        ``l_freq`` and ``h_freq`` are the frequencies below which and above
+        which, respectively, to filter out of the data. Thus the uses are:
+
+            * ``l_freq < h_freq``: band-pass filter
+            * ``l_freq > h_freq``: band-stop filter
+            * ``l_freq is not None and h_freq is None``: high-pass filter
+            * ``l_freq is None and h_freq is not None``: low-pass filter
+
+        ``self.info['lowpass']`` and ``self.info['highpass']`` are only
+        updated with picks=None.
+
+        .. note:: If n_jobs > 1, more memory is required as
+                  ``len(picks) * n_times`` additional time points need to
+                  be temporaily stored in memory.
+
+        Parameters
+        ----------
+        l_freq : float | None
+            Low cut-off frequency in Hz. If None the data are only low-passed.
+        h_freq : float | None
+            High cut-off frequency in Hz. If None the data are only
+            high-passed.
+        picks : array-like of int | None
+            Indices of channels to filter. If None only the data (MEG/EEG)
+            channels will be filtered.
+        filter_length : str | int
+            Length of the FIR filter to use (if applicable):
+
+            * 'auto' (default): the filter length is chosen based
+              on the size of the transition regions (6.6 times the reciprocal
+              of the shortest transition band for fir_window='hamming'
+              and fir_design="firwin2", and half that for "firwin").
+            * str: a human-readable time in
+              units of "s" or "ms" (e.g., "10s" or "5500ms") will be
+              converted to that number of samples if ``phase="zero"``, or
+              the shortest power-of-two length at least that duration for
+              ``phase="zero-double"``.
+            * int: specified length in samples. For fir_design="firwin",
+              this should not be used.
+
+        l_trans_bandwidth : float | str
+            Width of the transition band at the low cut-off frequency in Hz
+            (high pass or cutoff 1 in bandpass). Can be "auto"
+            (default) to use a multiple of ``l_freq``::
+
+                min(max(l_freq * 0.25, 2), l_freq)
+
+            Only used for ``method='fir'``.
+        h_trans_bandwidth : float | str
+            Width of the transition band at the high cut-off frequency in Hz
+            (low pass or cutoff 2 in bandpass). Can be "auto"
+            (default) to use a multiple of ``h_freq``::
+
+                min(max(h_freq * 0.25, 2.), info['sfreq'] / 2. - h_freq)
+
+            Only used for ``method='fir'``.
+        n_jobs : int | str
+            Number of jobs to run in parallel. Can be 'cuda' if scikits.cuda
+            is installed properly, CUDA is initialized, and method='fir'.
+        method : str
+            'fir' will use overlap-add FIR filtering, 'iir' will use IIR
+            forward-backward filtering (via filtfilt).
+        iir_params : dict | None
+            Dictionary of parameters to use for IIR filtering.
+            See mne.filter.construct_iir_filter for details. If iir_params
+            is None and method="iir", 4th order Butterworth will be used.
+        phase : str
+            Phase of the filter, only used if ``method='fir'``.
+            By default, a symmetric linear-phase FIR filter is constructed.
+            If ``phase='zero'`` (default), the delay of this filter
+            is compensated for. If ``phase=='zero-double'``, then this filter
+            is applied twice, once forward, and once backward. If 'minimum',
+            then a minimum-phase, causal filter will be used.
+        fir_window : str
+            The window to use in FIR design, can be "hamming" (default),
+            "hann" (default in 0.13), or "blackman".
+        fir_design : str
+            Can be "firwin" (default in 0.16) to use
+            :func:`scipy.signal.firwin`, or "firwin2" (default in 0.15 and
+            before) to use :func:`scipy.signal.firwin2`. "firwin" uses a
+            time-domain design technique that generally gives improved
+            attenuation using fewer samples than "firwin2".
+        pad : str
+            The type of padding to use. Supports all :func:`numpy.pad` ``mode``
+            options. Can also be "reflect_limited", which pads with a
+            reflected version of each vector mirrored on the first and last
+            values of the vector, followed by zeros. The default is "edge",
+            which pads with the edge values of each vector.
+            Only used for ``method='fir'``.
+        verbose : bool, str, int, or None
+            If not None, override default verbose level (see
+            :func:`mne.verbose` and :ref:`Logging documentation <tut_logging>`
+            for more). Defaults to self.verbose.
+
+        Returns
+        -------
+        inst : instance of Epochs or Evoked
+            The filtered data.
+
+        Notes
+        -----
+        .. versionadded:: 0.15
+        """
+        _check_preload(self, 'inst.filter')
+        if pad is None and method != 'iir':
+            pad = 'edge'
+        update_info, picks = _filt_check_picks(self.info, picks,
+                                               l_freq, h_freq)
+        filter_data(self._data, self.info['sfreq'], l_freq, h_freq, picks,
+                    filter_length, l_trans_bandwidth, h_trans_bandwidth,
+                    n_jobs, method, iir_params, copy=False, phase=phase,
+                    fir_window=fir_window, fir_design=fir_design, pad=pad)
+        _filt_update_info(self.info, update_info, l_freq, h_freq)
+        return self
+
+    @verbose
+    def resample(self, sfreq, npad='auto', window='boxcar', n_jobs=1,
+                 pad='edge', verbose=None):
+        """Resample data.
+
+        .. note:: Data must be loaded.
+
+        Parameters
+        ----------
+        sfreq : float
+            New sample rate to use
+        npad : int | str
+            Amount to pad the start and end of the data.
+            Can also be "auto" to use a padding that will result in
+            a power-of-two size (can be much faster).
+        window : string or tuple
+            Window to use in resampling. See :func:`scipy.signal.resample`.
+        n_jobs : int
+            Number of jobs to run in parallel.
+        pad : str
+            The type of padding to use. Supports all :func:`numpy.pad` ``mode``
+            options. Can also be "reflect_limited", which pads with a
+            reflected version of each vector mirrored on the first and last
+            values of the vector, followed by zeros. The default is "edge",
+            which pads with the edge values of each vector.
+
+            .. versionadded:: 0.15
+        verbose : bool, str, int, or None
+            If not None, override default verbose level (see
+            :func:`mne.verbose` :ref:`Logging documentation <tut_logging>` for
+            more). Defaults to self.verbose.
+
+        Returns
+        -------
+        inst : instance of Epochs | instance of Evoked
+            The resampled epochs object.
+
+        See Also
+        --------
+        mne.io.Raw.resample
+
+        Notes
+        -----
+        For some data, it may be more accurate to use npad=0 to reduce
+        artifacts. This is dataset dependent -- check your data!
+        """
+        from .epochs import BaseEpochs
+        _check_preload(self, 'inst.resample')
+        sfreq = float(sfreq)
+        o_sfreq = self.info['sfreq']
+        self._data = resample(self._data, sfreq, o_sfreq, npad, window=window,
+                              n_jobs=n_jobs, pad=pad)
+        self.info['sfreq'] = float(sfreq)
+        self.times = (np.arange(self._data.shape[-1], dtype=np.float) /
+                      sfreq + self.times[0])
+        # adjust indirectly affected variables
+        if isinstance(self, BaseEpochs):
+            self._raw_times = self.times
+        else:
+            self.first = int(self.times[0] * self.info['sfreq'])
+            self.last = len(self.times) + self.first - 1
+        return self
+
 
 @verbose
 def design_mne_c_filter(sfreq, l_freq=None, h_freq=40.,
                         l_trans_bandwidth=None, h_trans_bandwidth=5.,
                         verbose=None):
-    """Create a FIR filter like that used by MNE-C
+    """Create a FIR filter like that used by MNE-C.
 
     Parameters
     ----------
@@ -2177,8 +2149,9 @@ def design_mne_c_filter(sfreq, l_freq=None, h_freq=40.,
     h_trans_bandwidth : float
         High transition bandwidth in Hz.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
-        Defaults to self.verbose.
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more). Defaults to
+        self.verbose.
 
     Returns
     -------
@@ -2232,3 +2205,141 @@ def design_mne_c_filter(sfreq, l_freq=None, h_freq=40.,
     h = ifft(np.concatenate((freq_resp, freq_resp[::-1][:-1]))).real
     h = np.roll(h, n_freqs - 1)  # center the impulse like a linear-phase filt
     return h
+
+
+def _filt_check_picks(info, picks, h_freq, l_freq):
+    from .io.pick import _pick_data_or_ica
+    data_picks = _pick_data_or_ica(info)
+    update_info = False
+    if picks is None:
+        picks = data_picks
+        update_info = True
+        # let's be safe.
+        if len(picks) == 0:
+            raise RuntimeError('Could not find any valid channels for '
+                               'your Raw object. Please contact the '
+                               'MNE-Python developers.')
+    elif h_freq is not None or l_freq is not None:
+        if np.in1d(data_picks, picks).all():
+            update_info = True
+        else:
+            logger.info('Filtering a subset of channels. The highpass and '
+                        'lowpass values in the measurement info will not '
+                        'be updated.')
+    return update_info, picks
+
+
+def _filt_update_info(info, update_info, l_freq, h_freq):
+    if update_info:
+        if h_freq is not None and (l_freq is None or l_freq < h_freq) and \
+                (info["lowpass"] is None or h_freq < info['lowpass']):
+            info['lowpass'] = float(h_freq)
+        if l_freq is not None and (h_freq is None or l_freq < h_freq) and \
+                (info["highpass"] is None or l_freq > info['highpass']):
+            info['highpass'] = float(l_freq)
+
+
+###############################################################################
+# Class for interpolation between adjacent points
+
+class _Interp2(object):
+    r"""Interpolate between two points.
+
+    Parameters
+    ----------
+    interp : str
+        Can be 'zero', 'linear', 'hann', or 'cos2'.
+
+    Notes
+    -----
+    This will process data using overlapping windows of potentially
+    different sizes to achieve a constant output value using different
+    2-point interpolation schemes. For example, for linear interpolation,
+    and window sizes of 6 and 17, this would look like::
+
+        1 _     _
+          |\   / '-.           .-'
+          | \ /     '-.     .-'
+          |  x         |-.-|
+          | / \     .-'     '-.
+          |/   \_.-'           '-.
+        0 +----|----|----|----|---
+          0    5   10   15   20   25
+
+    """
+
+    @verbose
+    def __init__(self, interp='hann'):
+        # set up interpolation
+        self._last = dict()
+        self._current = dict()
+        self._count = dict()
+        self._n_samp = None
+        self.interp = interp
+
+    def __setitem__(self, key, value):
+        """Update an item."""
+        if value is None:
+            assert key not in self._current
+            return
+        if key in self._current:
+            self._last[key] = self._current[key].copy()
+        self._current[key] = value.copy()
+        self._count[key] = self._count.get(key, 0) + 1
+
+    @property
+    def n_samp(self):
+        return self._n_samp
+
+    @n_samp.setter
+    def n_samp(self, n_samp):
+        # all up to date
+        assert len(set(self._count.values())) == 1
+        self._n_samp = n_samp
+        self.interp = self.interp
+        self._chunks = np.concatenate((np.arange(0, n_samp, 10000), [n_samp]))
+
+    @property
+    def interp(self):
+        return self._interp
+
+    @interp.setter
+    def interp(self, interp):
+        known_types = ('cos2', 'linear', 'zero', 'hann')
+        if interp not in known_types:
+            raise ValueError('interp must be one of %s, got "%s"'
+                             % (known_types, interp))
+        self._interp = interp
+        if self.n_samp is not None:
+            if self._interp == 'zero':
+                self._interpolators = None
+            else:
+                if self._interp == 'linear':
+                    interp = np.linspace(1, 0, self.n_samp, endpoint=False)
+                elif self._interp == 'cos2':
+                    interp = np.cos(0.5 * np.pi * np.arange(self.n_samp)) ** 2
+                else:  # interp == 'hann'
+                    interp = np.hanning(self.n_samp * 2 + 1)[self.n_samp:-1]
+                self._interpolators = np.array([interp, 1 - interp])
+
+    def interpolate(self, key, data, out, picks=None, data_idx=None):
+        """Interpolate."""
+        picks = slice(None) if picks is None else picks
+        # Process data in large chunks to save on memory
+        for start, stop in zip(self._chunks[:-1], self._chunks[1:]):
+            time_sl = slice(start, stop)
+            if data_idx is not None:
+                # This is useful e.g. when circularly accessing the same data.
+                # This prevents STC blowups in raw data simulation.
+                data_sl = data[:, data_idx[time_sl]]
+            else:
+                data_sl = data[:, time_sl]
+            this_data = np.dot(self._last[key], data_sl)
+            if self._interpolators is not None:
+                this_data *= self._interpolators[0][time_sl]
+            out[picks, time_sl] += this_data
+            if self._interpolators is not None:
+                this_data = np.dot(self._current[key], data_sl)
+                this_data *= self._interpolators[1][time_sl]
+                out[picks, time_sl] += this_data
+            del this_data
diff --git a/mne/fixes.py b/mne/fixes.py
index 7f98674..5f35b9e 100644
--- a/mne/fixes.py
+++ b/mne/fixes.py
@@ -15,11 +15,14 @@ at which the fix is no longer needed.
 from __future__ import division
 
 import inspect
+from distutils.version import LooseVersion
 import re
 import warnings
 
 import numpy as np
-from scipy import linalg
+from scipy import linalg, __version__ as sp_version
+
+from .externals.six import string_types, iteritems
 
 
 ###############################################################################
@@ -51,7 +54,7 @@ else:
 def _safe_svd(A, **kwargs):
     """Wrapper to get around the SVD did not converge error of death"""
     # Intel has a bug with their GESVD driver:
-    #     https://software.intel.com/en-us/forums/intel-distribution-for-python/topic/628049  # noqa
+    #     https://software.intel.com/en-us/forums/intel-distribution-for-python/topic/628049  # noqa: E501
     # For SciPy 0.18 and up, we can work around it by using
     # lapack_driver='gesvd' instead.
     if kwargs.get('overwrite_a', False):
@@ -69,7 +72,85 @@ def _safe_svd(A, **kwargs):
 
 
 ###############################################################################
-# Back porting scipy.signal.sosfilt (0.17) and sosfiltfilt (0.18)
+# Backporting nibabel's read_geometry
+
+def _get_read_geometry():
+    """Get the geometry reading function."""
+    try:
+        import nibabel as nib
+        has_nibabel = True
+    except ImportError:
+        has_nibabel = False
+    if has_nibabel and LooseVersion(nib.__version__) > LooseVersion('2.1.0'):
+        from nibabel.freesurfer import read_geometry
+    else:
+        read_geometry = _read_geometry
+    return read_geometry
+
+
+def _read_geometry(filepath, read_metadata=False, read_stamp=False):
+    """Backport from nibabel."""
+    from .surface import _fread3, _fread3_many
+    volume_info = dict()
+
+    TRIANGLE_MAGIC = 16777214
+    QUAD_MAGIC = 16777215
+    NEW_QUAD_MAGIC = 16777213
+    with open(filepath, "rb") as fobj:
+        magic = _fread3(fobj)
+        if magic in (QUAD_MAGIC, NEW_QUAD_MAGIC):  # Quad file
+            nvert = _fread3(fobj)
+            nquad = _fread3(fobj)
+            (fmt, div) = (">i2", 100.) if magic == QUAD_MAGIC else (">f4", 1.)
+            coords = np.fromfile(fobj, fmt, nvert * 3).astype(np.float) / div
+            coords = coords.reshape(-1, 3)
+            quads = _fread3_many(fobj, nquad * 4)
+            quads = quads.reshape(nquad, 4)
+            #
+            #   Face splitting follows
+            #
+            faces = np.zeros((2 * nquad, 3), dtype=np.int)
+            nface = 0
+            for quad in quads:
+                if (quad[0] % 2) == 0:
+                    faces[nface] = quad[0], quad[1], quad[3]
+                    nface += 1
+                    faces[nface] = quad[2], quad[3], quad[1]
+                    nface += 1
+                else:
+                    faces[nface] = quad[0], quad[1], quad[2]
+                    nface += 1
+                    faces[nface] = quad[0], quad[2], quad[3]
+                    nface += 1
+
+        elif magic == TRIANGLE_MAGIC:  # Triangle file
+            create_stamp = fobj.readline().rstrip(b'\n').decode('utf-8')
+            fobj.readline()
+            vnum = np.fromfile(fobj, ">i4", 1)[0]
+            fnum = np.fromfile(fobj, ">i4", 1)[0]
+            coords = np.fromfile(fobj, ">f4", vnum * 3).reshape(vnum, 3)
+            faces = np.fromfile(fobj, ">i4", fnum * 3).reshape(fnum, 3)
+
+            if read_metadata:
+                volume_info = _read_volume_info(fobj)
+        else:
+            raise ValueError("File does not appear to be a Freesurfer surface")
+
+    coords = coords.astype(np.float)  # XXX: due to mayavi bug on mac 32bits
+
+    ret = (coords, faces)
+    if read_metadata:
+        if len(volume_info) == 0:
+            warnings.warn('No volume information contained in the file')
+        ret += (volume_info,)
+    if read_stamp:
+        ret += (create_stamp,)
+
+    return ret
+
+
+###############################################################################
+# Backporting scipy.signal.sosfilt (0.17) and sosfiltfilt (0.18)
 
 
 def _sosfiltfilt(sos, x, axis=-1, padtype='odd', padlen=None):
@@ -279,6 +360,552 @@ def get_sosfiltfilt():
     return sosfiltfilt
 
 
+def minimum_phase(h):
+    """Convert a linear-phase FIR filter to minimum phase.
+
+    Parameters
+    ----------
+    h : array
+        Linear-phase FIR filter coefficients.
+
+    Returns
+    -------
+    h_minimum : array
+        The minimum-phase version of the filter, with length
+        ``(length(h) + 1) // 2``.
+    """
+    try:
+        from scipy.signal import minimum_phase
+    except Exception:
+        pass
+    else:
+        return minimum_phase(h)
+    from scipy.fftpack import fft, ifft
+    h = np.asarray(h)
+    if np.iscomplexobj(h):
+        raise ValueError('Complex filters not supported')
+    if h.ndim != 1 or h.size <= 2:
+        raise ValueError('h must be 1D and at least 2 samples long')
+    n_half = len(h) // 2
+    if not np.allclose(h[-n_half:][::-1], h[:n_half]):
+        warnings.warn('h does not appear to by symmetric, conversion may '
+                      'fail', RuntimeWarning)
+    n_fft = 2 ** int(np.ceil(np.log2(2 * (len(h) - 1) / 0.01)))
+    # zero-pad; calculate the DFT
+    h_temp = np.abs(fft(h, n_fft))
+    # take 0.25*log(|H|**2) = 0.5*log(|H|)
+    h_temp += 1e-7 * h_temp[h_temp > 0].min()  # don't let log blow up
+    np.log(h_temp, out=h_temp)
+    h_temp *= 0.5
+    # IDFT
+    h_temp = ifft(h_temp).real
+    # multiply pointwise by the homomorphic filter
+    # lmin[n] = 2u[n] - d[n]
+    win = np.zeros(n_fft)
+    win[0] = 1
+    stop = (len(h) + 1) // 2
+    win[1:stop] = 2
+    if len(h) % 2:
+        win[stop] = 1
+    h_temp *= win
+    h_temp = ifft(np.exp(fft(h_temp)))
+    h_minimum = h_temp.real
+    n_out = n_half + len(h) % 2
+    return h_minimum[:n_out]
+
+
+###############################################################################
+# scipy.special.sph_harm ()
+
+def _sph_harm(order, degree, az, pol):
+    """Evaluate point in specified multipolar moment.
+
+    When using, pay close attention to inputs. Spherical harmonic notation for
+    order/degree, and theta/phi are both reversed in original SSS work compared
+    to many other sources. See mathworld.wolfram.com/SphericalHarmonic.html for
+    more discussion.
+
+    Note that scipy has ``scipy.special.sph_harm``, but that function is
+    too slow on old versions (< 0.15) for heavy use.
+
+    Parameters
+    ----------
+    order : int
+        Order of spherical harmonic. (Usually) corresponds to 'm'.
+    degree : int
+        Degree of spherical harmonic. (Usually) corresponds to 'l'.
+    az : float
+        Azimuthal (longitudinal) spherical coordinate [0, 2*pi]. 0 is aligned
+        with x-axis.
+    pol : float
+        Polar (or colatitudinal) spherical coordinate [0, pi]. 0 is aligned
+        with z-axis.
+    norm : bool
+        If True, include normalization factor.
+
+    Returns
+    -------
+    base : complex float
+        The spherical harmonic value.
+    """
+    from scipy.special import lpmv
+    from .preprocessing.maxwell import _sph_harm_norm
+
+    # Error checks
+    if np.abs(order) > degree:
+        raise ValueError('Absolute value of order must be <= degree')
+    # Ensure that polar and azimuth angles are arrays
+    az = np.asarray(az)
+    pol = np.asarray(pol)
+    if (np.abs(az) > 2 * np.pi).any():
+        raise ValueError('Azimuth coords must lie in [-2*pi, 2*pi]')
+    if(pol < 0).any() or (pol > np.pi).any():
+        raise ValueError('Polar coords must lie in [0, pi]')
+    # This is the "seismology" convention on Wikipedia, w/o Condon-Shortley
+    sph = lpmv(order, degree, np.cos(pol)) * np.exp(1j * order * az)
+    sph *= _sph_harm_norm(order, degree)
+    return sph
+
+
+def _get_sph_harm():
+    """Helper to get a usable spherical harmonic function."""
+    if LooseVersion(sp_version) < LooseVersion('0.17.1'):
+        sph_harm = _sph_harm
+    else:
+        from scipy.special import sph_harm
+    return sph_harm
+
+
+###############################################################################
+# Scipy spectrogram (for mne.time_frequency.psd_welch) needed for scipy < 0.16
+
+def _spectrogram(x, fs=1.0, window=('tukey',.25), nperseg=256, noverlap=None,
+                nfft=None, detrend='constant', return_onesided=True,
+                scaling='density', axis=-1, mode='psd'):
+    """
+    Compute a spectrogram with consecutive Fourier transforms.
+    Spectrograms can be used as a way of visualizing the change of a
+    nonstationary signal's frequency content over time.
+
+    Parameters
+    ----------
+    x : array_like
+        Time series of measurement values
+    fs : float, optional
+        Sampling frequency of the `x` time series. Defaults to 1.0.
+    window : str or tuple or array_like, optional
+        Desired window to use. See `get_window` for a list of windows and
+        required parameters. If `window` is array_like it will be used
+        directly as the window and its length will be used for nperseg.
+        Defaults to a Tukey window with shape parameter of 0.25.
+    nperseg : int, optional
+        Length of each segment.  Defaults to 256.
+    noverlap : int, optional
+        Number of points to overlap between segments. If None,
+        ``noverlap = nperseg // 8``.  Defaults to None.
+    nfft : int, optional
+        Length of the FFT used, if a zero padded FFT is desired.  If None,
+        the FFT length is `nperseg`. Defaults to None.
+    detrend : str or function or False, optional
+        Specifies how to detrend each segment. If `detrend` is a string,
+        it is passed as the ``type`` argument to `detrend`.  If it is a
+        function, it takes a segment and returns a detrended segment.
+        If `detrend` is False, no detrending is done.  Defaults to 'constant'.
+    return_onesided : bool, optional
+        If True, return a one-sided spectrum for real data. If False return
+        a two-sided spectrum. Note that for complex data, a two-sided
+        spectrum is always returned.
+    scaling : { 'density', 'spectrum' }, optional
+        Selects between computing the power spectral density ('density')
+        where `Pxx` has units of V**2/Hz and computing the power spectrum
+        ('spectrum') where `Pxx` has units of V**2, if `x` is measured in V
+        and fs is measured in Hz.  Defaults to 'density'
+    axis : int, optional
+        Axis along which the spectrogram is computed; the default is over
+        the last axis (i.e. ``axis=-1``).
+    mode : str, optional
+        Defines what kind of return values are expected. Options are ['psd',
+        'complex', 'magnitude', 'angle', 'phase'].
+
+    Returns
+    -------
+    f : ndarray
+        Array of sample frequencies.
+    t : ndarray
+        Array of segment times.
+    Sxx : ndarray
+        Spectrogram of x. By default, the last axis of Sxx corresponds to the
+        segment times.
+
+    See Also
+    --------
+    periodogram: Simple, optionally modified periodogram
+    lombscargle: Lomb-Scargle periodogram for unevenly sampled data
+    welch: Power spectral density by Welch's method.
+    csd: Cross spectral density by Welch's method.
+
+    Notes
+    -----
+    An appropriate amount of overlap will depend on the choice of window
+    and on your requirements. In contrast to welch's method, where the entire
+    data stream is averaged over, one may wish to use a smaller overlap (or
+    perhaps none at all) when computing a spectrogram, to maintain some
+    statistical independence between individual segments.
+    .. versionadded:: 0.16.0
+
+    References
+    ----------
+    .. [1] Oppenheim, Alan V., Ronald W. Schafer, John R. Buck "Discrete-Time
+           Signal Processing", Prentice Hall, 1999.
+    """
+    # Less overlap than welch, so samples are more statisically independent
+    if noverlap is None:
+        noverlap = nperseg // 8
+
+    freqs, time, Pxy = _spectral_helper(x, x, fs, window, nperseg, noverlap,
+                                        nfft, detrend, return_onesided, scaling,
+                                        axis, mode=mode)
+
+    return freqs, time, Pxy
+
+
+def _spectral_helper(x, y, fs=1.0, window='hann', nperseg=256,
+                    noverlap=None, nfft=None, detrend='constant',
+                    return_onesided=True, scaling='spectrum', axis=-1,
+                    mode='psd'):
+    """
+    Calculate various forms of windowed FFTs for PSD, CSD, etc.
+    This is a helper function that implements the commonality between the
+    psd, csd, and spectrogram functions. It is not designed to be called
+    externally. The windows are not averaged over; the result from each window
+    is returned.
+
+    Parameters
+    ---------
+    x : array_like
+        Array or sequence containing the data to be analyzed.
+    y : array_like
+        Array or sequence containing the data to be analyzed. If this is
+        the same object in memoery as x (i.e. _spectral_helper(x, x, ...)),
+        the extra computations are spared.
+    fs : float, optional
+        Sampling frequency of the time series. Defaults to 1.0.
+    window : str or tuple or array_like, optional
+        Desired window to use. See `get_window` for a list of windows and
+        required parameters. If `window` is array_like it will be used
+        directly as the window and its length will be used for nperseg.
+        Defaults to 'hann'.
+    nperseg : int, optional
+        Length of each segment.  Defaults to 256.
+    noverlap : int, optional
+        Number of points to overlap between segments. If None,
+        ``noverlap = nperseg // 2``.  Defaults to None.
+    nfft : int, optional
+        Length of the FFT used, if a zero padded FFT is desired.  If None,
+        the FFT length is `nperseg`. Defaults to None.
+    detrend : str or function or False, optional
+        Specifies how to detrend each segment. If `detrend` is a string,
+        it is passed as the ``type`` argument to `detrend`.  If it is a
+        function, it takes a segment and returns a detrended segment.
+        If `detrend` is False, no detrending is done.  Defaults to 'constant'.
+    return_onesided : bool, optional
+        If True, return a one-sided spectrum for real data. If False return
+        a two-sided spectrum. Note that for complex data, a two-sided
+        spectrum is always returned.
+    scaling : { 'density', 'spectrum' }, optional
+        Selects between computing the cross spectral density ('density')
+        where `Pxy` has units of V**2/Hz and computing the cross spectrum
+        ('spectrum') where `Pxy` has units of V**2, if `x` and `y` are
+        measured in V and fs is measured in Hz.  Defaults to 'density'
+    axis : int, optional
+        Axis along which the periodogram is computed; the default is over
+        the last axis (i.e. ``axis=-1``).
+    mode : str, optional
+        Defines what kind of return values are expected. Options are ['psd',
+        'complex', 'magnitude', 'angle', 'phase'].
+
+    Returns
+    -------
+    freqs : ndarray
+        Array of sample frequencies.
+    t : ndarray
+        Array of times corresponding to each data segment
+    result : ndarray
+        Array of output data, contents dependent on *mode* kwarg.
+
+    References
+    ----------
+    .. [1] Stack Overflow, "Rolling window for 1D arrays in Numpy?",
+        http://stackoverflow.com/a/6811241
+    .. [2] Stack Overflow, "Using strides for an efficient moving average
+        filter", http://stackoverflow.com/a/4947453
+
+    Notes
+    -----
+    Adapted from matplotlib.mlab
+    .. versionadded:: 0.16.0
+    """
+    from scipy import fftpack
+    from scipy.signal import signaltools
+    from scipy.signal.windows import get_window
+
+    if mode not in ['psd', 'complex', 'magnitude', 'angle', 'phase']:
+        raise ValueError("Unknown value for mode %s, must be one of: "
+                         "'default', 'psd', 'complex', "
+                         "'magnitude', 'angle', 'phase'" % mode)
+
+    # If x and y are the same object we can save ourselves some computation.
+    same_data = y is x
+
+    if not same_data and mode != 'psd':
+        raise ValueError("x and y must be equal if mode is not 'psd'")
+
+    axis = int(axis)
+
+    # Ensure we have np.arrays, get outdtype
+    x = np.asarray(x)
+    if not same_data:
+        y = np.asarray(y)
+        outdtype = np.result_type(x,y,np.complex64)
+    else:
+        outdtype = np.result_type(x,np.complex64)
+
+    if not same_data:
+        # Check if we can broadcast the outer axes together
+        xouter = list(x.shape)
+        youter = list(y.shape)
+        xouter.pop(axis)
+        youter.pop(axis)
+        try:
+            outershape = np.broadcast(np.empty(xouter), np.empty(youter)).shape
+        except ValueError:
+            raise ValueError('x and y cannot be broadcast together.')
+
+    if same_data:
+        if x.size == 0:
+            return np.empty(x.shape), np.empty(x.shape), np.empty(x.shape)
+    else:
+        if x.size == 0 or y.size == 0:
+            outshape = outershape + (min([x.shape[axis], y.shape[axis]]),)
+            emptyout = np.rollaxis(np.empty(outshape), -1, axis)
+            return emptyout, emptyout, emptyout
+
+    if x.ndim > 1:
+        if axis != -1:
+            x = np.rollaxis(x, axis, len(x.shape))
+            if not same_data and y.ndim > 1:
+                y = np.rollaxis(y, axis, len(y.shape))
+
+    # Check if x and y are the same length, zero-pad if necessary
+    if not same_data:
+        if x.shape[-1] != y.shape[-1]:
+            if x.shape[-1] < y.shape[-1]:
+                pad_shape = list(x.shape)
+                pad_shape[-1] = y.shape[-1] - x.shape[-1]
+                x = np.concatenate((x, np.zeros(pad_shape)), -1)
+            else:
+                pad_shape = list(y.shape)
+                pad_shape[-1] = x.shape[-1] - y.shape[-1]
+                y = np.concatenate((y, np.zeros(pad_shape)), -1)
+
+    # X and Y are same length now, can test nperseg with either
+    if x.shape[-1] < nperseg:
+        warnings.warn('nperseg = {0:d}, is greater than input length = {1:d}, '
+                      'using nperseg = {1:d}'.format(nperseg, x.shape[-1]))
+        nperseg = x.shape[-1]
+
+    nperseg = int(nperseg)
+    if nperseg < 1:
+        raise ValueError('nperseg must be a positive integer')
+
+    if nfft is None:
+        nfft = nperseg
+    elif nfft < nperseg:
+        raise ValueError('nfft must be greater than or equal to nperseg.')
+    else:
+        nfft = int(nfft)
+
+    if noverlap is None:
+        noverlap = nperseg//2
+    elif noverlap >= nperseg:
+        raise ValueError('noverlap must be less than nperseg.')
+    else:
+        noverlap = int(noverlap)
+
+    # Handle detrending and window functions
+    if not detrend:
+        def detrend_func(d):
+            return d
+    elif not hasattr(detrend, '__call__'):
+        def detrend_func(d):
+            return signaltools.detrend(d, type=detrend, axis=-1)
+    elif axis != -1:
+        # Wrap this function so that it receives a shape that it could
+        # reasonably expect to receive.
+        def detrend_func(d):
+            d = np.rollaxis(d, -1, axis)
+            d = detrend(d)
+            return np.rollaxis(d, axis, len(d.shape))
+    else:
+        detrend_func = detrend
+
+    if isinstance(window, string_types) or type(window) is tuple:
+        win = get_window(window, nperseg)
+    else:
+        win = np.asarray(window)
+        if len(win.shape) != 1:
+            raise ValueError('window must be 1-D')
+        if win.shape[0] != nperseg:
+            raise ValueError('window must have length of nperseg')
+
+    if np.result_type(win,np.complex64) != outdtype:
+        win = win.astype(outdtype)
+
+    if mode == 'psd':
+        if scaling == 'density':
+            scale = 1.0 / (fs * (win*win).sum())
+        elif scaling == 'spectrum':
+            scale = 1.0 / win.sum()**2
+        else:
+            raise ValueError('Unknown scaling: %r' % scaling)
+    else:
+        scale = 1
+
+    if return_onesided is True:
+        if np.iscomplexobj(x):
+            sides = 'twosided'
+        else:
+            sides = 'onesided'
+            if not same_data:
+                if np.iscomplexobj(y):
+                    sides = 'twosided'
+    else:
+        sides = 'twosided'
+
+    if sides == 'twosided':
+        num_freqs = nfft
+    elif sides == 'onesided':
+        if nfft % 2:
+            num_freqs = (nfft + 1)//2
+        else:
+            num_freqs = nfft//2 + 1
+
+    # Perform the windowed FFTs
+    result = _fft_helper(x, win, detrend_func, nperseg, noverlap, nfft)
+    result = result[..., :num_freqs]
+    freqs = fftpack.fftfreq(nfft, 1/fs)[:num_freqs]
+
+    if not same_data:
+        # All the same operations on the y data
+        result_y = _fft_helper(y, win, detrend_func, nperseg, noverlap, nfft)
+        result_y = result_y[..., :num_freqs]
+        result = np.conjugate(result) * result_y
+    elif mode == 'psd':
+        result = np.conjugate(result) * result
+    elif mode == 'magnitude':
+        result = np.absolute(result)
+    elif mode == 'angle' or mode == 'phase':
+        result = np.angle(result)
+    elif mode == 'complex':
+        pass
+
+    result *= scale
+    if sides == 'onesided':
+        if nfft % 2:
+            result[...,1:] *= 2
+        else:
+            # Last point is unpaired Nyquist freq point, don't double
+            result[...,1:-1] *= 2
+
+    t = np.arange(nperseg/2, x.shape[-1] - nperseg/2 + 1, nperseg - noverlap)/float(fs)
+
+    if sides != 'twosided' and not nfft % 2:
+        # get the last value correctly, it is negative otherwise
+        freqs[-1] *= -1
+
+    # we unwrap the phase here to handle the onesided vs. twosided case
+    if mode == 'phase':
+        result = np.unwrap(result, axis=-1)
+
+    result = result.astype(outdtype)
+
+    # All imaginary parts are zero anyways
+    if same_data and mode != 'complex':
+        result = result.real
+
+    # Output is going to have new last axis for window index
+    if axis != -1:
+        # Specify as positive axis index
+        if axis < 0:
+            axis = len(result.shape)-1-axis
+
+        # Roll frequency axis back to axis where the data came from
+        result = np.rollaxis(result, -1, axis)
+    else:
+        # Make sure window/time index is last axis
+        result = np.rollaxis(result, -1, -2)
+
+    return freqs, t, result
+
+
+def _fft_helper(x, win, detrend_func, nperseg, noverlap, nfft):
+    """
+    Calculate windowed FFT, for internal use by scipy.signal._spectral_helper
+    This is a helper function that does the main FFT calculation for
+    _spectral helper. All input valdiation is performed there, and the data
+    axis is assumed to be the last axis of x. It is not designed to be called
+    externally. The windows are not averaged over; the result from each window
+    is returned.
+
+    Returns
+    -------
+    result : ndarray
+        Array of FFT data
+
+    References
+    ----------
+    .. [1] Stack Overflow, "Repeat NumPy array without replicating data?",
+        http://stackoverflow.com/a/5568169
+
+    Notes
+    -----
+    Adapted from matplotlib.mlab
+    .. versionadded:: 0.16.0
+    """
+    from scipy import fftpack
+
+    # Created strided array of data segments
+    if nperseg == 1 and noverlap == 0:
+        result = x[..., np.newaxis]
+    else:
+        step = nperseg - noverlap
+        shape = x.shape[:-1]+((x.shape[-1]-noverlap)//step, nperseg)
+        strides = x.strides[:-1]+(step*x.strides[-1], x.strides[-1])
+        result = np.lib.stride_tricks.as_strided(x, shape=shape,
+                                                 strides=strides)
+
+    # Detrend each data segment individually
+    result = detrend_func(result)
+
+    # Apply window by multiplication
+    result = win * result
+
+    # Perform the fft. Acts on last axis by default. Zero-pads automatically
+    result = fftpack.fft(result, n=nfft)
+
+    return result
+
+
+def get_spectrogram():
+    '''helper function to get relevant spectrogram'''
+    from .utils import check_version
+    if check_version('scipy', '0.16.0'):
+        from scipy.signal import spectrogram
+    else:
+        spectrogram = _spectrogram
+    return spectrogram
+
+
 ###############################################################################
 # Misc utilities
 
@@ -431,3 +1058,164 @@ def _serialize_volume_info(volume_info):
             strings.append('{0} = {1:0.10g} {2:0.10g} {3:0.10g}\n'.format(
                 key.ljust(6), val[0], val[1], val[2]).encode('utf-8'))
     return b''.join(strings)
+
+
+##############################################################################
+# adapted from scikit-learn
+
+
+def is_classifier(estimator):
+    """Returns True if the given estimator is (probably) a classifier.
+
+    Parameters
+    ----------
+    estimator : object
+        Estimator object to test.
+
+    Returns
+    -------
+    out : bool
+        True if estimator is a classifier and False otherwise.
+    """
+    return getattr(estimator, "_estimator_type", None) == "classifier"
+
+
+def is_regressor(estimator):
+    """Returns True if the given estimator is (probably) a regressor.
+
+    Parameters
+    ----------
+    estimator : object
+        Estimator object to test.
+
+    Returns
+    -------
+    out : bool
+        True if estimator is a regressor and False otherwise.
+    """
+    return getattr(estimator, "_estimator_type", None) == "regressor"
+
+
+class BaseEstimator(object):
+    """Base class for all estimators in scikit-learn
+
+    Notes
+    -----
+    All estimators should specify all the parameters that can be set
+    at the class level in their ``__init__`` as explicit keyword
+    arguments (no ``*args`` or ``**kwargs``).
+    """
+
+    @classmethod
+    def _get_param_names(cls):
+        """Get parameter names for the estimator"""
+        try:
+            from inspect import signature
+        except ImportError:
+            from .externals.funcsigs import signature
+        # fetch the constructor or the original constructor before
+        # deprecation wrapping if any
+        init = getattr(cls.__init__, 'deprecated_original', cls.__init__)
+        if init is object.__init__:
+            # No explicit constructor to introspect
+            return []
+
+        # introspect the constructor arguments to find the model parameters
+        # to represent
+        init_signature = signature(init)
+        # Consider the constructor parameters excluding 'self'
+        parameters = [p for p in init_signature.parameters.values()
+                      if p.name != 'self' and p.kind != p.VAR_KEYWORD]
+        for p in parameters:
+            if p.kind == p.VAR_POSITIONAL:
+                raise RuntimeError("scikit-learn estimators should always "
+                                   "specify their parameters in the signature"
+                                   " of their __init__ (no varargs)."
+                                   " %s with constructor %s doesn't "
+                                   " follow this convention."
+                                   % (cls, init_signature))
+        # Extract and sort argument names excluding 'self'
+        return sorted([p.name for p in parameters])
+
+    def get_params(self, deep=True):
+        """Get parameters for this estimator.
+
+        Parameters
+        ----------
+        deep : boolean, optional
+            If True, will return the parameters for this estimator and
+            contained subobjects that are estimators.
+
+        Returns
+        -------
+        params : mapping of string to any
+            Parameter names mapped to their values.
+        """
+        out = dict()
+        for key in self._get_param_names():
+            # We need deprecation warnings to always be on in order to
+            # catch deprecated param values.
+            # This is set in utils/__init__.py but it gets overwritten
+            # when running under python3 somehow.
+            warnings.simplefilter("always", DeprecationWarning)
+            try:
+                with warnings.catch_warnings(record=True) as w:
+                    value = getattr(self, key, None)
+                if len(w) and w[0].category == DeprecationWarning:
+                    # if the parameter is deprecated, don't show it
+                    continue
+            finally:
+                warnings.filters.pop(0)
+
+            # XXX: should we rather test if instance of estimator?
+            if deep and hasattr(value, 'get_params'):
+                deep_items = value.get_params().items()
+                out.update((key + '__' + k, val) for k, val in deep_items)
+            out[key] = value
+        return out
+
+    def set_params(self, **params):
+        """Set the parameters of this estimator.
+        The method works on simple estimators as well as on nested objects
+        (such as pipelines). The latter have parameters of the form
+        ``<component>__<parameter>`` so that it's possible to update each
+        component of a nested object.
+        Returns
+        -------
+        self
+        """
+        if not params:
+            # Simple optimisation to gain speed (inspect is slow)
+            return self
+        valid_params = self.get_params(deep=True)
+        for key, value in iteritems(params):
+            split = key.split('__', 1)
+            if len(split) > 1:
+                # nested objects case
+                name, sub_name = split
+                if name not in valid_params:
+                    raise ValueError('Invalid parameter %s for estimator %s. '
+                                     'Check the list of available parameters '
+                                     'with `estimator.get_params().keys()`.' %
+                                     (name, self))
+                sub_object = valid_params[name]
+                sub_object.set_params(**{sub_name: value})
+            else:
+                # simple objects case
+                if key not in valid_params:
+                    raise ValueError('Invalid parameter %s for estimator %s. '
+                                     'Check the list of available parameters '
+                                     'with `estimator.get_params().keys()`.' %
+                                     (key, self.__class__.__name__))
+                setattr(self, key, value)
+        return self
+
+    def __repr__(self):
+        from sklearn.base import _pprint
+        class_name = self.__class__.__name__
+        return '%s(%s)' % (class_name, _pprint(self.get_params(deep=False),
+                                               offset=len(class_name),),)
+
+    # __getstate__ and __setstate__ are omitted because they only contain
+    # conditionals that are not satisfied by our objects (e.g.,
+    # ``if type(self).__module__.startswith('sklearn.')``.
diff --git a/mne/forward/__init__.py b/mne/forward/__init__.py
index c413e39..0d2ccd1 100644
--- a/mne/forward/__init__.py
+++ b/mne/forward/__init__.py
@@ -1,3 +1,5 @@
+"""Forward modeling code."""
+
 from .forward import (Forward, read_forward_solution, write_forward_solution,
                       is_fixed_orient, _read_forward_meas_info,
                       write_forward_meas_info,
@@ -8,8 +10,7 @@ from .forward import (Forward, read_forward_solution, write_forward_solution,
                       _restrict_gain_matrix, _stc_src_sel,
                       _fill_measurement_info, _apply_forward,
                       _subject_from_forward, convert_forward_solution,
-                      _to_fixed_ori, prepare_bem_model, _merge_meg_eeg_fwds,
-                      _do_forward_solution)
+                      _merge_meg_eeg_fwds, _do_forward_solution)
 from ._make_forward import (make_forward_solution, _prepare_for_forward,
                             _prep_meg_channels, _prep_eeg_channels,
                             _to_forward_dict, _create_meg_coils,
diff --git a/mne/forward/_compute_forward.py b/mne/forward/_compute_forward.py
index 843e79c..592a312 100644
--- a/mne/forward/_compute_forward.py
+++ b/mne/forward/_compute_forward.py
@@ -16,11 +16,10 @@
 import numpy as np
 from copy import deepcopy
 
-from ..surface import (fast_cross_3d, _find_nearest_tri_pt, _get_tri_supp_geom,
-                       _triangle_coords)
+from ..surface import fast_cross_3d, _project_onto_surface
 from ..io.constants import FIFF
 from ..transforms import apply_trans
-from ..utils import logger, verbose
+from ..utils import logger, verbose, _pl
 from ..parallel import parallel_func
 from ..io.compensator import get_current_comp, make_compensator
 from ..io.pick import pick_types
@@ -152,7 +151,7 @@ def _do_lin_field_coeff(bem_rr, tris, tn, ta, rmags, cosmags, ws, bins):
 
 
 def _concatenate_coils(coils):
-    """Helper to concatenate MEG coil parameters."""
+    """Concatenate MEG coil parameters."""
     rmags = np.concatenate([coil['rmag'] for coil in coils])
     cosmags = np.concatenate([coil['cosmag'] for coil in coils])
     ws = np.concatenate([coil['w'] for coil in coils])
@@ -234,27 +233,20 @@ def _bem_specify_els(bem, els, mults):
     """
     sol = np.zeros((len(els), bem['solution'].shape[1]))
     scalp = bem['surfs'][0]
-    # Get supplementary geometry information for tris and rr
-    scalp['geom'] = _get_tri_supp_geom(scalp['tris'], scalp['rr'])
-    inds = np.arange(len(scalp['tris']))  # Inds of every BEM vertex
-
-    # Iterate over all electrodes
-    # In principle this could be parallelized, but pickling overhead is huge
-    # (makes it slower than non-parallel)
-    for k, el in enumerate(els):
-        # Get electrode and reference position in head coords
-        el_r = apply_trans(bem['head_mri_t']['trans'], el['rmag'])
-        # Iterate over all integration points
-        for elw, r in zip(el['w'], el_r):
-            # Get index of closest tri on scalp BEM to electrode position
-            best = _find_nearest_tri_pt(inds, r, scalp['geom'], True)[2]
-            # Calculate a linear interpolation between the vertex values
-            tri = scalp['tris'][best]  # Get 3 vertex indices of closest tri
-            # Get coords of pt projected onto closest triangle
-            x, y, z = _triangle_coords(r, scalp['geom'], best)
-            w = elw * np.array([(1.0 - x - y), x, y])
-            amt = np.dot(w, bem['solution'][tri])
-            sol[k] += amt
+
+    # Operate on all integration points for all electrodes (in MRI coords)
+    rrs = np.concatenate([apply_trans(bem['head_mri_t']['trans'], el['rmag'])
+                          for el in els], axis=0)
+    ws = np.concatenate([el['w'] for el in els])
+    tri_weights, tri_idx = _project_onto_surface(rrs, scalp)
+    tri_weights *= ws
+    weights = np.einsum('ij,jik->jk', tri_weights,
+                        bem['solution'][scalp['tris'][tri_idx]])
+    # there are way more vertices than electrodes generally, so let's iterate
+    # over the electrodes
+    edges = np.concatenate([[0], np.cumsum([len(el['w']) for el in els])])
+    for ii, (start, stop) in enumerate(zip(edges[:-1], edges[1:])):
+        sol[ii] = weights[start:stop].sum(0)
     sol *= mults
     return sol
 
@@ -347,8 +339,9 @@ def _bem_inf_pots(mri_rr, bem_rr, mri_Q=None):
 
 
 def _bem_inf_fields(rr, rmag, cosmag):
-    """Compute infinite-medium magnetic field at one MEG sensor from all
-    dipoles in all 3 basis directions.
+    """Compute infinite-medium magnetic field at one MEG sensor.
+
+    This operates on all dipoles in all 3 basis directions.
 
     Parameters
     ----------
@@ -481,7 +474,6 @@ def _do_inf_pots(mri_rr, bem_rr, mri_Q, sol):
     B : ndarray, (n_dipoles * 3, n_sensors)
         Forward solution for sensors due to volume currents
     """
-
     # Doing work of 'fwd_bem_pot_calc' in MNE-C
     # The following code is equivalent to this, but saves memory
     # v0s = _bem_inf_pots(rr, bem_rr, Q)  # n_rr x 3 x n_bem_rr
@@ -685,9 +677,9 @@ def _prep_field_computation(rr, bem, fwd_data, n_jobs, verbose=None):
     n_jobs : int
         Number of jobs to run in parallel
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose)
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
     """
-
     bem_rr = mults = mri_Q = head_mri_t = None
     if not bem['is_sphere']:
         if bem['bem_method'] != FIFF.FWD_BEM_LINEAR_COLL:
@@ -770,7 +762,8 @@ def _compute_forwards_meeg(rr, fd, n_jobs, verbose=None):
     n_jobs : int
         Number of jobs to run in parallel
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose)
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -778,7 +771,6 @@ def _compute_forwards_meeg(rr, fd, n_jobs, verbose=None):
         Each element contains ndarray, shape (3 * n_dipoles, n_sensors) where
         n_sensors depends on which channel types are requested (MEG and/or EEG)
     """
-
     n_jobs = max(min(n_jobs, len(rr)), 1)
     Bs = list()
     # The dipole location and orientation must be transformed to mri coords
@@ -799,8 +791,7 @@ def _compute_forwards_meeg(rr, fd, n_jobs, verbose=None):
         # Do the actual forward calculation for a list MEG/EEG sensors
         logger.info('Computing %s at %d source location%s '
                     '(free orientations)...'
-                    % (coil_type.upper(), len(rr),
-                       '' if len(rr) == 1 else 's'))
+                    % (coil_type.upper(), len(rr), _pl(rr)))
         # Calculate forward solution using spherical or BEM model
         B = fun(rr, mri_rr, mri_Q, coils, solution, bem_rr, n_jobs,
                 coil_type)
@@ -852,7 +843,6 @@ def _compute_forwards(rr, bem, coils_list, ccoils_list, infos, coil_types,
         Each element contains ndarray, shape (3 * n_dipoles, n_sensors) where
         n_sensors depends on which channel types are requested (MEG and/or EEG)
     """
-
     # Split calculation into two steps to save (potentially) a lot of time
     # when e.g. dipole fitting
     fwd_data = dict(coils_list=coils_list, ccoils_list=ccoils_list,
diff --git a/mne/forward/_field_interpolation.py b/mne/forward/_field_interpolation.py
index 71e868f..be586fd 100644
--- a/mne/forward/_field_interpolation.py
+++ b/mne/forward/_field_interpolation.py
@@ -1,7 +1,6 @@
 # -*- coding: utf-8 -*-
 
 from copy import deepcopy
-from functools import partial
 
 import numpy as np
 from scipy import linalg
@@ -12,16 +11,18 @@ from ..io.pick import pick_types, pick_info
 from ..surface import get_head_surf, get_meg_helmet_surf
 
 from ..io.proj import _has_eeg_average_ref_proj, make_projector
-from ..transforms import transform_surface_to, read_trans, _find_trans
+from ..transforms import (transform_surface_to, read_trans, _find_trans,
+                          _ensure_trans)
 from ._make_forward import _create_meg_coils, _create_eeg_els, _read_coil_defs
 from ._lead_dots import (_do_self_dots, _do_surface_dots, _get_legen_table,
-                         _get_legen_lut_fast, _get_legen_lut_accurate,
                          _do_cross_dots)
 from ..parallel import check_n_jobs
 from ..utils import logger, verbose
+from ..externals.six import string_types
 
 
 def _is_axial_coil(coil):
+    """Determine if the coil is axial."""
     is_ax = coil['coil_class'] in (FIFF.FWD_COILC_MAG,
                                    FIFF.FWD_COILC_AXIAL_GRAD,
                                    FIFF.FWD_COILC_AXIAL_GRAD2)
@@ -29,6 +30,8 @@ def _is_axial_coil(coil):
 
 
 def _ad_hoc_noise(coils, ch_type='meg'):
+    """Create ad-hoc noise covariance."""
+    # XXX should de-duplicate with make_ad_hoc_cov
     v = np.empty(len(coils))
     if ch_type == 'meg':
         axs = np.array([_is_axial_coil(coil) for coil in coils], dtype=bool)
@@ -41,24 +44,18 @@ def _ad_hoc_noise(coils, ch_type='meg'):
 
 
 def _setup_dots(mode, coils, ch_type):
-    """Setup dot products"""
+    """Set up dot products."""
+    from scipy.interpolate import interp1d
     int_rad = 0.06
     noise = _ad_hoc_noise(coils, ch_type)
-    if mode == 'fast':
-        # Use 50 coefficients with nearest-neighbor interpolation
-        n_coeff = 50
-        lut_fun = _get_legen_lut_fast
-    else:  # 'accurate'
-        # Use 100 coefficients with linear interpolation
-        n_coeff = 100
-        lut_fun = _get_legen_lut_accurate
+    n_coeff, interp = (50, 'nearest') if mode == 'fast' else (100, 'linear')
     lut, n_fact = _get_legen_table(ch_type, False, n_coeff, verbose=False)
-    lut_fun = partial(lut_fun, lut=lut)
+    lut_fun = interp1d(np.linspace(-1, 1, lut.shape[0]), lut, interp, axis=0)
     return int_rad, noise, lut_fun, n_fact
 
 
 def _compute_mapping_matrix(fmd, info):
-    """Do the hairy computations"""
+    """Do the hairy computations."""
     logger.info('    Preparing the mapping matrix...')
     # assemble a projector and apply it to the data
     ch_names = fmd['ch_names']
@@ -81,8 +78,10 @@ def _compute_mapping_matrix(fmd, info):
     # Eigenvalue truncation
     sumk = np.cumsum(sing)
     sumk /= sumk[-1]
-    fmd['nest'] = np.where(sumk > (1.0 - fmd['miss']))[0][0]
-    logger.info('    [Truncate at %d missing %g]' % (fmd['nest'], fmd['miss']))
+    fmd['nest'] = np.where(sumk > (1.0 - fmd['miss']))[0][0] + 1
+    logger.info('    Truncating at %d/%d components to omit less than %g '
+                '(%0.2g)' % (fmd['nest'], len(sing), fmd['miss'],
+                             1. - sumk[fmd['nest'] - 1]))
     sing = 1.0 / sing[:fmd['nest']]
 
     # Put the inverse together
@@ -211,7 +210,7 @@ def _as_meg_type_evoked(evoked, ch_type='grad', mode='fast'):
 
     # change channel names to emphasize they contain interpolated data
     for ch in evoked.info['chs']:
-        ch['ch_name'] += '_virtual'
+        ch['ch_name'] += '_v'
     evoked.info._update_redundant()
     evoked.info._check_consistency()
     return evoked
@@ -220,7 +219,7 @@ def _as_meg_type_evoked(evoked, ch_type='grad', mode='fast'):
 @verbose
 def _make_surface_mapping(info, surf, ch_type='meg', trans=None, mode='fast',
                           n_jobs=1, origin=(0., 0., 0.04), verbose=None):
-    """Re-map M/EEG data to a surface
+    """Re-map M/EEG data to a surface.
 
     Parameters
     ----------
@@ -245,7 +244,8 @@ def _make_surface_mapping(info, surf, ch_type='meg', trans=None, mode='fast',
         coords and in meters. The default is ``'auto'``, which means
         a head-digitization-based origin fit.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -333,7 +333,7 @@ def _make_surface_mapping(info, surf, ch_type='meg', trans=None, mode='fast',
 def make_field_map(evoked, trans='auto', subject=None, subjects_dir=None,
                    ch_type=None, mode='fast', meg_surf='helmet',
                    origin=(0., 0., 0.04), n_jobs=1, verbose=None):
-    """Compute surface maps used for field display in 3D
+    """Compute surface maps used for field display in 3D.
 
     Parameters
     ----------
@@ -362,19 +362,19 @@ def make_field_map(evoked, trans='auto', subject=None, subjects_dir=None,
         to compute the MEG field map. The default value is ``'helmet'``
     origin : array-like, shape (3,) | str
         Origin of internal and external multipolar moment space in head
-        coords and in meters. The default is ``'auto'``, which means
-        a head-digitization-based origin fit.
+        coords and in meters. Can be ``'auto'``, which means
+        a head-digitization-based origin fit. Default is ``(0., 0., 0.04)``.
 
         .. versionadded:: 0.11
 
     n_jobs : int
         The number of jobs to run in parallel.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
         .. versionadded:: 0.11
 
-
     Returns
     -------
     surf_maps : list
@@ -403,7 +403,9 @@ def make_field_map(evoked, trans='auto', subject=None, subjects_dir=None,
         raise RuntimeError('No data available for mapping.')
 
     if trans is not None:
-        trans = read_trans(trans)
+        if isinstance(trans, string_types):
+            trans = read_trans(trans)
+        trans = _ensure_trans(trans, 'head', 'mri')
 
     if meg_surf not in ['helmet', 'head']:
         raise ValueError('Surface to plot MEG fields must be '
diff --git a/mne/forward/_lead_dots.py b/mne/forward/_lead_dots.py
index 05fcba8..2817238 100644
--- a/mne/forward/_lead_dots.py
+++ b/mne/forward/_lead_dots.py
@@ -18,7 +18,7 @@ from ..utils import logger, verbose, _get_extra_data_path
 # FAST LEGENDRE (DERIVATIVE) POLYNOMIALS USING LOOKUP TABLE
 
 def _next_legen_der(n, x, p0, p01, p0d, p0dd):
-    """Compute the next Legendre polynomial and its derivatives"""
+    """Compute the next Legendre polynomial and its derivatives."""
     # only good for n > 1 !
     help_ = p0
     helpd = p0d
@@ -30,12 +30,12 @@ def _next_legen_der(n, x, p0, p01, p0d, p0dd):
 
 
 def _get_legen(x, n_coeff=100):
-    """Get Legendre polynomials expanded about x"""
+    """Get Legendre polynomials expanded about x."""
     return legendre.legvander(x, n_coeff - 1)
 
 
 def _get_legen_der(xx, n_coeff=100):
-    """Get Legendre polynomial derivatives expanded about x"""
+    """Get Legendre polynomial derivatives expanded about x."""
     coeffs = np.empty((len(xx), n_coeff, 3))
     for c, x in zip(coeffs, xx):
         p0s, p0ds, p0dds = c[:, 0], c[:, 1], c[:, 2]
@@ -51,7 +51,7 @@ def _get_legen_der(xx, n_coeff=100):
 @verbose
 def _get_legen_table(ch_type, volume_integral=False, n_coeff=100,
                      n_interp=20000, force_calc=False, verbose=None):
-    """Return a (generated) LUT of Legendre (derivative) polynomial coeffs"""
+    """Return a (generated) LUT of Legendre (derivative) polynomial coeffs."""
     if n_interp % 2 != 0:
         raise RuntimeError('n_interp must be even')
     fname = op.join(_get_extra_data_path(), 'tables')
@@ -69,9 +69,8 @@ def _get_legen_table(ch_type, volume_integral=False, n_coeff=100,
         extra_str = ''
         lut_shape = (n_interp + 1, n_coeff)
     if not op.isfile(fname) or force_calc:
-        n_out = (n_interp // 2)
         logger.info('Generating Legendre%s table...' % extra_str)
-        x_interp = np.arange(-n_out, n_out + 1, dtype=np.float64) / n_out
+        x_interp = np.linspace(-1, 1, n_interp + 1)
         lut = leg_fun(x_interp, n_coeff).astype(np.float32)
         if not force_calc:
             with open(fname, 'wb') as fid:
@@ -106,44 +105,8 @@ def _get_legen_table(ch_type, volume_integral=False, n_coeff=100,
     return lut, n_fact
 
 
-def _get_legen_lut_fast(x, lut, block=None):
-    """Return Legendre coefficients for given x values in -1<=x<=1"""
-    # map into table vals (works for both vals and deriv tables)
-    n_interp = (lut.shape[0] - 1.0)
-    # equiv to "(x + 1.0) / 2.0) * n_interp" but faster
-    mm = x * (n_interp / 2.0)
-    mm += 0.5 * n_interp
-    # nearest-neighbor version (could be decent enough...)
-    idx = np.round(mm).astype(int)
-    if block is None:
-        vals = lut[idx]
-    else:  # read only one block at a time to minimize memory consumption
-        vals = lut[idx, :, block]
-    return vals
-
-
-def _get_legen_lut_accurate(x, lut, block=None):
-    """Return Legendre coefficients for given x values in -1<=x<=1"""
-    # map into table vals (works for both vals and deriv tables)
-    n_interp = (lut.shape[0] - 1.0)
-    # equiv to "(x + 1.0) / 2.0) * n_interp" but faster
-    mm = x * (n_interp / 2.0)
-    mm += 0.5 * n_interp
-    # slower, more accurate interpolation version
-    mm = np.minimum(mm, n_interp - 0.0000000001)
-    idx = np.floor(mm).astype(int)
-    w2 = mm - idx
-    if block is None:
-        w2.shape += tuple([1] * (lut.ndim - w2.ndim))  # expand to correct size
-        vals = (1 - w2) * lut[idx] + w2 * lut[idx + 1]
-    else:  # read only one block at a time to minimize memory consumption
-        w2.shape += tuple([1] * (lut[:, :, block].ndim - w2.ndim))
-        vals = (1 - w2) * lut[idx, :, block] + w2 * lut[idx + 1, :, block]
-    return vals
-
-
 def _comp_sum_eeg(beta, ctheta, lut_fun, n_fact):
-    """Lead field dot products using Legendre polynomial (P_n) series"""
+    """Lead field dot products using Legendre polynomial (P_n) series."""
     # Compute the sum occurring in the evaluation.
     # The result is
     #   sums[:]    (2n+1)^2/n beta^n P_n
@@ -266,6 +229,7 @@ def _fast_sphere_dot_r0(r, rr1_orig, rr2s, lr1, lr2s, cosmags1, cosmags2s,
 
     # outer product, sum over coords
     ct = np.einsum('ik,jk->ij', rr1_orig, rr2)
+    np.clip(ct, -1, 1, ct)
 
     # expand axes
     rr1 = rr1_orig[:, np.newaxis, :]  # (n_rr1, n_rr2, n_coord) e.g. 4x4x3
@@ -366,7 +330,7 @@ def _do_self_dots(intrad, volume, coils, r0, ch_type, lut, n_fact, n_jobs):
 
 def _do_self_dots_subset(intrad, rmags, rlens, cosmags, ws, volume, lut,
                          n_fact, ch_type, idx):
-    """Helper for parallelization"""
+    """Parallelize."""
     # all possible combinations of two magnetometers
     products = np.zeros((len(rmags), len(rmags)))
     for ci1 in idx:
@@ -438,7 +402,7 @@ def _do_cross_dots(intrad, volume, coils1, coils2, r0, ch_type,
 
 def _do_surface_dots(intrad, volume, coils, surf, sel, r0, ch_type,
                      lut, n_fact, n_jobs):
-    """Compute the map construction products
+    """Compute the map construction products.
 
     Parameters
     ----------
@@ -505,7 +469,7 @@ def _do_surface_dots(intrad, volume, coils, surf, sel, r0, ch_type,
 def _do_surface_dots_subset(intrad, rsurf, rmags, rref, refl, lsurf, rlens,
                             this_nn, cosmags, ws, volume, lut, n_fact, ch_type,
                             idx):
-    """Helper for parallelization.
+    """Parallelize.
 
     Parameters
     ----------
diff --git a/mne/forward/_make_forward.py b/mne/forward/_make_forward.py
index b4521e3..c4b574c 100644
--- a/mne/forward/_make_forward.py
+++ b/mne/forward/_make_forward.py
@@ -5,6 +5,7 @@
 #
 # License: BSD (3-clause)
 
+from copy import deepcopy
 import os
 from os import path as op
 import numpy as np
@@ -16,6 +17,7 @@ from ..transforms import (_ensure_trans, transform_surface_to, apply_trans,
                           _get_trans, _print_coord_trans, _coord_frame_name,
                           Transform)
 from ..utils import logger, verbose, warn
+from ..parallel import check_n_jobs
 from ..source_space import (_ensure_src, _filter_source_spaces,
                             _make_discrete_source_space, SourceSpaces)
 from ..source_estimate import VolSourceEstimate
@@ -23,8 +25,7 @@ from ..surface import _normalize_vectors
 from ..bem import read_bem_solution, _bem_find_surface, ConductorModel
 from ..externals.six import string_types
 
-from .forward import (Forward, write_forward_solution, _merge_meg_eeg_fwds,
-                      convert_forward_solution)
+from .forward import Forward, _merge_meg_eeg_fwds, convert_forward_solution
 from ._compute_forward import _compute_forwards
 
 
@@ -45,8 +46,8 @@ def _read_coil_defs(elekta_defs=False, verbose=None):
         so the first matching coil should be selected for optimal
         integration parameters.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
-        Defaults to raw.verbose.
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -65,57 +66,65 @@ def _read_coil_defs(elekta_defs=False, verbose=None):
     return coils
 
 
+# Typically we only have 1 or 2 coil def files, but they can end up being
+# read a lot. Let's keep a list of them and just reuse them:
+_coil_register = {}
+
+
 def _read_coil_def_file(fname):
-    """Helper to read a coil def file"""
-    big_val = 0.5
-    coils = list()
-    with open(fname, 'r') as fid:
-        lines = fid.readlines()
-    lines = lines[::-1]
-    while len(lines) > 0:
-        line = lines.pop()
-        if line[0] != '#':
-            vals = np.fromstring(line, sep=' ')
-            assert len(vals) in (6, 7)  # newer numpy can truncate comment
-            start = line.find('"')
-            end = len(line.strip()) - 1
-            assert line.strip()[end] == '"'
-            desc = line[start:end]
-            npts = int(vals[3])
-            coil = dict(coil_type=vals[1], coil_class=vals[0], desc=desc,
-                        accuracy=vals[2], size=vals[4], base=vals[5])
-            # get parameters of each component
-            rmag = list()
-            cosmag = list()
-            w = list()
-            for p in range(npts):
-                # get next non-comment line
-                line = lines.pop()
-                while(line[0] == '#'):
-                    line = lines.pop()
+    """Read a coil def file."""
+    if fname not in _coil_register:
+        big_val = 0.5
+        coils = list()
+        with open(fname, 'r') as fid:
+            lines = fid.readlines()
+        lines = lines[::-1]
+        while len(lines) > 0:
+            line = lines.pop()
+            if line[0] != '#':
                 vals = np.fromstring(line, sep=' ')
-                assert len(vals) == 7
-                # Read and verify data for each integration point
-                w.append(vals[0])
-                rmag.append(vals[[1, 2, 3]])
-                cosmag.append(vals[[4, 5, 6]])
-            w = np.array(w)
-            rmag = np.array(rmag)
-            cosmag = np.array(cosmag)
-            size = np.sqrt(np.sum(cosmag ** 2, axis=1))
-            if np.any(np.sqrt(np.sum(rmag ** 2, axis=1)) > big_val):
-                raise RuntimeError('Unreasonable integration point')
-            if np.any(size <= 0):
-                raise RuntimeError('Unreasonable normal')
-            cosmag /= size[:, np.newaxis]
-            coil.update(dict(w=w, cosmag=cosmag, rmag=rmag))
-            coils.append(coil)
+                assert len(vals) in (6, 7)  # newer numpy can truncate comment
+                start = line.find('"')
+                end = len(line.strip()) - 1
+                assert line.strip()[end] == '"'
+                desc = line[start:end]
+                npts = int(vals[3])
+                coil = dict(coil_type=vals[1], coil_class=vals[0], desc=desc,
+                            accuracy=vals[2], size=vals[4], base=vals[5])
+                # get parameters of each component
+                rmag = list()
+                cosmag = list()
+                w = list()
+                for p in range(npts):
+                    # get next non-comment line
+                    line = lines.pop()
+                    while(line[0] == '#'):
+                        line = lines.pop()
+                    vals = np.fromstring(line, sep=' ')
+                    assert len(vals) == 7
+                    # Read and verify data for each integration point
+                    w.append(vals[0])
+                    rmag.append(vals[[1, 2, 3]])
+                    cosmag.append(vals[[4, 5, 6]])
+                w = np.array(w)
+                rmag = np.array(rmag)
+                cosmag = np.array(cosmag)
+                size = np.sqrt(np.sum(cosmag ** 2, axis=1))
+                if np.any(np.sqrt(np.sum(rmag ** 2, axis=1)) > big_val):
+                    raise RuntimeError('Unreasonable integration point')
+                if np.any(size <= 0):
+                    raise RuntimeError('Unreasonable normal')
+                cosmag /= size[:, np.newaxis]
+                coil.update(dict(w=w, cosmag=cosmag, rmag=rmag))
+                coils.append(coil)
+        _coil_register[fname] = coils
+    coils = deepcopy(_coil_register[fname])
     logger.info('%d coil definitions read', len(coils))
     return coils
 
 
 def _create_meg_coil(coilset, ch, acc, do_es):
-    """Create a coil definition using templates, transform if necessary"""
+    """Create a coil definition using templates, transform if necessary."""
     # Also change the coordinate frame if so desired
     if ch['kind'] not in [FIFF.FIFFV_MEG_CH, FIFF.FIFFV_REF_MEG_CH]:
         raise RuntimeError('%s is not a MEG channel' % ch['ch_name'])
@@ -150,12 +159,12 @@ def _create_meg_coil(coilset, ch, acc, do_es):
 
 
 def _create_eeg_el(ch, t=None):
-    """Create an electrode definition, transform coords if necessary"""
+    """Create an electrode definition, transform coords if necessary."""
     if ch['kind'] != FIFF.FIFFV_EEG_CH:
         raise RuntimeError('%s is not an EEG channel. Cannot create an '
                            'electrode definition.' % ch['ch_name'])
     if t is None:
-        t = Transform('head', 'head', np.eye(4))  # identity, no change
+        t = Transform('head', 'head')  # identity, no change
     if t.from_str != 'head':
         raise RuntimeError('Inappropriate coordinate transformation')
 
@@ -178,7 +187,7 @@ def _create_eeg_el(ch, t=None):
 
 
 def _create_meg_coils(chs, acc, t=None, coilset=None, do_es=False):
-    """Create a set of MEG coils in the head coordinate frame"""
+    """Create a set of MEG coils in the head coordinate frame."""
     acc = _accuracy_dict[acc] if isinstance(acc, string_types) else acc
     coilset = _read_coil_defs(verbose=False) if coilset is None else coilset
     coils = [_create_meg_coil(coilset, ch, acc, do_es) for ch in chs]
@@ -187,7 +196,7 @@ def _create_meg_coils(chs, acc, t=None, coilset=None, do_es=False):
 
 
 def _transform_orig_meg_coils(coils, t, do_es=True):
-    """Helper to transform original (device) MEG coil positions"""
+    """Transform original (device) MEG coil positions."""
     if t is None:
         return
     for coil in coils:
@@ -204,19 +213,21 @@ def _transform_orig_meg_coils(coils, t, do_es=True):
 
 
 def _create_eeg_els(chs):
-    """Create a set of EEG electrodes in the head coordinate frame"""
+    """Create a set of EEG electrodes in the head coordinate frame."""
     return [_create_eeg_el(ch) for ch in chs]
 
 
 @verbose
 def _setup_bem(bem, bem_extra, neeg, mri_head_t, verbose=None):
-    """Set up a BEM for forward computation"""
+    """Set up a BEM for forward computation, making a copy and modifying."""
     logger.info('')
     if isinstance(bem, string_types):
         logger.info('Setting up the BEM model using %s...\n' % bem_extra)
         bem = read_bem_solution(bem)
-    if not isinstance(bem, ConductorModel):
-        raise TypeError('bem must be a string or ConductorModel')
+    else:
+        if not isinstance(bem, ConductorModel):
+            raise TypeError('bem must be a string or ConductorModel')
+        bem = bem.copy()
     if bem['is_sphere']:
         logger.info('Using the sphere model.\n')
         if len(bem['layers']) == 0 and neeg > 0:
@@ -225,6 +236,10 @@ def _setup_bem(bem, bem_extra, neeg, mri_head_t, verbose=None):
         if bem['coord_frame'] != FIFF.FIFFV_COORD_HEAD:
             raise RuntimeError('Spherical model is not in head coordinates')
     else:
+        if bem['surfs'][0]['coord_frame'] != FIFF.FIFFV_COORD_MRI:
+            raise RuntimeError(
+                'BEM is in %s coordinates, should be in MRI'
+                % (_coord_frame_name(bem['surfs'][0]['coord_frame']),))
         if neeg > 0 and len(bem['surfs']) == 1:
             raise RuntimeError('Cannot use a homogeneous model in EEG '
                                'calculations')
@@ -240,8 +255,8 @@ def _setup_bem(bem, bem_extra, neeg, mri_head_t, verbose=None):
 @verbose
 def _prep_meg_channels(info, accurate=True, exclude=(), ignore_ref=False,
                        elekta_defs=False, head_frame=True, do_es=False,
-                       verbose=None):
-    """Prepare MEG coil definitions for forward calculation
+                       do_picking=True, verbose=None):
+    """Prepare MEG coil definitions for forward calculation.
 
     Parameters
     ----------
@@ -262,9 +277,11 @@ def _prep_meg_channels(info, accurate=True, exclude=(), ignore_ref=False,
         If True (default), use head frame coords. Otherwise, use device frame.
     do_es : bool
         If True, compute and store ex, ey, ez, and r0_exey.
+    do_picking : bool
+        If True, pick info and return it.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
-        Defaults to raw.verbose.
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -277,10 +294,8 @@ def _prep_meg_channels(info, accurate=True, exclude=(), ignore_ref=False,
     meginfo : instance of Info
         Information subselected for just the set of MEG coils
     """
-
     accuracy = 'accurate' if accurate else 'normal'
     info_extra = 'info'
-    meg_info = None
     megnames, megcoils, compcoils = [], [], []
 
     # Find MEG channels
@@ -293,7 +308,7 @@ def _prep_meg_channels(info, accurate=True, exclude=(), ignore_ref=False,
         raise RuntimeError('Could not find any MEG channels')
 
     # Get channel info and names for MEG channels
-    megchs = pick_info(info, picks)['chs']
+    megchs = [info['chs'][pick] for pick in picks]
     megnames = [info['ch_names'][p] for p in picks]
     logger.info('Read %3d MEG channels from %s'
                 % (len(picks), info_extra))
@@ -319,7 +334,6 @@ def _prep_meg_channels(info, accurate=True, exclude=(), ignore_ref=False,
     ncomp_data = len(info['comps'])
     ref_meg = True if not ignore_ref else False
     picks = pick_types(info, meg=True, ref_meg=ref_meg, exclude=exclude)
-    meg_info = pick_info(info, picks) if nmeg > 0 else None
 
     # Create coil descriptions with transformation to head or device frame
     templates = _read_coil_defs(elekta_defs=elekta_defs)
@@ -347,12 +361,15 @@ def _prep_meg_channels(info, accurate=True, exclude=(), ignore_ref=False,
         assert megcoils[0]['coord_frame'] == FIFF.FIFFV_COORD_DEVICE
         logger.info('MEG coil definitions created in device coordinate.')
 
-    return megcoils, compcoils, megnames, meg_info
+    out = (megcoils, compcoils, megnames)
+    if do_picking:
+        out = out + (pick_info(info, picks) if nmeg > 0 else None,)
+    return out
 
 
 @verbose
 def _prep_eeg_channels(info, exclude=(), verbose=None):
-    """Prepare EEG electrode definitions for forward calculation
+    """Prepare EEG electrode definitions for forward calculation.
 
     Parameters
     ----------
@@ -362,8 +379,8 @@ def _prep_eeg_channels(info, exclude=(), verbose=None):
         List of channels to exclude. If 'bads', exclude channels in
         info['bads']
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
-        Defaults to raw.verbose.
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -399,10 +416,8 @@ def _prep_eeg_channels(info, exclude=(), verbose=None):
 @verbose
 def _prepare_for_forward(src, mri_head_t, info, bem, mindist, n_jobs,
                          bem_extra='', trans='', info_extra='',
-                         meg=True, eeg=True, ignore_ref=False, fname=None,
-                         overwrite=False, verbose=None):
-    """Helper to prepare for forward computation"""
-
+                         meg=True, eeg=True, ignore_ref=False, verbose=None):
+    """Prepare for forward computation."""
     # Read the source locations
     logger.info('')
     # let's make a copy in case we modify something
@@ -423,8 +438,8 @@ def _prepare_for_forward(src, mri_head_t, info, bem, mindist, n_jobs,
     _print_coord_trans(mri_head_t)
 
     # make a new dict with the relevant information
-    arg_list = [info_extra, trans, src, bem_extra, fname, meg, eeg,
-                mindist, overwrite, n_jobs, verbose]
+    arg_list = [info_extra, trans, src, bem_extra, meg, eeg, mindist,
+                n_jobs, verbose]
     cmd = 'make_forward_solution(%s)' % (', '.join([str(a) for a in arg_list]))
     mri_id = dict(machid=np.zeros(2, np.int32), version=0, secs=0, usecs=0)
     info = Info(chs=info['chs'], comps=info['comps'],
@@ -484,10 +499,10 @@ def _prepare_for_forward(src, mri_head_t, info, bem, mindist, n_jobs,
 
 
 @verbose
-def make_forward_solution(info, trans, src, bem, fname=None, meg=True,
-                          eeg=True, mindist=0.0, ignore_ref=False,
-                          overwrite=False, n_jobs=1, verbose=None):
-    """Calculate a forward solution for a subject
+def make_forward_solution(info, trans, src, bem, meg=True, eeg=True,
+                          mindist=0.0, ignore_ref=False, n_jobs=1,
+                          verbose=None):
+    """Calculate a forward solution for a subject.
 
     Parameters
     ----------
@@ -508,9 +523,6 @@ def make_forward_solution(info, trans, src, bem, fname=None, meg=True,
     bem : dict | str
         Filename of the BEM (e.g., "sample-5120-5120-5120-bem-sol.fif") to
         use, or a loaded sphere model (dict).
-    fname : str | None
-        Destination forward solution filename. If None, the solution
-        will not be saved.
     meg : bool
         If True (Default), include MEG computations.
     eeg : bool
@@ -521,25 +533,28 @@ def make_forward_solution(info, trans, src, bem, fname=None, meg=True,
         If True, do not include reference channels in compensation. This
         option should be True for KIT files, since forward computation
         with reference channels is not currently supported.
-    overwrite : bool
-        If True, the destination file (if it exists) will be overwritten.
-        If False (default), an error will be raised if the file exists.
     n_jobs : int
         Number of jobs to run in parallel.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
     fwd : instance of Forward
         The forward solution.
 
+    See Also
+    --------
+    convert_forward_solution
+
     Notes
     -----
-    Some of the forward solution calculation options from the C code
-    (e.g., `--grad`, `--fixed`) are not implemented here. For those,
-    consider using the C command line tools, or request that they
-    be added to the MNE-Python.
+    The ``--grad`` option from MNE-C (to compute gradients) is not implemented
+    here.
+
+    To create a fixed-orientation forward solution, use this function
+    followed by :func:`mne.convert_forward_solution`.
     """
     # Currently not (sup)ported:
     # 1. --grad option (gradients of the field, not used much)
@@ -549,10 +564,10 @@ def make_forward_solution(info, trans, src, bem, fname=None, meg=True,
     # read the transformation from MRI to HEAD coordinates
     # (could also be HEAD to MRI)
     mri_head_t, trans = _get_trans(trans)
-    bem_extra = 'dict' if isinstance(bem, dict) else bem
-    if fname is not None and op.isfile(fname) and not overwrite:
-        raise IOError('file "%s" exists, consider using overwrite=True'
-                      % fname)
+    if isinstance(bem, ConductorModel):
+        bem_extra = 'instance of ConductorModel'
+    else:
+        bem_extra = bem
     if not isinstance(info, (Info, string_types)):
         raise TypeError('info should be an instance of Info or string')
     if isinstance(info, string_types):
@@ -560,27 +575,27 @@ def make_forward_solution(info, trans, src, bem, fname=None, meg=True,
         info = read_info(info, verbose=False)
     else:
         info_extra = 'instance of Info'
+    n_jobs = check_n_jobs(n_jobs)
 
     # Report the setup
-    logger.info('Source space                 : %s' % src)
-    logger.info('MRI -> head transform source : %s' % trans)
-    logger.info('Measurement data             : %s' % info_extra)
-    if isinstance(bem, dict) and bem['is_sphere']:
-        logger.info('Sphere model                 : origin at %s mm'
+    logger.info('Source space          : %s' % src)
+    logger.info('MRI -> head transform : %s' % trans)
+    logger.info('Measurement data      : %s' % info_extra)
+    if isinstance(bem, ConductorModel) and bem['is_sphere']:
+        logger.info('Sphere model      : origin at %s mm'
                     % (bem['r0'],))
         logger.info('Standard field computations')
     else:
-        logger.info('BEM model                    : %s' % bem_extra)
+        logger.info('Conductor model   : %s' % bem_extra)
         logger.info('Accurate field computations')
     logger.info('Do computations in %s coordinates',
                 _coord_frame_name(FIFF.FIFFV_COORD_HEAD))
     logger.info('Free source orientations')
-    logger.info('Destination for the solution : %s' % fname)
 
     megcoils, meg_info, compcoils, megnames, eegels, eegnames, rr, info, \
         update_kwargs, bem = _prepare_for_forward(
             src, mri_head_t, info, bem, mindist, n_jobs, bem_extra, trans,
-            info_extra, meg, eeg, ignore_ref, fname, overwrite)
+            info_extra, meg, eeg, ignore_ref)
     del (src, mri_head_t, trans, info_extra, bem_extra, mindist,
          meg, eeg, ignore_ref)
 
@@ -602,27 +617,23 @@ def make_forward_solution(info, trans, src, bem, fname=None, meg=True,
     # done in the C code) because mne-python assumes forward solution source
     # spaces are in head coords.
     fwd.update(**update_kwargs)
-    if fname is not None:
-        logger.info('writing %s...', fname)
-        write_forward_solution(fname, fwd, overwrite, verbose=False)
-
     logger.info('Finished.')
     return fwd
 
 
 def make_forward_dipole(dipole, bem, info, trans=None, n_jobs=1, verbose=None):
-    """Convert dipole object to source estimate and calculate forward operator
+    """Convert dipole object to source estimate and calculate forward operator.
 
     The instance of Dipole is converted to a discrete source space,
     which is then combined with a BEM or a sphere model and
     the sensor information in info to form a forward operator.
 
     The source estimate object (with the forward operator) can be projected to
-    sensor-space using :func:`mne.simulation.evoked.simulate_evoked`.
+    sensor-space using :func:`mne.simulation.simulate_evoked`.
 
-    Note that if the (unique) time points of the dipole object are unevenly
-    spaced, the first output will be a list of single-timepoint source
-    estimates.
+    .. note:: If the (unique) time points of the dipole object are unevenly
+              spaced, the first output will be a list of single-timepoint
+              source estimates.
 
     Parameters
     ----------
@@ -641,7 +652,8 @@ def make_forward_dipole(dipole, bem, info, trans=None, n_jobs=1, verbose=None):
     n_jobs : int
         Number of jobs to run in parallel (used in making forward solution).
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -675,11 +687,11 @@ def make_forward_dipole(dipole, bem, info, trans=None, n_jobs=1, verbose=None):
 
     # Forward operator created for channels in info (use pick_info to restrict)
     # Use defaults for most params, including min_dist
-    fwd = make_forward_solution(info, trans, src, bem, fname=None,
-                                n_jobs=n_jobs, verbose=verbose)
+    fwd = make_forward_solution(info, trans, src, bem, n_jobs=n_jobs,
+                                verbose=verbose)
     # Convert from free orientations to fixed (in-place)
     convert_forward_solution(fwd, surf_ori=False, force_fixed=True,
-                             copy=False, verbose=None)
+                             copy=False, use_cps=False, verbose=None)
 
     # Check for omissions due to proximity to inner skull in
     # make_forward_solution, which will result in an exception
@@ -737,7 +749,7 @@ def make_forward_dipole(dipole, bem, info, trans=None, n_jobs=1, verbose=None):
 def _to_forward_dict(fwd, names, fwd_grad=None,
                      coord_frame=FIFF.FIFFV_COORD_HEAD,
                      source_ori=FIFF.FIFFV_MNE_FREE_ORI):
-    """Convert forward solution matrices to dicts"""
+    """Convert forward solution matrices to dicts."""
     assert names is not None
     if len(fwd) == 0:
         return None
diff --git a/mne/forward/forward.py b/mne/forward/forward.py
index cba55f5..bd8a6f5 100644
--- a/mne/forward/forward.py
+++ b/mne/forward/forward.py
@@ -30,9 +30,9 @@ from ..io.pick import (pick_channels_forward, pick_info, pick_channels,
 from ..io.write import (write_int, start_block, end_block,
                         write_coord_trans, write_ch_info, write_name_list,
                         write_string, start_file, end_file, write_id)
-from ..io.base import _BaseRaw
+from ..io.base import BaseRaw
 from ..evoked import Evoked, EvokedArray
-from ..epochs import _BaseEpochs
+from ..epochs import BaseEpochs
 from ..source_space import (_read_source_spaces_from_tree,
                             find_source_space_hemi,
                             _write_source_spaces_to_fid)
@@ -40,20 +40,19 @@ from ..source_estimate import VolSourceEstimate
 from ..transforms import (transform_surface_to, invert_transform,
                           write_trans)
 from ..utils import (_check_fname, get_subjects_dir, has_mne_c, warn,
-                     run_subprocess, check_fname, logger, verbose, deprecated)
+                     run_subprocess, check_fname, logger, verbose)
 from ..label import Label
 
 
 class Forward(dict):
-    """Forward class to represent info from forward solution
-    """
+    """Forward class to represent info from forward solution."""
+
     def copy(self):
-        """Copy the Forward instance"""
+        """Copy the Forward instance."""
         return Forward(deepcopy(self))
 
     def __repr__(self):
-        """Summarize forward info instead of printing all"""
-
+        """Summarize forward info instead of printing all."""
         entr = '<Forward'
 
         nchan = len(pick_types(self['info'], meg=True, eeg=False, exclude=[]))
@@ -96,33 +95,8 @@ class Forward(dict):
         return entr
 
 
- at deprecated("it will be removed in mne 0.14; use mne.make_bem_solution() "
-            "instead.")
-def prepare_bem_model(bem, sol_fname=None, method='linear'):
-    """Wrapper for the mne_prepare_bem_model command line utility
-
-    Parameters
-    ----------
-    bem : str
-        The name of the file containing the triangulations of the BEM surfaces
-        and the conductivities of the compartments. The standard ending for
-        this file is -bem.fif and it is produced either with the utility
-        mne_surf2bem or the convenience script mne_setup_forward_model.
-    sol_fname : None | str
-        The output file. None (the default) will employ the standard naming
-        scheme. To conform with the standard naming conventions the filename
-        should start with the subject name and end in "-bem-sol.fif".
-    method : 'linear' | 'constant'
-        The BEM approach.
-    """
-    cmd = ['mne_prepare_bem_model', '--bem', bem, '--method', method]
-    if sol_fname is not None:
-        cmd.extend(('--sol', sol_fname))
-    run_subprocess(cmd)
-
-
 def _block_diag(A, n):
-    """Constructs a block diagonal from a packed structure
+    """Construct a block diagonal from a packed structure.
 
     You have to try it on a matrix to see what it's doing.
 
@@ -171,7 +145,7 @@ def _block_diag(A, n):
 
 
 def _inv_block_diag(A, n):
-    """Constructs an inverse block diagonal from a packed structure
+    """Construct an inverse block diagonal from a packed structure.
 
     You have to try it on a matrix to see what it's doing.
 
@@ -185,6 +159,7 @@ def _inv_block_diag(A, n):
         The matrix.
     n : int
         The block size.
+
     Returns
     -------
     bd : sparse matrix
@@ -216,7 +191,7 @@ def _inv_block_diag(A, n):
 
 
 def _get_tag_int(fid, node, name, id_):
-    """Helper to check we have an appropriate tag"""
+    """Check we have an appropriate tag."""
     tag = find_tag(fid, node, id_)
     if tag is None:
         fid.close()
@@ -225,8 +200,7 @@ def _get_tag_int(fid, node, name, id_):
 
 
 def _read_one(fid, node):
-    """Read all interesting stuff for one forward solution
-    """
+    """Read all interesting stuff for one forward solution."""
     # This function assumes the fid is open as a context manager
     if node is None:
         return None
@@ -273,7 +247,7 @@ def _read_one(fid, node):
 
 
 def _read_forward_meas_info(tree, fid):
-    """Read light measurement info from forward operator
+    """Read light measurement info from forward operator.
 
     Parameters
     ----------
@@ -364,13 +338,13 @@ def _read_forward_meas_info(tree, fid):
 
 
 def _subject_from_forward(forward):
-    """Get subject id from inverse operator"""
+    """Get subject id from inverse operator."""
     return forward['src'][0].get('subject_his_id', None)
 
 
 @verbose
 def _merge_meg_eeg_fwds(megfwd, eegfwd, verbose=None):
-    """Merge loaded MEG and EEG forward dicts into one dict"""
+    """Merge loaded MEG and EEG forward dicts into one dict."""
     if megfwd is not None and eegfwd is not None:
         if (megfwd['sol']['data'].shape[1] != eegfwd['sol']['data'].shape[1] or
                 megfwd['source_ori'] != eegfwd['source_ori'] or
@@ -405,17 +379,19 @@ def _merge_meg_eeg_fwds(megfwd, eegfwd, verbose=None):
 
 
 @verbose
-def read_forward_solution(fname, force_fixed=False, surf_ori=False,
+def read_forward_solution(fname, force_fixed=None, surf_ori=None,
                           include=[], exclude=[], verbose=None):
-    """Read a forward solution a.k.a. lead field
+    """Read a forward solution a.k.a. lead field.
 
     Parameters
     ----------
     fname : string
         The file name, which should end with -fwd.fif or -fwd.fif.gz.
-    force_fixed : bool, optional (default False)
+    force_fixed : None | bool, optional (default None)
+        Deprecated. Use :func:`convert_forward_solution`.
         Force fixed source orientation mode?
-    surf_ori : bool, optional (default False)
+    surf_ori : None | bool, optional (default None)
+        Deprecated. Use :func:`convert_forward_solution`.
         Use surface-based source coordinate system? Note that force_fixed=True
         implies surf_ori=True.
     include : list, optional
@@ -425,7 +401,8 @@ def read_forward_solution(fname, force_fixed=False, surf_ori=False,
         List of names of channels to exclude. If empty include all
         channels.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -435,7 +412,31 @@ def read_forward_solution(fname, force_fixed=False, surf_ori=False,
     See Also
     --------
     write_forward_solution, make_forward_solution
+
+    Notes
+    -----
+    Forward solutions, which are derived from an original forward solution with
+    free orientation, are always stored on disk as forward solution with free
+    orientation in X/Y/Z RAS coordinates. To apply any transformation to the
+    forward operator (surface orientation, fixed orienation) please apply
+    :func:`convert_forward_solution` after reading the forward solution with
+    :func:`read_forward_solution`.
+
+    Forward solutions, which are derived from an original forward solution with
+    fixed orientation, are stored on disk as forward solution with fixed
+    surface-based orientations. Please note that the transformation to
+    surface-based, fixed orienation cannot be reverted after loading the
+    forward solution with :func:`read_forward_solution`.
     """
+    if force_fixed is not None:
+        warn('force_fixed is deprecated and will be removed in 0.16. '
+             'For handling transformations, apply convert_forward_solution '
+             'after read_forward_solution instead.', DeprecationWarning)
+    if surf_ori is not None:
+        warn('surf_ori is deprecated and will be removed in 0.16. '
+             'For handling transformations, apply convert_forward_solution '
+             'after read_forward_solution instead.', DeprecationWarning)
+
     check_fname(fname, 'forward', ('-fwd.fif', '-fwd.fif.gz'))
 
     #   Open the file, create directory
@@ -534,12 +535,11 @@ def read_forward_solution(fname, force_fixed=False, surf_ori=False,
         raise ValueError('Only forward solutions computed in MRI or head '
                          'coordinates are acceptable')
 
-    nuse = 0
-
     # Transform each source space to the HEAD or MRI coordinate frame,
     # depending on the coordinate frame of the forward solution
     # NOTE: the function transform_surface_to will also work on discrete and
     # volume sources
+    nuse = 0
     for s in src:
         try:
             s = transform_surface_to(s, fwd['coord_frame'], mri_head_t)
@@ -560,19 +560,37 @@ def read_forward_solution(fname, force_fixed=False, surf_ori=False,
     fwd['source_rr'] = np.concatenate([ss['rr'][ss['vertno'], :]
                                        for ss in src], axis=0)
 
-    # deal with transformations, storing orig copies so transforms can be done
-    # as necessary later
+    #   Store original source orientations
     fwd['_orig_source_ori'] = fwd['source_ori']
-    convert_forward_solution(fwd, surf_ori, force_fixed, copy=False)
+
+    #   Deal with include and exclude
     fwd = pick_channels_forward(fwd, include=include, exclude=exclude)
 
+    if surf_ori is not None or force_fixed is not None:
+        # Deal with transformations
+        if surf_ori is None:
+            surf_ori = False
+        if force_fixed is None:
+            force_fixed = False
+        convert_forward_solution(fwd, surf_ori=surf_ori,
+                                 force_fixed=force_fixed, copy=False)
+    else:
+        if is_fixed_orient(fwd, orig=True):
+            fwd['source_nn'] = np.concatenate([_src['nn'][_src['vertno'], :]
+                                              for _src in fwd['src']], axis=0)
+            fwd['source_ori'] = FIFF.FIFFV_MNE_FIXED_ORI
+            fwd['surf_ori'] = True
+        else:
+            fwd['source_nn'] = np.kron(np.ones((fwd['nsource'], 1)), np.eye(3))
+            fwd['source_ori'] = FIFF.FIFFV_MNE_FREE_ORI
+            fwd['surf_ori'] = False
     return Forward(fwd)
 
 
 @verbose
 def convert_forward_solution(fwd, surf_ori=False, force_fixed=False,
-                             copy=True, verbose=None):
-    """Convert forward solution between different source orientations
+                             copy=True, use_cps=None, verbose=None):
+    """Convert forward solution between different source orientations.
 
     Parameters
     ----------
@@ -585,16 +603,57 @@ def convert_forward_solution(fwd, surf_ori=False, force_fixed=False,
         Force fixed source orientation mode?
     copy : bool
         Whether to return a new instance or modify in place.
+    use_cps : None | bool (default None)
+        Whether to use cortical patch statistics to define normal
+        orientations. Only used when surf_ori and/or force_fixed are True.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
     fwd : Forward
         The modified forward solution.
     """
+    if use_cps is None:
+        if force_fixed:
+            use_cps = False
+            warn('The default settings controlling the application of '
+                 'cortical patch statistics (cps) in the creation of forward '
+                 'operators with fixed orientation will be modified in 0.16. '
+                 'The cps (if available) will then be applied by default. '
+                 'To avoid this warning, set use_cps explicitly to False (the '
+                 'current default) or True (the new default).', FutureWarning)
+        else:
+            use_cps = True
+
     fwd = fwd.copy() if copy else fwd
 
+    if force_fixed is True:
+        surf_ori = True
+
+    if any([src['type'] == 'vol' for src in fwd['src']]) and force_fixed:
+        warn('Forward operator was generated with sources from a '
+             'volume source space. Conversion to fixed orientation is not '
+             'possible. Setting force_fixed to False. surf_ori is ignored for '
+             'volume source spaces.')
+        force_fixed = False
+
+    if surf_ori:
+        if use_cps is True:
+            if fwd['src'][0].get('patch_inds') is not None:
+                use_ave_nn = True
+                logger.info('    Average patch normals will be employed in '
+                            'the rotation to the local surface coordinates..'
+                            '..')
+            else:
+                use_ave_nn = False
+                logger.info('    No patch info available. The standard source '
+                            'space normals will be employed in the rotation '
+                            'to the local surface coordinates....')
+        else:
+            use_ave_nn = False
+
     # We need to change these entries (only):
     # 1. source_nn
     # 2. sol['data']
@@ -602,12 +661,11 @@ def convert_forward_solution(fwd, surf_ori=False, force_fixed=False,
     # 4. sol_grad['data']
     # 5. sol_grad['ncol']
     # 6. source_ori
-    if is_fixed_orient(fwd, orig=True) or force_fixed:  # Fixed
-        nuse = 0
+
+    if is_fixed_orient(fwd, orig=True) or (force_fixed and not use_ave_nn):
+        # Fixed
         fwd['source_nn'] = np.concatenate([s['nn'][s['vertno'], :]
                                            for s in fwd['src']], axis=0)
-
-        #   Modify the forward solution for fixed source orientations
         if not is_fixed_orient(fwd, orig=True):
             logger.info('    Changing to fixed-orientation forward '
                         'solution with surface-based source orientations...')
@@ -617,58 +675,64 @@ def convert_forward_solution(fwd, surf_ori=False, force_fixed=False,
             fwd['sol']['data'] = (fwd['_orig_sol'] *
                                   fix_rot).astype('float32')
             fwd['sol']['ncol'] = fwd['nsource']
-            fwd['source_ori'] = FIFF.FIFFV_MNE_FIXED_ORI
-
             if fwd['sol_grad'] is not None:
                 x = sparse.block_diag([fix_rot] * 3)
                 fwd['sol_grad']['data'] = fwd['_orig_sol_grad'] * x  # dot prod
                 fwd['sol_grad']['ncol'] = 3 * fwd['nsource']
-            logger.info('    [done]')
         fwd['source_ori'] = FIFF.FIFFV_MNE_FIXED_ORI
         fwd['surf_ori'] = True
+
     elif surf_ori:  # Free, surf-oriented
         #   Rotate the local source coordinate systems
-        nuse_total = sum([s['nuse'] for s in fwd['src']])
-        fwd['source_nn'] = np.empty((3 * nuse_total, 3), dtype=np.float)
+        fwd['source_nn'] = np.kron(np.ones((fwd['nsource'], 1)), np.eye(3))
         logger.info('    Converting to surface-based source orientations...')
-        if fwd['src'][0]['patch_inds'] is not None:
-            use_ave_nn = True
-            logger.info('    Average patch normals will be employed in the '
-                        'rotation to the local surface coordinates....')
-        else:
-            use_ave_nn = False
-
         #   Actually determine the source orientations
-        nuse = 0
         pp = 0
         for s in fwd['src']:
-            for p in range(s['nuse']):
-                #  Project out the surface normal and compute SVD
-                if use_ave_nn is True:
-                    nn = s['nn'][s['pinfo'][s['patch_inds'][p]], :]
-                    nn = np.sum(nn, axis=0)[:, np.newaxis]
-                    nn /= linalg.norm(nn)
-                else:
-                    nn = s['nn'][s['vertno'][p], :][:, np.newaxis]
-                U, S, _ = linalg.svd(np.eye(3, 3) - nn * nn.T)
-                #  Make sure that ez is in the direction of nn
-                if np.sum(nn.ravel() * U[:, 2].ravel()) < 0:
-                    U *= -1.0
-                fwd['source_nn'][pp:pp + 3, :] = U.T
-                pp += 3
-            nuse += s['nuse']
+            if s['type'] in ['surf', 'discrete']:
+                for p in range(s['nuse']):
+                    #  Project out the surface normal and compute SVD
+                    if use_ave_nn is True:
+                        nn = s['nn'][s['pinfo'][s['patch_inds'][p]], :]
+                        nn = np.sum(nn, axis=0)[:, np.newaxis]
+                        nn /= linalg.norm(nn)
+                    else:
+                        nn = s['nn'][s['vertno'][p], :][:, np.newaxis]
+                    U, S, _ = linalg.svd(np.eye(3, 3) - nn * nn.T)
+                    #  Make sure that ez is in the direction of nn
+                    if np.sum(nn.ravel() * U[:, 2].ravel()) < 0:
+                        U *= -1.0
+                    fwd['source_nn'][pp:pp + 3, :] = U.T
+                    pp += 3
+            else:
+                pp += 3 * s['nuse']
 
         #   Rotate the solution components as well
-        surf_rot = _block_diag(fwd['source_nn'].T, 3)
-        fwd['sol']['data'] = fwd['_orig_sol'] * surf_rot
-        fwd['sol']['ncol'] = 3 * fwd['nsource']
-        if fwd['sol_grad'] is not None:
-            x = sparse.block_diag([surf_rot] * 3)
-            fwd['sol_grad']['data'] = fwd['_orig_sol_grad'] * x  # dot prod
-            fwd['sol_grad']['ncol'] = 3 * fwd['nsource']
-        logger.info('[done]')
-        fwd['source_ori'] = FIFF.FIFFV_MNE_FREE_ORI
-        fwd['surf_ori'] = True
+        if force_fixed:
+            fwd['source_nn'] = fwd['source_nn'][2::3, :]
+            fix_rot = _block_diag(fwd['source_nn'].T, 1)
+            # newer versions of numpy require explicit casting here, so *= no
+            # longer works
+            fwd['sol']['data'] = (fwd['_orig_sol'] *
+                                  fix_rot).astype('float32')
+            fwd['sol']['ncol'] = fwd['nsource']
+            if fwd['sol_grad'] is not None:
+                x = sparse.block_diag([fix_rot] * 3)
+                fwd['sol_grad']['data'] = fwd['_orig_sol_grad'] * x  # dot prod
+                fwd['sol_grad']['ncol'] = 3 * fwd['nsource']
+            fwd['source_ori'] = FIFF.FIFFV_MNE_FIXED_ORI
+            fwd['surf_ori'] = True
+        else:
+            surf_rot = _block_diag(fwd['source_nn'].T, 3)
+            fwd['sol']['data'] = fwd['_orig_sol'] * surf_rot
+            fwd['sol']['ncol'] = 3 * fwd['nsource']
+            if fwd['sol_grad'] is not None:
+                x = sparse.block_diag([surf_rot] * 3)
+                fwd['sol_grad']['data'] = fwd['_orig_sol_grad'] * x  # dot prod
+                fwd['sol_grad']['ncol'] = 9 * fwd['nsource']
+            fwd['source_ori'] = FIFF.FIFFV_MNE_FREE_ORI
+            fwd['surf_ori'] = True
+
     else:  # Free, cartesian
         logger.info('    Cartesian source orientations...')
         fwd['source_nn'] = np.kron(np.ones((fwd['nsource'], 1)), np.eye(3))
@@ -676,17 +740,18 @@ def convert_forward_solution(fwd, surf_ori=False, force_fixed=False,
         fwd['sol']['ncol'] = 3 * fwd['nsource']
         if fwd['sol_grad'] is not None:
             fwd['sol_grad']['data'] = fwd['_orig_sol_grad'].copy()
-            fwd['sol_grad']['ncol'] = 3 * fwd['nsource']
+            fwd['sol_grad']['ncol'] = 9 * fwd['nsource']
         fwd['source_ori'] = FIFF.FIFFV_MNE_FREE_ORI
         fwd['surf_ori'] = False
-        logger.info('[done]')
+
+    logger.info('    [done]')
 
     return fwd
 
 
 @verbose
 def write_forward_solution(fname, fwd, overwrite=False, verbose=None):
-    """Write forward solution to a file
+    """Write forward solution to a file.
 
     Parameters
     ----------
@@ -698,11 +763,27 @@ def write_forward_solution(fname, fwd, overwrite=False, verbose=None):
     overwrite : bool
         If True, overwrite destination file (if it exists).
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     See Also
     --------
     read_forward_solution
+
+    Notes
+    -----
+    Forward solutions, which are derived from an original forward solution with
+    free orientation, are always stored on disk as forward solution with free
+    orientation in X/Y/Z RAS coordinates. Transformations (surface orientation,
+    fixed orienation) will be reverted. To reapply any transformation to the
+    forward operator please apply :func:`convert_forward_solution` after
+    reading the forward solution with :func:`read_forward_solution`.
+
+    Forward solutions, which are derived from an original forward solution with
+    fixed orientation, are stored on disk as forward solution with fixed
+    surface-based orientations. Please note that the transformation to
+    surface-based, fixed orienation cannot be reverted after loading the
+    forward solution with :func:`read_forward_solution`.
     """
     check_fname(fname, 'forward', ('-fwd.fif', '-fwd.fif.gz'))
 
@@ -756,24 +837,34 @@ def write_forward_solution(fname, fwd, overwrite=False, verbose=None):
     #
     _write_source_spaces_to_fid(fid, src)
     n_vert = sum([ss['nuse'] for ss in src])
-    n_col = fwd['sol']['data'].shape[1]
-    if fwd['source_ori'] == FIFF.FIFFV_MNE_FIXED_ORI:
-        assert n_col == n_vert
+    if fwd['_orig_source_ori'] == FIFF.FIFFV_MNE_FIXED_ORI:
+        n_col = n_vert
     else:
-        assert n_col == 3 * n_vert
+        n_col = 3 * n_vert
 
-    # Undo surf_ori rotation
-    sol = fwd['sol']['data']
+    # Undo transformations
+    sol = fwd['_orig_sol'].copy()
     if fwd['sol_grad'] is not None:
-        sol_grad = fwd['sol_grad']['data']
+        sol_grad = fwd['_orig_sol_grad'].copy()
     else:
         sol_grad = None
 
     if fwd['surf_ori'] is True:
-        inv_rot = _inv_block_diag(fwd['source_nn'].T, 3)
-        sol = sol * inv_rot
-        if sol_grad is not None:
-            sol_grad = sol_grad * sparse.block_diag([inv_rot] * 3)  # dot prod
+        if fwd['_orig_source_ori'] == FIFF.FIFFV_MNE_FIXED_ORI:
+            warn('The forward solution, which is stored on disk now, is based '
+                 'on a forward solution with fixed orientation. Please note '
+                 'that the transformation to surface-based, fixed orientation '
+                 'cannot be reverted after loading the forward solution with '
+                 'read_forward_solution.', RuntimeWarning)
+        else:
+            warn('This forward solution is based on a forward solution with '
+                 'free orientation. The original forward solution is stored '
+                 'on disk in X/Y/Z RAS coordinates. Any transformation '
+                 '(surface orientation or fixed orientation) will be '
+                 'reverted. To reapply any transformation to the forward '
+                 'operator please apply convert_forward_solution after '
+                 'reading the forward solution with read_forward_solution.',
+                 RuntimeWarning)
 
     #
     # MEG forward solution
@@ -794,7 +885,8 @@ def write_forward_solution(fname, fwd, overwrite=False, verbose=None):
         start_block(fid, FIFF.FIFFB_MNE_FORWARD_SOLUTION)
         write_int(fid, FIFF.FIFF_MNE_INCLUDED_METHODS, FIFF.FIFFV_MNE_MEG)
         write_int(fid, FIFF.FIFF_MNE_COORD_FRAME, fwd['coord_frame'])
-        write_int(fid, FIFF.FIFF_MNE_SOURCE_ORIENTATION, fwd['source_ori'])
+        write_int(fid, FIFF.FIFF_MNE_SOURCE_ORIENTATION,
+                  fwd['_orig_source_ori'])
         write_int(fid, FIFF.FIFF_MNE_SOURCE_SPACE_NPOINTS, n_vert)
         write_int(fid, FIFF.FIFF_NCHAN, n_meg)
         write_named_matrix(fid, FIFF.FIFF_MNE_FORWARD_SOLUTION, meg_solution)
@@ -817,7 +909,8 @@ def write_forward_solution(fname, fwd, overwrite=False, verbose=None):
         start_block(fid, FIFF.FIFFB_MNE_FORWARD_SOLUTION)
         write_int(fid, FIFF.FIFF_MNE_INCLUDED_METHODS, FIFF.FIFFV_MNE_EEG)
         write_int(fid, FIFF.FIFF_MNE_COORD_FRAME, fwd['coord_frame'])
-        write_int(fid, FIFF.FIFF_MNE_SOURCE_ORIENTATION, fwd['source_ori'])
+        write_int(fid, FIFF.FIFF_MNE_SOURCE_ORIENTATION,
+                  fwd['_orig_source_ori'])
         write_int(fid, FIFF.FIFF_NCHAN, n_eeg)
         write_int(fid, FIFF.FIFF_MNE_SOURCE_SPACE_NPOINTS, n_vert)
         write_named_matrix(fid, FIFF.FIFF_MNE_FORWARD_SOLUTION, eeg_solution)
@@ -834,22 +927,8 @@ def write_forward_solution(fname, fwd, overwrite=False, verbose=None):
     end_file(fid)
 
 
-def _to_fixed_ori(forward):
-    """Helper to convert the forward solution to fixed ori from free"""
-    if not forward['surf_ori'] or is_fixed_orient(forward):
-        raise ValueError('Only surface-oriented, free-orientation forward '
-                         'solutions can be converted to fixed orientaton')
-    forward['sol']['data'] = forward['sol']['data'][:, 2::3]
-    forward['sol']['ncol'] = forward['sol']['ncol'] / 3
-    forward['source_ori'] = FIFF.FIFFV_MNE_FIXED_ORI
-    logger.info('    Converted the forward solution into the '
-                'fixed-orientation mode.')
-    return forward
-
-
 def is_fixed_orient(forward, orig=False):
-    """Has forward operator fixed orientation?
-    """
+    """Check if the forward operator is fixed orientation."""
     if orig:  # if we want to know about the original version
         fixed_ori = (forward['_orig_source_ori'] == FIFF.FIFFV_MNE_FIXED_ORI)
     else:  # most of the time we want to know about the current version
@@ -858,7 +937,7 @@ def is_fixed_orient(forward, orig=False):
 
 
 def write_forward_meas_info(fid, info):
-    """Write measurement info stored in forward solution
+    """Write measurement info stored in forward solution.
 
     Parameters
     ----------
@@ -901,16 +980,17 @@ def write_forward_meas_info(fid, info):
 
 @verbose
 def compute_orient_prior(forward, loose=0.2, verbose=None):
-    """Compute orientation prior
+    """Compute orientation prior.
 
     Parameters
     ----------
     forward : dict
         Forward operator.
-    loose : float in [0, 1] or None
+    loose : float in [0, 1]
         The loose orientation parameter.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -919,23 +999,26 @@ def compute_orient_prior(forward, loose=0.2, verbose=None):
     """
     is_fixed_ori = is_fixed_orient(forward)
     n_sources = forward['sol']['data'].shape[1]
-
-    if loose is not None:
-        if not (0 <= loose <= 1):
-            raise ValueError('loose value should be smaller than 1 and bigger '
-                             'than 0, or None for not loose orientations.')
-
-        if loose < 1 and not forward['surf_ori']:
-            raise ValueError('Forward operator is not oriented in surface '
-                             'coordinates. loose parameter should be None '
-                             'not %s.' % loose)
-
-        if is_fixed_ori:
-            warn('Ignoring loose parameter with forward operator '
-                 'with fixed orientation.')
+    if loose is None:
+        warn('loose=None is deprecated and will be removed in 0.16, use '
+             'loose=0. for fixed constraint and loose=1. for free '
+             'orientations', DeprecationWarning)
+        loose = 0. if is_fixed_ori else 1.
+
+    loose = float(loose)
+    if not (0 <= loose <= 1):
+        raise ValueError('loose value should be smaller than 1 and bigger '
+                         'than 0, got %s.' % (loose,))
+    if loose < 1 and not forward['surf_ori']:
+        raise ValueError('Forward operator is not oriented in surface '
+                         'coordinates. loose parameter should be 1 '
+                         'not %s.' % loose)
+    if is_fixed_ori and loose != 0:
+        raise ValueError('loose must be 0. with forward operator '
+                         'with fixed orientation.')
 
     orient_prior = np.ones(n_sources, dtype=np.float)
-    if (not is_fixed_ori) and (loose is not None) and (loose < 1):
+    if not is_fixed_ori and loose < 1:
         logger.info('Applying loose dipole orientations. Loose value '
                     'of %s.' % loose)
         orient_prior[np.mod(np.arange(n_sources), 3) != 2] *= loose
@@ -944,7 +1027,7 @@ def compute_orient_prior(forward, loose=0.2, verbose=None):
 
 
 def _restrict_gain_matrix(G, info):
-    """Restrict gain matrix entries for optimal depth weighting"""
+    """Restrict gain matrix entries for optimal depth weighting."""
     # Figure out which ones have been used
     if not (len(info['chs']) == G.shape[0]):
         raise ValueError("G.shape[0] and length of info['chs'] do not match: "
@@ -971,8 +1054,7 @@ def _restrict_gain_matrix(G, info):
 
 def compute_depth_prior(G, gain_info, is_fixed_ori, exp=0.8, limit=10.0,
                         patch_areas=None, limit_depth_chs=False):
-    """Compute weighting for depth prior
-    """
+    """Compute weighting for depth prior."""
     logger.info('Creating the depth weighting matrix...')
 
     # If possible, pick best depth-weighting channels
@@ -1026,8 +1108,7 @@ def compute_depth_prior(G, gain_info, is_fixed_ori, exp=0.8, limit=10.0,
 
 
 def _stc_src_sel(src, stc):
-    """ Select the vertex indices of a source space using a source estimate
-    """
+    """Select the vertex indices of a source space using a source estimate."""
     if isinstance(stc, VolSourceEstimate):
         vertices = [stc.vertices]
     else:
@@ -1047,13 +1128,11 @@ def _stc_src_sel(src, stc):
 
 
 def _fill_measurement_info(info, fwd, sfreq):
-    """ Fill the measurement info of a Raw or Evoked object
-    """
+    """Fill the measurement info of a Raw or Evoked object."""
     sel = pick_channels(info['ch_names'], fwd['sol']['row_names'])
     info = pick_info(info, sel)
     info['bads'] = []
 
-    info['filename'] = None
     # this is probably correct based on what's done in meas_info.py...
     info['meas_id'] = fwd['info']['meas_id']
     info['file_id'] = info['meas_id']
@@ -1073,8 +1152,7 @@ def _fill_measurement_info(info, fwd, sfreq):
 
 @verbose
 def _apply_forward(fwd, stc, start=None, stop=None, verbose=None):
-    """ Apply forward model and return data, times, ch_names
-    """
+    """Apply forward model and return data, times, ch_names."""
     if not is_fixed_orient(fwd):
         raise ValueError('Only fixed-orientation forward operators are '
                          'supported.')
@@ -1112,10 +1190,9 @@ def _apply_forward(fwd, stc, start=None, stop=None, verbose=None):
 
 
 @verbose
-def apply_forward(fwd, stc, info, start=None, stop=None,
+def apply_forward(fwd, stc, info, start=None, stop=None, use_cps=None,
                   verbose=None):
-    """
-    Project source space currents to sensor space using a forward operator.
+    """Project source space currents to sensor space using a forward operator.
 
     The sensor space data is computed for all channels present in fwd. Use
     pick_channels_forward or pick_types_forward to restrict the solution to a
@@ -1130,7 +1207,7 @@ def apply_forward(fwd, stc, info, start=None, stop=None,
     Parameters
     ----------
     fwd : Forward
-        Forward operator to use. Has to be fixed-orientation.
+        Forward operator to use.
     stc : SourceEstimate
         The source estimate from which the sensor space data is computed.
     info : instance of Info
@@ -1139,8 +1216,14 @@ def apply_forward(fwd, stc, info, start=None, stop=None,
         Index of first time sample (index not time is seconds).
     stop : int, optional
         Index of first time sample not to include (index not time is seconds).
+    use_cps : None | bool (default None)
+        Whether to use cortical patch statistics to define normal
+        orientations when converting to fixed orientation (if necessary).
+
+        .. versionadded:: 0.15
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -1158,6 +1241,8 @@ def apply_forward(fwd, stc, info, start=None, stop=None,
                              'evoked_template.' % ch_name)
 
     # project the source estimate to the sensor space
+    if not is_fixed_orient(fwd):
+        fwd = convert_forward_solution(fwd, force_fixed=True, use_cps=use_cps)
     data, times = _apply_forward(fwd, stc, start, stop)
 
     # fill the measurement info
@@ -1176,7 +1261,7 @@ def apply_forward(fwd, stc, info, start=None, stop=None,
 @verbose
 def apply_forward_raw(fwd, stc, info, start=None, stop=None,
                       verbose=None):
-    """Project source space currents to sensor space using a forward operator
+    """Project source space currents to sensor space using a forward operator.
 
     The sensor space data is computed for all channels present in fwd. Use
     pick_channels_forward or pick_types_forward to restrict the solution to a
@@ -1200,7 +1285,8 @@ def apply_forward_raw(fwd, stc, info, start=None, stop=None,
     stop : int, optional
         Index of first time sample not to include (index not time is seconds).
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -1235,7 +1321,7 @@ def apply_forward_raw(fwd, stc, info, start=None, stop=None,
 
 
 def restrict_forward_to_stc(fwd, stc):
-    """Restricts forward operator to active sources in a source estimate
+    """Restrict forward operator to active sources in a source estimate.
 
     Parameters
     ----------
@@ -1253,7 +1339,6 @@ def restrict_forward_to_stc(fwd, stc):
     --------
     restrict_forward_to_label
     """
-
     fwd_out = deepcopy(fwd)
     src_sel = _stc_src_sel(fwd['src'], stc)
 
@@ -1262,27 +1347,46 @@ def restrict_forward_to_stc(fwd, stc):
 
     if is_fixed_orient(fwd):
         idx = src_sel
+        if fwd['sol_grad'] is not None:
+            idx_grad = (3 * src_sel[:, None] + np.arange(3)).ravel()
     else:
         idx = (3 * src_sel[:, None] + np.arange(3)).ravel()
+        if fwd['sol_grad'] is not None:
+            idx_grad = (9 * src_sel[:, None] + np.arange(9)).ravel()
 
     fwd_out['source_nn'] = fwd['source_nn'][idx]
     fwd_out['sol']['data'] = fwd['sol']['data'][:, idx]
+    if fwd['sol_grad'] is not None:
+        fwd_out['sol_grad']['data'] = fwd['sol_grad']['data'][:, idx_grad]
     fwd_out['sol']['ncol'] = len(idx)
 
+    if is_fixed_orient(fwd, orig=True):
+        idx = src_sel
+        if fwd['sol_grad'] is not None:
+            idx_grad = (3 * src_sel[:, None] + np.arange(3)).ravel()
+    else:
+        idx = (3 * src_sel[:, None] + np.arange(3)).ravel()
+        if fwd['sol_grad'] is not None:
+            idx_grad = (9 * src_sel[:, None] + np.arange(9)).ravel()
+
+    fwd_out['_orig_sol'] = fwd['_orig_sol'][:, idx]
+    if fwd['sol_grad'] is not None:
+        fwd_out['_orig_sol_grad'] = fwd['_orig_sol_grad'][:, idx_grad]
+
     for i in range(2):
         fwd_out['src'][i]['vertno'] = stc.vertices[i]
         fwd_out['src'][i]['nuse'] = len(stc.vertices[i])
         fwd_out['src'][i]['inuse'] = fwd['src'][i]['inuse'].copy()
         fwd_out['src'][i]['inuse'].fill(0)
         fwd_out['src'][i]['inuse'][stc.vertices[i]] = 1
-        fwd_out['src'][i]['use_tris'] = np.array([], int)
+        fwd_out['src'][i]['use_tris'] = np.array([[]], int)
         fwd_out['src'][i]['nuse_tri'] = np.array([0])
 
     return fwd_out
 
 
 def restrict_forward_to_label(fwd, labels):
-    """Restricts forward operator to labels
+    """Restrict forward operator to labels.
 
     Parameters
     ----------
@@ -1320,6 +1424,12 @@ def restrict_forward_to_label(fwd, labels):
     fwd_out['nsource'] = 0
     fwd_out['source_nn'] = np.zeros((0, 3))
     fwd_out['sol']['data'] = np.zeros((fwd['sol']['data'].shape[0], 0))
+    fwd_out['_orig_sol'] = np.zeros((fwd['_orig_sol'].shape[0], 0))
+    if fwd['sol_grad'] is not None:
+        fwd_out['sol_grad']['data'] = np.zeros(
+            (fwd['sol_grad']['data'].shape[0], 0))
+        fwd_out['_orig_sol_grad'] = np.zeros(
+            (fwd['_orig_sol_grad'].shape[0], 0))
     fwd_out['sol']['ncol'] = 0
     nuse_lh = fwd['src'][0]['nuse']
 
@@ -1328,7 +1438,7 @@ def restrict_forward_to_label(fwd, labels):
         fwd_out['src'][i]['nuse'] = 0
         fwd_out['src'][i]['inuse'] = fwd['src'][i]['inuse'].copy()
         fwd_out['src'][i]['inuse'].fill(0)
-        fwd_out['src'][i]['use_tris'] = np.array([], int)
+        fwd_out['src'][i]['use_tris'] = np.array([[]], int)
         fwd_out['src'][i]['nuse_tri'] = np.array([0])
 
         # src_sel is idx to cols in fwd that are in any label per hemi
@@ -1349,15 +1459,39 @@ def restrict_forward_to_label(fwd, labels):
 
         if is_fixed_orient(fwd):
             idx = src_sel
+            if fwd['sol_grad'] is not None:
+                idx_grad = (3 * src_sel[:, None] + np.arange(3)).ravel()
         else:
             idx = (3 * src_sel[:, None] + np.arange(3)).ravel()
+            if fwd['sol_grad'] is not None:
+                idx_grad = (9 * src_sel[:, None] + np.arange(9)).ravel()
 
-        fwd_out['source_nn'] = np.vstack([fwd_out['source_nn'],
-                                          fwd['source_nn'][idx]])
-        fwd_out['sol']['data'] = np.hstack([fwd_out['sol']['data'],
-                                            fwd['sol']['data'][:, idx]])
+        fwd_out['source_nn'] = np.vstack(
+            [fwd_out['source_nn'], fwd['source_nn'][idx]])
+        fwd_out['sol']['data'] = np.hstack(
+            [fwd_out['sol']['data'], fwd['sol']['data'][:, idx]])
+        if fwd['sol_grad'] is not None:
+            fwd_out['sol_grad']['data'] = np.hstack(
+                [fwd_out['sol_grad']['data'],
+                 fwd['sol_rad']['data'][:, idx_grad]])
         fwd_out['sol']['ncol'] += len(idx)
 
+        if is_fixed_orient(fwd, orig=True):
+            idx = src_sel
+            if fwd['sol_grad'] is not None:
+                idx_grad = (3 * src_sel[:, None] + np.arange(3)).ravel()
+        else:
+            idx = (3 * src_sel[:, None] + np.arange(3)).ravel()
+            if fwd['sol_grad'] is not None:
+                idx_grad = (9 * src_sel[:, None] + np.arange(9)).ravel()
+
+        fwd_out['_orig_sol'] = np.hstack(
+            [fwd_out['_orig_sol'], fwd['_orig_sol'][:, idx]])
+        if fwd['sol_grad'] is not None:
+            fwd_out['_orig_sol_grad'] = np.hstack(
+                [fwd_out['_orig_sol_grad'],
+                 fwd['_orig_sol_grad'][:, idx_grad]])
+
     return fwd_out
 
 
@@ -1366,7 +1500,7 @@ def _do_forward_solution(subject, meas, fname=None, src=None, spacing=None,
                          eeg=True, meg=True, fixed=False, grad=False,
                          mricoord=False, overwrite=False, subjects_dir=None,
                          verbose=None):
-    """Calculate a forward solution for a subject using MNE-C routines
+    """Calculate a forward solution for a subject using MNE-C routines.
 
     This is kept around for testing purposes.
 
@@ -1423,11 +1557,12 @@ def _do_forward_solution(subject, meas, fname=None, src=None, spacing=None,
     subjects_dir : None | str
         Override the SUBJECTS_DIR environment variable.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     See Also
     --------
-    forward.make_forward_solution
+    make_forward_solution
 
     Returns
     -------
@@ -1450,7 +1585,7 @@ def _do_forward_solution(subject, meas, fname=None, src=None, spacing=None,
     if isinstance(meas, string_types):
         if not op.isfile(meas):
             raise IOError('measurement file "%s" could not be found' % meas)
-    elif isinstance(meas, (_BaseRaw, _BaseEpochs, Evoked)):
+    elif isinstance(meas, (BaseRaw, BaseEpochs, Evoked)):
         meas_file = op.join(temp_dir, 'info.fif')
         write_info(meas_file, meas.info)
         meas = meas_file
@@ -1528,7 +1663,7 @@ def _do_forward_solution(subject, meas, fname=None, src=None, spacing=None,
             pass  # spacing in mm
         else:
             # allow both "ico4" and "ico-4" style values
-            match = re.match("(oct|ico)-?(\d+)$", spacing)
+            match = re.match(r"(oct|ico)-?(\d+)$", spacing)
             if match is None:
                 raise ValueError("Invalid spacing parameter: %r" % spacing)
             spacing = '-'.join(match.groups())
@@ -1573,7 +1708,7 @@ def _do_forward_solution(subject, meas, fname=None, src=None, spacing=None,
 
 @verbose
 def average_forward_solutions(fwds, weights=None):
-    """Average forward solutions
+    """Average forward solutions.
 
     Parameters
     ----------
diff --git a/mne/forward/tests/test_field_interpolation.py b/mne/forward/tests/test_field_interpolation.py
index 0393ac7..4e09a3c 100644
--- a/mne/forward/tests/test_field_interpolation.py
+++ b/mne/forward/tests/test_field_interpolation.py
@@ -1,25 +1,23 @@
-from functools import partial
 from os import path as op
 
 import numpy as np
 from numpy.polynomial import legendre
 from numpy.testing import (assert_allclose, assert_array_equal, assert_equal,
                            assert_array_almost_equal)
+from scipy.interpolate import interp1d
 from nose.tools import assert_raises, assert_true
+import pytest
 
 from mne.forward import _make_surface_mapping, make_field_map
 from mne.forward._lead_dots import (_comp_sum_eeg, _comp_sums_meg,
-                                    _get_legen_table,
-                                    _get_legen_lut_fast,
-                                    _get_legen_lut_accurate,
-                                    _do_cross_dots)
+                                    _get_legen_table, _do_cross_dots)
 from mne.forward._make_forward import _create_meg_coils
 from mne.forward._field_interpolation import _setup_dots
 from mne.surface import get_meg_helmet_surf, get_head_surf
 from mne.datasets import testing
 from mne import read_evokeds, pick_types
 from mne.externals.six.moves import zip
-from mne.utils import run_tests_if_main, slow_test
+from mne.utils import run_tests_if_main
 
 
 base_dir = op.join(op.dirname(__file__), '..', '..', 'io', 'tests', 'data')
@@ -32,7 +30,7 @@ subjects_dir = op.join(data_path, 'subjects')
 
 
 def test_legendre_val():
-    """Test Legendre polynomial (derivative) equivalence"""
+    """Test Legendre polynomial (derivative) equivalence."""
     rng = np.random.RandomState(0)
     # check table equiv
     xs = np.linspace(-1., 1., 1000)
@@ -42,10 +40,11 @@ def test_legendre_val():
     vals_np = legendre.legvander(xs, n_terms - 1)
 
     # Table approximation
-    for fun, nc in zip([_get_legen_lut_fast, _get_legen_lut_accurate],
-                       [100, 50]):
+    for nc, interp in zip([100, 50], ['nearest', 'linear']):
         lut, n_fact = _get_legen_table('eeg', n_coeff=nc, force_calc=True)
-        vals_i = fun(xs, lut)
+        lut_fun = interp1d(np.linspace(-1, 1, lut.shape[0]), lut, interp,
+                           axis=0)
+        vals_i = lut_fun(xs)
         # Need a "1:" here because we omit the first coefficient in our table!
         assert_allclose(vals_np[:, 1:vals_i.shape[1] + 1], vals_i,
                         rtol=1e-2, atol=5e-3)
@@ -53,7 +52,6 @@ def test_legendre_val():
         # Now let's look at our sums
         ctheta = rng.rand(20, 30) * 2.0 - 1.0
         beta = rng.rand(20, 30) * 0.8
-        lut_fun = partial(fun, lut=lut)
         c1 = _comp_sum_eeg(beta.flatten(), ctheta.flatten(), lut_fun, n_fact)
         c1.shape = beta.shape
 
@@ -74,15 +72,15 @@ def test_legendre_val():
     ctheta = rng.rand(20 * 30) * 2.0 - 1.0
     beta = rng.rand(20 * 30) * 0.8
     lut, n_fact = _get_legen_table('meg', n_coeff=10, force_calc=True)
-    fun = partial(_get_legen_lut_fast, lut=lut)
+    fun = interp1d(np.linspace(-1, 1, lut.shape[0]), lut, 'nearest', axis=0)
     coeffs = _comp_sums_meg(beta, ctheta, fun, n_fact, False)
     lut, n_fact = _get_legen_table('meg', n_coeff=20, force_calc=True)
-    fun = partial(_get_legen_lut_accurate, lut=lut)
+    fun = interp1d(np.linspace(-1, 1, lut.shape[0]), lut, 'linear', axis=0)
     coeffs = _comp_sums_meg(beta, ctheta, fun, n_fact, False)
 
 
 def test_legendre_table():
-    """Test Legendre table calculation"""
+    """Test Legendre table calculation."""
     # double-check our table generation
     n = 10
     for ch_type in ['eeg', 'meg']:
@@ -96,7 +94,7 @@ def test_legendre_table():
 
 @testing.requires_testing_data
 def test_make_field_map_eeg():
-    """Test interpolation of EEG field onto head"""
+    """Test interpolation of EEG field onto head."""
     evoked = read_evokeds(evoked_fname, condition='Left Auditory')
     evoked.info['bads'] = ['MEG 2443', 'EEG 053']  # add some bads
     surf = get_head_surf('sample', subjects_dir=subjects_dir)
@@ -119,9 +117,9 @@ def test_make_field_map_eeg():
 
 
 @testing.requires_testing_data
- at slow_test
+ at pytest.mark.slowtest
 def test_make_field_map_meg():
-    """Test interpolation of MEG field onto helmet | head"""
+    """Test interpolation of MEG field onto helmet | head."""
     evoked = read_evokeds(evoked_fname, condition='Left Auditory')
     info = evoked.info
     surf = get_meg_helmet_surf(info)
@@ -172,24 +170,27 @@ def test_make_field_map_meg():
 
 @testing.requires_testing_data
 def test_make_field_map_meeg():
-    """Test making a M/EEG field map onto helmet & head"""
+    """Test making a M/EEG field map onto helmet & head."""
     evoked = read_evokeds(evoked_fname, baseline=(-0.2, 0.0))[0]
     picks = pick_types(evoked.info, meg=True, eeg=True)
     picks = picks[::10]
     evoked.pick_channels([evoked.ch_names[p] for p in picks])
     evoked.info.normalize_proj()
     maps = make_field_map(evoked, trans_fname, subject='sample',
-                          subjects_dir=subjects_dir, n_jobs=1)
+                          subjects_dir=subjects_dir, n_jobs=1, verbose='debug')
     assert_equal(maps[0]['data'].shape, (642, 6))  # EEG->Head
     assert_equal(maps[1]['data'].shape, (304, 31))  # MEG->Helmet
     # reasonable ranges
-    for map_ in maps:
-        assert_true(0.5 < map_['data'].max() < 2)
-        assert_true(-2 < map_['data'].min() < -0.5)
+    maxs = (1.2, 2.0)  # before #4418, was (1.1, 2.0)
+    mins = (-0.8, -1.3)  # before #4418, was (-0.6, -1.2)
+    assert_equal(len(maxs), len(maps))
+    for map_, max_, min_ in zip(maps, maxs, mins):
+        assert_allclose(map_['data'].max(), max_, rtol=5e-2)
+        assert_allclose(map_['data'].min(), min_, rtol=5e-2)
     # calculated from correct looking mapping on 2015/12/26
-    assert_allclose(np.sqrt(np.sum(maps[0]['data'] ** 2)), 16.6088,
+    assert_allclose(np.sqrt(np.sum(maps[0]['data'] ** 2)), 19.0903,  # 16.6088,
                     atol=1e-3, rtol=1e-3)
-    assert_allclose(np.sqrt(np.sum(maps[1]['data'] ** 2)), 20.1245,
+    assert_allclose(np.sqrt(np.sum(maps[1]['data'] ** 2)), 19.4748,  # 20.1245,
                     atol=1e-3, rtol=1e-3)
 
 
@@ -206,7 +207,6 @@ def _setup_args(info):
 @testing.requires_testing_data
 def test_as_meg_type_evoked():
     """Test interpolation of data on to virtual channels."""
-
     # validation tests
     evoked = read_evokeds(evoked_fname, condition='Left Auditory')
     assert_raises(ValueError, evoked.as_type, 'meg')
@@ -218,7 +218,7 @@ def test_as_meg_type_evoked():
     virt_evoked = evoked.copy().pick_channels(ch_names=ch_names[:10:1])
     virt_evoked.info.normalize_proj()
     virt_evoked = virt_evoked.as_type('mag')
-    assert_true(all('_virtual' in ch for ch in virt_evoked.info['ch_names']))
+    assert_true(all(ch.endswith('_v') for ch in virt_evoked.info['ch_names']))
 
     # pick from and to channels
     evoked_from = evoked.copy().pick_channels(ch_names=ch_names[2:10:3])
@@ -243,4 +243,5 @@ def test_as_meg_type_evoked():
     data2 = evoked.as_type('grad').data.ravel()
     assert_true(np.corrcoef(data1, data2)[0, 1] > 0.95)
 
+
 run_tests_if_main()
diff --git a/mne/forward/tests/test_forward.py b/mne/forward/tests/test_forward.py
index 3f1f330..784c6f4 100644
--- a/mne/forward/tests/test_forward.py
+++ b/mne/forward/tests/test_forward.py
@@ -4,6 +4,7 @@ import warnings
 import gc
 
 from nose.tools import assert_true, assert_raises
+import pytest
 import numpy as np
 from numpy.testing import (assert_array_almost_equal, assert_equal,
                            assert_array_equal, assert_allclose)
@@ -16,9 +17,9 @@ from mne import (read_forward_solution, apply_forward, apply_forward_raw,
 from mne.tests.common import assert_naming
 from mne.label import read_label
 from mne.utils import (requires_mne, run_subprocess, _TempDir,
-                       run_tests_if_main, slow_test)
+                       run_tests_if_main)
 from mne.forward import (restrict_forward_to_stc, restrict_forward_to_label,
-                         Forward)
+                         Forward, is_fixed_orient)
 
 data_path = testing.data_path(download=False)
 fname_meeg = op.join(data_path, 'MEG', 'sample',
@@ -41,11 +42,13 @@ def compare_forwards(f1, f2):
     """Helper to compare two potentially converted forward solutions"""
     assert_allclose(f1['sol']['data'], f2['sol']['data'])
     assert_equal(f1['sol']['ncol'], f2['sol']['ncol'])
+    assert_equal(f1['sol']['ncol'], f1['sol']['data'].shape[1])
     assert_allclose(f1['source_nn'], f2['source_nn'])
     if f1['sol_grad'] is not None:
         assert_true(f2['sol_grad'] is not None)
         assert_allclose(f1['sol_grad']['data'], f2['sol_grad']['data'])
         assert_equal(f1['sol_grad']['ncol'], f2['sol_grad']['ncol'])
+        assert_equal(f1['sol_grad']['ncol'], f1['sol_grad']['data'].shape[1])
     else:
         assert_true(f2['sol_grad'] is None)
     assert_equal(f1['source_ori'], f2['source_ori'])
@@ -64,29 +67,48 @@ def test_convert_forward():
     assert_true(isinstance(fwd, Forward))
     # look at surface orientation
     fwd_surf = convert_forward_solution(fwd, surf_ori=True)
-    fwd_surf_io = read_forward_solution(fname_meeg_grad, surf_ori=True)
+
+    # The following test can be removed in 0.16
+    # Capture warning when using surf_ori in reading
+    with warnings.catch_warnings(record=True):
+        warnings.simplefilter('always')
+        fwd_surf_io = read_forward_solution(fname_meeg_grad, surf_ori=True)
     compare_forwards(fwd_surf, fwd_surf_io)
     del fwd_surf_io
     gc.collect()
+
     # go back
     fwd_new = convert_forward_solution(fwd_surf, surf_ori=False)
     assert_true(repr(fwd_new))
     assert_true(isinstance(fwd_new, Forward))
     compare_forwards(fwd, fwd_new)
+    del fwd_new
+    gc.collect()
+
     # now go to fixed
-    fwd_fixed = convert_forward_solution(fwd_surf, surf_ori=False,
-                                         force_fixed=True)
+    fwd_fixed = convert_forward_solution(fwd_surf, surf_ori=True,
+                                         force_fixed=True, use_cps=False)
     del fwd_surf
     gc.collect()
     assert_true(repr(fwd_fixed))
     assert_true(isinstance(fwd_fixed, Forward))
-    fwd_fixed_io = read_forward_solution(fname_meeg_grad, surf_ori=False,
-                                         force_fixed=True)
+    assert_true(is_fixed_orient(fwd_fixed))
+
+    # The following test can be removed in 0.16
+    # Capture warning when using force_fixed in reading
+    with warnings.catch_warnings(record=True):
+        warnings.simplefilter('always')
+        fwd_fixed_io = read_forward_solution(fname_meeg_grad, force_fixed=True)
+    assert_true(repr(fwd_fixed_io))
+    assert_true(isinstance(fwd_fixed_io, Forward))
+    assert_true(is_fixed_orient(fwd_fixed_io))
     compare_forwards(fwd_fixed, fwd_fixed_io)
     del fwd_fixed_io
     gc.collect()
+
     # now go back to cartesian (original condition)
-    fwd_new = convert_forward_solution(fwd_fixed)
+    fwd_new = convert_forward_solution(fwd_fixed, surf_ori=False,
+                                       force_fixed=False)
     assert_true(repr(fwd_new))
     assert_true(isinstance(fwd_new, Forward))
     compare_forwards(fwd, fwd_new)
@@ -94,7 +116,7 @@ def test_convert_forward():
     gc.collect()
 
 
- at slow_test
+ at pytest.mark.slowtest
 @testing.requires_testing_data
 def test_io_forward():
     """Test IO for forward solutions
@@ -104,15 +126,20 @@ def test_io_forward():
     n_channels, n_src = 366, 108
     fwd = read_forward_solution(fname_meeg_grad)
     assert_true(isinstance(fwd, Forward))
-    fwd = read_forward_solution(fname_meeg_grad, surf_ori=True)
+    fwd = read_forward_solution(fname_meeg_grad)
+    fwd = convert_forward_solution(fwd, surf_ori=True)
     leadfield = fwd['sol']['data']
     assert_equal(leadfield.shape, (n_channels, n_src))
     assert_equal(len(fwd['sol']['row_names']), n_channels)
     fname_temp = op.join(temp_dir, 'test-fwd.fif')
-    write_forward_solution(fname_temp, fwd, overwrite=True)
+    with warnings.catch_warnings(record=True):
+        warnings.simplefilter('always')
+        write_forward_solution(fname_temp, fwd, overwrite=True)
 
-    fwd = read_forward_solution(fname_meeg_grad, surf_ori=True)
-    fwd_read = read_forward_solution(fname_temp, surf_ori=True)
+    fwd = read_forward_solution(fname_meeg_grad)
+    fwd = convert_forward_solution(fwd, surf_ori=True)
+    fwd_read = read_forward_solution(fname_temp)
+    fwd_read = convert_forward_solution(fwd_read, surf_ori=True)
     leadfield = fwd_read['sol']['data']
     assert_equal(leadfield.shape, (n_channels, n_src))
     assert_equal(len(fwd_read['sol']['row_names']), n_channels)
@@ -121,7 +148,43 @@ def test_io_forward():
     assert_true('mri_head_t' in fwd_read)
     assert_array_almost_equal(fwd['sol']['data'], fwd_read['sol']['data'])
 
-    fwd = read_forward_solution(fname_meeg_grad, force_fixed=True)
+    fwd = read_forward_solution(fname_meeg)
+    fwd = convert_forward_solution(fwd, surf_ori=True, force_fixed=True,
+                                   use_cps=False)
+    with warnings.catch_warnings(record=True) as w:
+        warnings.simplefilter('always')
+        write_forward_solution(fname_temp, fwd, overwrite=True)
+    fwd_read = read_forward_solution(fname_temp)
+    fwd_read = convert_forward_solution(fwd_read, surf_ori=True,
+                                        force_fixed=True, use_cps=False)
+    assert_true(repr(fwd_read))
+    assert_true(isinstance(fwd_read, Forward))
+    assert_true(is_fixed_orient(fwd_read))
+    compare_forwards(fwd, fwd_read)
+
+    fwd = convert_forward_solution(fwd, surf_ori=True, force_fixed=True,
+                                   use_cps=True)
+    leadfield = fwd['sol']['data']
+    assert_equal(leadfield.shape, (n_channels, 1494 / 3))
+    assert_equal(len(fwd['sol']['row_names']), n_channels)
+    assert_equal(len(fwd['info']['chs']), n_channels)
+    assert_true('dev_head_t' in fwd['info'])
+    assert_true('mri_head_t' in fwd)
+    assert_true(fwd['surf_ori'])
+    with warnings.catch_warnings(record=True) as w:
+        warnings.simplefilter('always')
+        write_forward_solution(fname_temp, fwd, overwrite=True)
+    fwd_read = read_forward_solution(fname_temp)
+    fwd_read = convert_forward_solution(fwd_read, surf_ori=True,
+                                        force_fixed=True, use_cps=True)
+    assert_true(repr(fwd_read))
+    assert_true(isinstance(fwd_read, Forward))
+    assert_true(is_fixed_orient(fwd_read))
+    compare_forwards(fwd, fwd_read)
+
+    fwd = read_forward_solution(fname_meeg_grad)
+    fwd = convert_forward_solution(fwd, surf_ori=True, force_fixed=True,
+                                   use_cps=True)
     leadfield = fwd['sol']['data']
     assert_equal(leadfield.shape, (n_channels, n_src / 3))
     assert_equal(len(fwd['sol']['row_names']), n_channels)
@@ -129,6 +192,16 @@ def test_io_forward():
     assert_true('dev_head_t' in fwd['info'])
     assert_true('mri_head_t' in fwd)
     assert_true(fwd['surf_ori'])
+    with warnings.catch_warnings(record=True) as w:
+        warnings.simplefilter('always')
+        write_forward_solution(fname_temp, fwd, overwrite=True)
+    fwd_read = read_forward_solution(fname_temp)
+    fwd_read = convert_forward_solution(fwd_read, surf_ori=True,
+                                        force_fixed=True, use_cps=True)
+    assert_true(repr(fwd_read))
+    assert_true(isinstance(fwd_read, Forward))
+    assert_true(is_fixed_orient(fwd_read))
+    compare_forwards(fwd, fwd_read)
 
     # test warnings on bad filenames
     fwd = read_forward_solution(fname_meeg_grad)
@@ -155,7 +228,9 @@ def test_apply_forward():
     sfreq = 10.0
     t_start = 0.123
 
-    fwd = read_forward_solution(fname_meeg, force_fixed=True)
+    fwd = read_forward_solution(fname_meeg)
+    fwd = convert_forward_solution(fwd, surf_ori=True, force_fixed=True,
+                                   use_cps=True)
     fwd = pick_types_forward(fwd, meg=True)
     assert_true(isinstance(fwd, Forward))
 
@@ -204,7 +279,9 @@ def test_restrict_forward_to_stc():
     sfreq = 10.0
     t_start = 0.123
 
-    fwd = read_forward_solution(fname_meeg, force_fixed=True)
+    fwd = read_forward_solution(fname_meeg)
+    fwd = convert_forward_solution(fwd, surf_ori=True, force_fixed=True,
+                                   use_cps=True)
     fwd = pick_types_forward(fwd, meg=True)
 
     vertno = [fwd['src'][0]['vertno'][0:15], fwd['src'][1]['vertno'][0:5]]
@@ -220,7 +297,8 @@ def test_restrict_forward_to_stc():
     assert_equal(fwd_out['src'][0]['vertno'], fwd['src'][0]['vertno'][0:15])
     assert_equal(fwd_out['src'][1]['vertno'], fwd['src'][1]['vertno'][0:5])
 
-    fwd = read_forward_solution(fname_meeg, force_fixed=False)
+    fwd = read_forward_solution(fname_meeg)
+    fwd = convert_forward_solution(fwd, surf_ori=True, force_fixed=False)
     fwd = pick_types_forward(fwd, meg=True)
 
     vertno = [fwd['src'][0]['vertno'][0:15], fwd['src'][1]['vertno'][0:5]]
@@ -235,12 +313,26 @@ def test_restrict_forward_to_stc():
     assert_equal(fwd_out['src'][0]['vertno'], fwd['src'][0]['vertno'][0:15])
     assert_equal(fwd_out['src'][1]['vertno'], fwd['src'][1]['vertno'][0:5])
 
+    # Test saving the restricted forward object. This only works if all fields
+    # are properly accounted for.
+    temp_dir = _TempDir()
+    fname_copy = op.join(temp_dir, 'copy-fwd.fif')
+    with warnings.catch_warnings(record=True):
+        warnings.simplefilter('always')
+        write_forward_solution(fname_copy, fwd_out, overwrite=True)
+    fwd_out_read = read_forward_solution(fname_copy)
+    fwd_out_read = convert_forward_solution(fwd_out_read, surf_ori=True,
+                                            force_fixed=False)
+    compare_forwards(fwd_out, fwd_out_read)
+
 
 @testing.requires_testing_data
 def test_restrict_forward_to_label():
     """Test restriction of source space to label
     """
-    fwd = read_forward_solution(fname_meeg, force_fixed=True)
+    fwd = read_forward_solution(fname_meeg)
+    fwd = convert_forward_solution(fwd, surf_ori=True, force_fixed=True,
+                                   use_cps=True)
     fwd = pick_types_forward(fwd, meg=True)
 
     label_path = op.join(data_path, 'MEG', 'sample', 'labels')
@@ -266,7 +358,7 @@ def test_restrict_forward_to_label():
     assert_equal(fwd_out['src'][0]['vertno'], vertno_lh)
     assert_equal(fwd_out['src'][1]['vertno'], vertno_rh)
 
-    fwd = read_forward_solution(fname_meeg, force_fixed=False)
+    fwd = read_forward_solution(fname_meeg)
     fwd = pick_types_forward(fwd, meg=True)
 
     label_path = op.join(data_path, 'MEG', 'sample', 'labels')
@@ -293,6 +385,14 @@ def test_restrict_forward_to_label():
     assert_equal(fwd_out['src'][0]['vertno'], vertno_lh)
     assert_equal(fwd_out['src'][1]['vertno'], vertno_rh)
 
+    # Test saving the restricted forward object. This only works if all fields
+    # are properly accounted for.
+    temp_dir = _TempDir()
+    fname_copy = op.join(temp_dir, 'copy-fwd.fif')
+    write_forward_solution(fname_copy, fwd_out, overwrite=True)
+    fwd_out_read = read_forward_solution(fname_copy)
+    compare_forwards(fwd_out, fwd_out_read)
+
 
 @testing.requires_testing_data
 @requires_mne
diff --git a/mne/forward/tests/test_make_forward.py b/mne/forward/tests/test_make_forward.py
index ca6b816..eebacaa 100644
--- a/mne/forward/tests/test_make_forward.py
+++ b/mne/forward/tests/test_make_forward.py
@@ -6,6 +6,7 @@ import os.path as op
 import warnings
 
 from nose.tools import assert_raises, assert_true
+import pytest
 import numpy as np
 from numpy.testing import (assert_equal, assert_allclose)
 
@@ -18,14 +19,14 @@ from mne import (read_forward_solution, make_forward_solution,
                  pick_types_forward, pick_info, pick_types, Transform,
                  read_evokeds, read_cov, read_dipole)
 from mne.utils import (requires_mne, requires_nibabel, _TempDir,
-                       run_tests_if_main, slow_test, run_subprocess)
+                       run_tests_if_main, run_subprocess)
 from mne.forward._make_forward import _create_meg_coils, make_forward_dipole
 from mne.forward._compute_forward import _magnetic_dipole_field_vec
 from mne.forward import Forward, _do_forward_solution
 from mne.dipole import Dipole, fit_dipole
 from mne.simulation import simulate_evoked
 from mne.source_estimate import VolSourceEstimate
-from mne.source_space import (get_volume_labels_from_aseg,
+from mne.source_space import (get_volume_labels_from_aseg, write_source_spaces,
                               _compare_source_spaces, setup_source_space)
 
 data_path = testing.data_path(download=False)
@@ -50,16 +51,16 @@ fname_bem_meg = op.join(subjects_dir, 'sample', 'bem',
 def _compare_forwards(fwd, fwd_py, n_sensors, n_src,
                       meg_rtol=1e-4, meg_atol=1e-9,
                       eeg_rtol=1e-3, eeg_atol=1e-3):
-    """Helper to test forwards"""
+    """Test forwards."""
     # check source spaces
     assert_equal(len(fwd['src']), len(fwd_py['src']))
     _compare_source_spaces(fwd['src'], fwd_py['src'], mode='approx')
     for surf_ori, force_fixed in product([False, True], [False, True]):
         # use copy here to leave our originals unmodified
-        fwd = convert_forward_solution(fwd, surf_ori, force_fixed,
-                                       copy=True)
+        fwd = convert_forward_solution(fwd, surf_ori, force_fixed, copy=True,
+                                       use_cps=True)
         fwd_py = convert_forward_solution(fwd_py, surf_ori, force_fixed,
-                                          copy=True)
+                                          copy=True, use_cps=True)
         check_src = n_src // 3 if force_fixed else n_src
 
         for key in ('nchan', 'source_rr', 'source_ori',
@@ -94,17 +95,16 @@ def _compare_forwards(fwd, fwd_py, n_sensors, n_src,
 
 
 def test_magnetic_dipole():
-    """Test basic magnetic dipole forward calculation
-    """
-    trans = Transform('mri', 'head', np.eye(4))
+    """Test basic magnetic dipole forward calculation."""
+    trans = Transform('mri', 'head')
     info = read_info(fname_raw)
     picks = pick_types(info, meg=True, eeg=False, exclude=[])
     info = pick_info(info, picks[:12])
     coils = _create_meg_coils(info['chs'], 'normal', trans)
-    # magnetic dipole at device origin
+    # magnetic dipole far (meters!) from device origin
     r0 = np.array([0., 13., -6.])
     for ch, coil in zip(info['chs'], coils):
-        rr = (ch['loc'][:3] + r0) / 2.
+        rr = (ch['loc'][:3] + r0) / 2.  # get halfway closer
         far_fwd = _magnetic_dipole_field_vec(r0[np.newaxis, :], [coil])
         near_fwd = _magnetic_dipole_field_vec(rr[np.newaxis, :], [coil])
         ratio = 8. if ch['ch_name'][-1] == '1' else 16.  # grad vs mag
@@ -114,8 +114,7 @@ def test_magnetic_dipole():
 @testing.requires_testing_data
 @requires_mne
 def test_make_forward_solution_kit():
-    """Test making fwd using KIT, BTI, and CTF (compensated) files
-    """
+    """Test making fwd using KIT, BTI, and CTF (compensated) files."""
     kit_dir = op.join(op.dirname(__file__), '..', '..', 'io', 'kit',
                       'tests', 'data')
     sqd_path = op.join(kit_dir, 'test.sqd')
@@ -138,8 +137,9 @@ def test_make_forward_solution_kit():
     # first set up a small testing source space
     temp_dir = _TempDir()
     fname_src_small = op.join(temp_dir, 'sample-oct-2-src.fif')
-    src = setup_source_space('sample', fname_src_small, 'oct2',
-                             subjects_dir=subjects_dir, add_dist=False)
+    src = setup_source_space('sample', 'oct2', subjects_dir=subjects_dir,
+                             add_dist=False)
+    write_source_spaces(fname_src_small, src)  # to enable working with MNE-C
     n_src = 108  # this is the resulting # of verts in fwd
 
     # first use mne-C: convert file, make forward solution
@@ -190,7 +190,7 @@ def test_make_forward_solution_kit():
     _compare_forwards(fwd, fwd_py, 274, n_src)
 
     # CTF with compensation changed in python
-    ctf_raw = read_raw_fif(fname_ctf_raw, add_eeg_ref=False)
+    ctf_raw = read_raw_fif(fname_ctf_raw)
     ctf_raw.apply_gradient_compensation(2)
 
     fwd_py = make_forward_solution(ctf_raw.info, fname_trans, src,
@@ -203,13 +203,13 @@ def test_make_forward_solution_kit():
     _compare_forwards(fwd, fwd_py, 274, n_src)
 
 
- at slow_test
+ at pytest.mark.slowtest
 @testing.requires_testing_data
 def test_make_forward_solution():
-    """Test making M-EEG forward solution from python
-    """
+    """Test making M-EEG forward solution from python."""
     fwd_py = make_forward_solution(fname_raw, fname_trans, fname_src,
-                                   fname_bem, mindist=5.0, eeg=True, meg=True)
+                                   fname_bem, mindist=5.0, eeg=True, meg=True,
+                                   n_jobs=-1)
     assert_true(isinstance(fwd_py, Forward))
     fwd = read_forward_solution(fname_meeg)
     assert_true(isinstance(fwd, Forward))
@@ -219,11 +219,12 @@ def test_make_forward_solution():
 @testing.requires_testing_data
 @requires_mne
 def test_make_forward_solution_sphere():
-    """Test making a forward solution with a sphere model"""
+    """Test making a forward solution with a sphere model."""
     temp_dir = _TempDir()
     fname_src_small = op.join(temp_dir, 'sample-oct-2-src.fif')
-    src = setup_source_space('sample', fname_src_small, 'oct2',
-                             subjects_dir=subjects_dir, add_dist=False)
+    src = setup_source_space('sample', 'oct2', subjects_dir=subjects_dir,
+                             add_dist=False)
+    write_source_spaces(fname_src_small, src)  # to enable working with MNE-C
     out_name = op.join(temp_dir, 'tmp-fwd.fif')
     run_subprocess(['mne_forward_solution', '--meg', '--eeg',
                     '--meas', fname_raw, '--src', fname_src_small,
@@ -244,12 +245,11 @@ def test_make_forward_solution_sphere():
                         1.0, rtol=1e-3)
 
 
- at slow_test
+ at pytest.mark.slowtest
 @testing.requires_testing_data
 @requires_nibabel(False)
 def test_forward_mixed_source_space():
-    """Test making the forward solution for a mixed source space
-    """
+    """Test making the forward solution for a mixed source space."""
     temp_dir = _TempDir()
     # get the surface source space
     surf = read_source_spaces(fname_src)
@@ -258,12 +258,10 @@ def test_forward_mixed_source_space():
     label_names = get_volume_labels_from_aseg(fname_aseg)
     vol_labels = [label_names[int(np.random.rand() * len(label_names))]
                   for _ in range(2)]
-    vol1 = setup_volume_source_space('sample', fname=None, pos=20.,
-                                     mri=fname_aseg,
+    vol1 = setup_volume_source_space('sample', pos=20., mri=fname_aseg,
                                      volume_label=vol_labels[0],
                                      add_interpolator=False)
-    vol2 = setup_volume_source_space('sample', fname=None, pos=20.,
-                                     mri=fname_aseg,
+    vol2 = setup_volume_source_space('sample', pos=20., mri=fname_aseg,
                                      volume_label=vol_labels[1],
                                      add_interpolator=False)
 
@@ -296,10 +294,10 @@ def test_forward_mixed_source_space():
                   mri_resolution=True, trans=vox_mri_t)
 
 
- at slow_test
+ at pytest.mark.slowtest
 @testing.requires_testing_data
 def test_make_forward_dipole():
-    """Test forward-projecting dipoles"""
+    """Test forward-projecting dipoles."""
     rng = np.random.RandomState(0)
 
     evoked = read_evokeds(fname_evo)[0]
@@ -337,10 +335,10 @@ def test_make_forward_dipole():
     # Now simulate evoked responses for each of the test dipoles,
     # and fit dipoles to them (sphere model, MEG and EEG)
     times, pos, amplitude, ori, gof = [], [], [], [], []
-    snr = 20.  # add a tiny amount of noise to the simulated evokeds
+    nave = 100  # add a tiny amount of noise to the simulated evokeds
     for s in stc:
         evo_test = simulate_evoked(fwd, s, info, cov,
-                                   snr=snr, random_state=rng)
+                                   nave=nave, random_state=rng)
         # evo_test.add_proj(make_eeg_average_ref_proj(evo_test.info))
         dfit, resid = fit_dipole(evo_test, cov, sphere, None)
         times += dfit.times.tolist()
diff --git a/mne/gui/__init__.py b/mne/gui/__init__.py
index ac5231f..d4aff8b 100644
--- a/mne/gui/__init__.py
+++ b/mne/gui/__init__.py
@@ -4,11 +4,23 @@
 #
 # License: BSD (3-clause)
 
-from ..utils import _check_mayavi_version
+import os
+
+from ..utils import _check_mayavi_version, verbose, get_config
+from ._backend import _testing_mode
+
+
+def _initialize_gui(frame, view=None):
+    """Initialize GUI depending on testing mode."""
+    if _testing_mode():  # open without entering mainloop
+        return frame.edit_traits(view=view), frame
+    else:
+        frame.configure_traits(view=view)
+        return frame
 
 
 def combine_kit_markers():
-    """Create a new KIT marker file by interpolating two marker files
+    """Create a new KIT marker file by interpolating two marker files.
 
     Notes
     -----
@@ -18,16 +30,20 @@ def combine_kit_markers():
     from ._backend import _check_backend
     _check_backend()
     from ._marker_gui import CombineMarkersFrame
-    gui = CombineMarkersFrame()
-    gui.configure_traits()
-    return gui
+    frame = CombineMarkersFrame()
+    return _initialize_gui(frame)
 
 
-def coregistration(tabbed=False, split=True, scene_width=500, inst=None,
-                   subject=None, subjects_dir=None):
-    """Coregister an MRI with a subject's head shape
+ at verbose
+def coregistration(tabbed=False, split=True, scene_width=None, inst=None,
+                   subject=None, subjects_dir=None, guess_mri_subject=None,
+                   scene_height=None, head_opacity=None, head_high_res=None,
+                   trans=None, scrollable=True, verbose=None):
+    """Coregister an MRI with a subject's head shape.
 
-    The recommended way to use the GUI is through bash with::
+    The recommended way to use the GUI is through bash with:
+
+    .. code-block::  bash
 
         $ mne coreg
 
@@ -40,8 +56,10 @@ def coregistration(tabbed=False, split=True, scene_width=500, inst=None,
     split : bool
         Split the main panels with a movable splitter (good for QT4 but
         unnecessary for wx backend).
-    scene_width : int
+    scene_width : int | None
         Specify a minimum width for the 3d scene (in pixels).
+        Default is None, which uses ``MNE_COREG_SCENE_WIDTH`` config value
+        (which defaults to 500).
     inst : None | str
         Path to an instance file containing the digitizer data. Compatible for
         Raw, Epochs, and Evoked files.
@@ -50,6 +68,28 @@ def coregistration(tabbed=False, split=True, scene_width=500, inst=None,
     subjects_dir : None | path
         Override the SUBJECTS_DIR environment variable
         (sys.environ['SUBJECTS_DIR'])
+    guess_mri_subject : bool
+        When selecting a new head shape file, guess the subject's name based
+        on the filename and change the MRI subject accordingly (default True).
+    scene_height : int | None
+        Specify a minimum height for the 3d scene (in pixels).
+        Default is None, which uses ``MNE_COREG_SCENE_WIDTH`` config value
+        (which defaults to 400).
+    head_opacity : float | None
+        The opacity of the head surface in the range [0., 1.].
+        Default is None, which uses ``MNE_COREG_HEAD_OPACITY`` config value
+        (which defaults to 1.).
+    head_high_res : bool | None
+        Use a high resolution head surface.
+        Default is None, which uses ``MNE_COREG_HEAD_HIGH_RES`` config value
+        (which defaults to True).
+    trans : str | None
+        The transform file to use.
+    scrollable : bool
+        Make the coregistration panel vertically scrollable (default True).
+    verbose : bool, str, int, or None
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Notes
     -----
@@ -59,18 +99,38 @@ def coregistration(tabbed=False, split=True, scene_width=500, inst=None,
     subjects for which no MRI is available
     <http://www.slideshare.net/mne-python/mnepython-scale-mri>`_.
     """
+    config = get_config(home_dir=os.environ.get('_MNE_FAKE_HOME_DIR'))
+    if guess_mri_subject is None:
+        guess_mri_subject = config.get(
+            'MNE_COREG_GUESS_MRI_SUBJECT', 'true') == 'true'
+    if head_high_res is None:
+        head_high_res = config.get('MNE_COREG_HEAD_HIGH_RES', 'true') == 'true'
+    if head_opacity is None:
+        head_opacity = config.get('MNE_COREG_HEAD_OPACITY', 1.)
+    if scene_width is None:
+        scene_width = config.get('MNE_COREG_SCENE_WIDTH', 500)
+    if scene_height is None:
+        scene_height = config.get('MNE_COREG_SCENE_HEIGHT', 400)
+    if subjects_dir is None:
+        if 'SUBJECTS_DIR' in config:
+            subjects_dir = config['SUBJECTS_DIR']
+        elif 'MNE_COREG_SUBJECTS_DIR' in config:
+            subjects_dir = config['MNE_COREG_SUBJECTS_DIR']
+    head_opacity = float(head_opacity)
+    scene_width = int(scene_width)
+    scene_height = int(scene_height)
     _check_mayavi_version()
     from ._backend import _check_backend
     _check_backend()
     from ._coreg_gui import CoregFrame, _make_view
-    view = _make_view(tabbed, split, scene_width)
-    gui = CoregFrame(inst, subject, subjects_dir)
-    gui.configure_traits(view=view)
-    return gui
+    view = _make_view(tabbed, split, scene_width, scene_height, scrollable)
+    frame = CoregFrame(inst, subject, subjects_dir, guess_mri_subject,
+                       head_opacity, head_high_res, trans, config)
+    return _initialize_gui(frame, view)
 
 
 def fiducials(subject=None, fid_file=None, subjects_dir=None):
-    """Set the fiducials for an MRI subject
+    """Set the fiducials for an MRI subject.
 
     Parameters
     ----------
@@ -91,13 +151,12 @@ def fiducials(subject=None, fid_file=None, subjects_dir=None):
     from ._backend import _check_backend
     _check_backend()
     from ._fiducials_gui import FiducialsFrame
-    gui = FiducialsFrame(subject, subjects_dir, fid_file=fid_file)
-    gui.configure_traits()
-    return gui
+    frame = FiducialsFrame(subject, subjects_dir, fid_file=fid_file)
+    return _initialize_gui(frame)
 
 
 def kit2fiff():
-    """Convert KIT files to the fiff format
+    """Convert KIT files to the fiff format.
 
     The recommended way to use the GUI is through bash with::
 
@@ -108,6 +167,5 @@ def kit2fiff():
     from ._backend import _check_backend
     _check_backend()
     from ._kit2fiff_gui import Kit2FiffFrame
-    gui = Kit2FiffFrame()
-    gui.configure_traits()
-    return gui
+    frame = Kit2FiffFrame()
+    return _initialize_gui(frame)
diff --git a/mne/gui/_backend.py b/mne/gui/_backend.py
index 9bfc0b3..f1c89d6 100644
--- a/mne/gui/_backend.py
+++ b/mne/gui/_backend.py
@@ -1,8 +1,10 @@
-"""Deal with pyface backend issues"""
+"""Deal with pyface backend issues."""
 # Author: Christian Brodbeck <christianbrodbeck at nyu.edu>
 #
 # License: BSD (3-clause)
 
+import os
+
 
 def _check_backend():
     from ..utils import _check_pyface_backend
@@ -25,3 +27,8 @@ def _check_backend():
                "recommend using qt4 which can be enabled by installing the "
                "pyside package." % backend)
     warning(None, msg, "Pyface Backend Warning")
+
+
+def _testing_mode():
+    """Determine if we're running tests."""
+    return os.getenv('_MNE_GUI_TESTING_MODE', '') == 'true'
diff --git a/mne/gui/_coreg_gui.py b/mne/gui/_coreg_gui.py
index cb9d522..06c49bf 100644
--- a/mne/gui/_coreg_gui.py
+++ b/mne/gui/_coreg_gui.py
@@ -1,4 +1,34 @@
-"""Traits-based GUI for head-MRI coregistration"""
+# -*- coding: utf-8 -*-
+"""Traits-based GUI for head-MRI coregistration.
+
+Hierarchy
+---------
+This is the hierarchy of classes for control. Brackets like [1] denote
+properties that are set to be equivalent.
+
+::
+
+  CoregFrame: GUI for head-MRI coregistration.
+  |-- CoregModel (model): Traits object for estimating the head mri transform.
+  |   |-- MRIHeadWithFiducialsModel (mri) [1]: Represent an MRI head shape with fiducials.
+  |   |-- MRISubjectSource (subject_source) [2]: Find subjects in SUBJECTS_DIR and select one.
+  |   |-- SurfaceSource (bem): Expose points and tris of a file storing a surface.
+  |   |-- FiducialsSource (fid): Expose points of a given fiducials fif file.
+  |   +-- DigSource (hsp): Expose measurement information from a inst file.
+  |-- MlabSceneModel (scene) [3]: mayavi.core.ui.mayavi_scene
+  |-- HeadViewController (headview) [4]: Set head views for the given coordinate system.
+  |   +-- MlabSceneModel (scene) [3*]: ``HeadViewController(scene=CoregFrame.scene)``
+  |-- SubjectSelectorPanel (subject_panel): Subject selector panel
+  |   +-- MRISubjectSource (model) [2*]: ``SubjectSelectorPanel(model=self.model.mri.subject_source)``
+  |-- SurfaceObject (mri_obj) [5]: Represent a solid object in a mayavi scene.
+  |-- FiducialsPanel (fid_panel): Set fiducials on an MRI surface.
+  |   |-- MRIHeadWithFiducialsModel (model) [1*]: ``FiducialsPanel(model=CoregFrame.model.mri, headview=CoregFrame.headview)``
+  |   |-- HeadViewController (headview) [4*]: ``FiducialsPanel(model=CoregFrame.model.mri, headview=CoregFrame.headview)``
+  |   +-- SurfaceObject (hsp_obj) [5*]: ``CoregFrame.fid_panel.hsp_obj = CoregFrame.mri_obj``
+  |-- CoregPanel (coreg_panel): Coregistration panel for Head<->MRI with scaling.
+  +-- PointObject ({hsp, eeg, lpa, nasion, rpa, hsp_lpa, hsp_nasion, hsp_rpa} + _obj): Represent a group of individual points in a mayavi scene.
+
+"""  # noqa: E501
 
 # Authors: Christian Brodbeck <christianbrodbeck at nyu.edu>
 #
@@ -14,44 +44,35 @@ import warnings
 import numpy as np
 from scipy.spatial.distance import cdist
 
-# allow import without traits
-try:
-    from mayavi.core.ui.mayavi_scene import MayaviScene
-    from mayavi.tools.mlab_scene_model import MlabSceneModel
-    from pyface.api import (error, confirm, OK, YES, NO, CANCEL,
-                            information, FileDialog, GUI)
-    from traits.api import (Bool, Button, cached_property, DelegatesTo,
-                            Directory, Enum, Float, HasTraits,
-                            HasPrivateTraits, Instance, Int, on_trait_change,
-                            Property, Str)
-    from traitsui.api import (View, Item, Group, HGroup, VGroup, VGrid,
-                              EnumEditor, Handler, Label, TextEditor)
-    from traitsui.menu import Action, UndoButton, CancelButton, NoButtons
-    from tvtk.pyface.scene_editor import SceneEditor
-except Exception:
-    from ..utils import trait_wraith
-    HasTraits = HasPrivateTraits = Handler = object
-    cached_property = on_trait_change = MayaviScene = MlabSceneModel =\
-        Bool = Button = DelegatesTo = Directory = Enum = Float = Instance =\
-        Int = Property = Str = View = Item = Group = HGroup = VGroup = VGrid =\
-        EnumEditor = Label = TextEditor = Action = UndoButton = CancelButton =\
-        NoButtons = SceneEditor = trait_wraith
-
+from mayavi.core.ui.mayavi_scene import MayaviScene
+from mayavi.tools.mlab_scene_model import MlabSceneModel
+from pyface.api import (error, confirm, OK, YES, NO, CANCEL, information,
+                        FileDialog, GUI)
+from traits.api import (Bool, Button, cached_property, DelegatesTo, Directory,
+                        Enum, Float, HasTraits, HasPrivateTraits, Instance,
+                        Int, on_trait_change, Property, Str)
+from traitsui.api import (View, Item, Group, HGroup, VGroup, VGrid, EnumEditor,
+                          Handler, Label, TextEditor, Spring, InstanceEditor)
+from traitsui.menu import Action, UndoButton, CancelButton, NoButtons
+from tvtk.pyface.scene_editor import SceneEditor
 
 from ..bem import make_bem_solution, write_bem_solution
 from ..coreg import bem_fname, trans_fname
+from ..defaults import DEFAULTS
 from ..transforms import (write_trans, read_trans, apply_trans, rotation,
                           translation, scaling, rotation_angles, Transform)
 from ..coreg import (fit_matched_points, fit_point_cloud, scale_mri,
                      _find_fiducials_files, _point_cloud_error)
-from ..utils import get_subjects_dir, logger
-from ._fiducials_gui import MRIHeadWithFiducialsModel, FiducialsPanel
-from ._file_traits import trans_wildcard, InstSource, SubjectSelectorPanel
-from ._viewer import (defaults, HeadViewController, PointObject, SurfaceObject,
-                      _testing_mode)
+from ..viz._3d import _toggle_mlab_render
+from ..utils import logger, set_config
+from ._fiducials_gui import MRIHeadWithFiducialsModel, FiducialsPanel, _mm_fmt
+from ._file_traits import trans_wildcard, DigSource, SubjectSelectorPanel
+from ._viewer import HeadViewController, PointObject, SurfaceObject
 
+defaults = DEFAULTS['coreg']
 
-laggy_float_editor = TextEditor(auto_set=False, enter_set=True, evaluate=float)
+laggy_float_editor = TextEditor(auto_set=False, enter_set=True, evaluate=float,
+                                format_func=_mm_fmt)
 
 
 class CoregModel(HasPrivateTraits):
@@ -61,101 +82,120 @@ class CoregModel(HasPrivateTraits):
     -----
     Transform from head to mri space is modelled with the following steps:
 
-     * move the head shape to its nasion position
-     * rotate the head shape with user defined rotation around its nasion
-     * move the head shape by user defined translation
-     * move the head shape origin to the mri nasion
+    * move the head shape to its nasion position
+    * rotate the head shape with user defined rotation around its nasion
+    * move the head shape by user defined translation
+    * move the head shape origin to the mri nasion
 
     If MRI scaling is enabled,
 
-     * the MRI is scaled relative to its origin center (prior to any
-       transformation of the digitizer head)
-
+    * the MRI is scaled relative to its origin center (prior to any
+      transformation of the digitizer head)
 
     Don't sync transforms to anything to prevent them from being recomputed
     upon every parameter change.
     """
+
     # data sources
     mri = Instance(MRIHeadWithFiducialsModel, ())
-    hsp = Instance(InstSource, ())
+    hsp = Instance(DigSource, ())
 
     # parameters
+    guess_mri_subject = Bool(True)  # change MRI subject when dig file changes
     grow_hair = Float(label="Grow Hair [mm]", desc="Move the back of the MRI "
                       "head outwards to compensate for hair on the digitizer "
                       "head shape")
     n_scale_params = Enum(0, 1, 3, desc="Scale the MRI to better fit the "
                           "subject's head shape (a new MRI subject will be "
                           "created with a name specified upon saving)")
-    scale_x = Float(1, label="Right (X)")
-    scale_y = Float(1, label="Anterior (Y)")
-    scale_z = Float(1, label="Superior (Z)")
-    rot_x = Float(0, label="Right (X)")
-    rot_y = Float(0, label="Anterior (Y)")
-    rot_z = Float(0, label="Superior (Z)")
-    trans_x = Float(0, label="Right (X)")
-    trans_y = Float(0, label="Anterior (Y)")
-    trans_z = Float(0, label="Superior (Z)")
-
+    scale_x = Float(1, label="R (X)")
+    scale_y = Float(1, label="A (Y)")
+    scale_z = Float(1, label="S (Z)")
+    rot_x = Float(0, label="R (X)")
+    rot_y = Float(0, label="A (Y)")
+    rot_z = Float(0, label="S (Z)")
+    trans_x = Float(0, label="R (X)")
+    trans_y = Float(0, label="A (Y)")
+    trans_z = Float(0, label="S (Z)")
+
+    # options during scaling
+    scale_labels = Bool(True, desc="whether to scale *.label files")
+    copy_annot = Bool(True, desc="whether to copy *.annot files for scaled "
+                      "subject")
     prepare_bem_model = Bool(True, desc="whether to run mne_prepare_bem_model "
                              "after scaling the MRI")
 
     # secondary to parameters
-    scale = Property(depends_on=['n_scale_params', 'scale_x', 'scale_y',
-                                 'scale_z'])
-    has_fid_data = Property(Bool, depends_on=['mri_origin', 'hsp.nasion'],
-                            desc="Required fiducials data is present.")
-    has_pts_data = Property(Bool, depends_on=['mri.points', 'hsp.points'])
+    scale = Property(
+        depends_on=['n_scale_params', 'scale_x', 'scale_y', 'scale_z'])
+    has_fid_data = Property(
+        Bool,
+        desc="Required fiducials data is present.",
+        depends_on=['mri_origin', 'hsp.nasion'])
+    has_pts_data = Property(
+        Bool,
+        depends_on=['mri.points', 'hsp.points'])
+    has_eeg_data = Property(
+        Bool,
+        depends_on=['mri.points', 'hsp.eeg_points'])
 
     # MRI dependent
-    mri_origin = Property(depends_on=['mri.nasion', 'scale'],
-                          desc="Coordinates of the scaled MRI's nasion.")
+    mri_origin = Property(
+        desc="Coordinates of the scaled MRI's nasion.",
+        depends_on=['mri.nasion', 'scale'])
 
     # target transforms
-    mri_scale_trans = Property(depends_on=['scale'])
-    head_mri_trans = Property(depends_on=['hsp.nasion', 'rot_x', 'rot_y',
-                                          'rot_z', 'trans_x', 'trans_y',
-                                          'trans_z', 'mri_origin'],
-                              desc="Transformaiton of the head shape to "
-                              "match the scaled MRI.")
+    mri_scale_trans = Property(
+        depends_on=['scale'])
+    head_mri_trans = Property(
+        desc="Transformaiton of the head shape to match the scaled MRI.",
+        depends_on=['hsp.nasion', 'rot_x', 'rot_y', 'rot_z',
+                    'trans_x', 'trans_y', 'trans_z', 'mri_origin'])
 
     # info
     subject_has_bem = DelegatesTo('mri')
     lock_fiducials = DelegatesTo('mri')
-    can_prepare_bem_model = Property(Bool, depends_on=['n_scale_params',
-                                                       'subject_has_bem'])
+    can_prepare_bem_model = Property(
+        Bool,
+        depends_on=['n_scale_params', 'subject_has_bem'])
     can_save = Property(Bool, depends_on=['head_mri_trans'])
-    raw_subject = Property(depends_on='hsp.inst_fname', desc="Subject guess "
-                           "based on the raw file name.")
+    raw_subject = Property(
+        desc="Subject guess based on the raw file name.",
+        depends_on=['hsp.inst_fname'])
 
     # transformed geometry
     processed_mri_points = Property(depends_on=['mri.points', 'grow_hair'])
-    transformed_mri_points = Property(depends_on=['processed_mri_points',
-                                                  'mri_scale_trans'])
-    transformed_hsp_points = Property(depends_on=['hsp.points',
-                                                  'head_mri_trans'])
-    transformed_mri_lpa = Property(depends_on=['mri.lpa', 'mri_scale_trans'])
+    transformed_mri_points = Property(
+        depends_on=['processed_mri_points', 'mri_scale_trans'])
+    transformed_hsp_points = Property(
+        depends_on=['hsp.points', 'head_mri_trans'])
+    transformed_mri_lpa = Property(
+        depends_on=['mri.lpa', 'mri_scale_trans'])
     transformed_hsp_lpa = Property(depends_on=['hsp.lpa', 'head_mri_trans'])
-    transformed_mri_nasion = Property(depends_on=['mri.nasion',
-                                                  'mri_scale_trans'])
-    transformed_hsp_nasion = Property(depends_on=['hsp.nasion',
-                                                  'head_mri_trans'])
-    transformed_mri_rpa = Property(depends_on=['mri.rpa', 'mri_scale_trans'])
-    transformed_hsp_rpa = Property(depends_on=['hsp.rpa', 'head_mri_trans'])
+    transformed_mri_nasion = Property(
+        depends_on=['mri.nasion', 'mri_scale_trans'])
+    transformed_hsp_nasion = Property(
+        depends_on=['hsp.nasion', 'head_mri_trans'])
+    transformed_mri_rpa = Property(
+        depends_on=['mri.rpa', 'mri_scale_trans'])
+    transformed_hsp_rpa = Property(
+        depends_on=['hsp.rpa', 'head_mri_trans'])
 
     # fit properties
-    lpa_distance = Property(depends_on=['transformed_mri_lpa',
-                                        'transformed_hsp_lpa'])
-    nasion_distance = Property(depends_on=['transformed_mri_nasion',
-                                           'transformed_hsp_nasion'])
-    rpa_distance = Property(depends_on=['transformed_mri_rpa',
-                                        'transformed_hsp_rpa'])
-    point_distance = Property(depends_on=['transformed_mri_points',
-                                          'transformed_hsp_points'])
+    lpa_distance = Property(
+        depends_on=['transformed_mri_lpa', 'transformed_hsp_lpa'])
+    nasion_distance = Property(
+        depends_on=['transformed_mri_nasion', 'transformed_hsp_nasion'])
+    rpa_distance = Property(
+        depends_on=['transformed_mri_rpa', 'transformed_hsp_rpa'])
+    point_distance = Property(
+        depends_on=['transformed_mri_points', 'transformed_hsp_points'])
 
     # fit property info strings
-    fid_eval_str = Property(depends_on=['lpa_distance', 'nasion_distance',
-                                        'rpa_distance'])
-    points_eval_str = Property(depends_on='point_distance')
+    fid_eval_str = Property(
+        depends_on=['lpa_distance', 'nasion_distance', 'rpa_distance'])
+    points_eval_str = Property(
+        depends_on=['point_distance'])
 
     @cached_property
     def _get_can_prepare_bem_model(self):
@@ -171,6 +211,11 @@ class CoregModel(HasPrivateTraits):
         return has
 
     @cached_property
+    def _get_has_eeg_data(self):
+        has = (np.any(self.mri.points) and np.any(self.hsp.eeg_points))
+        return has
+
+    @cached_property
     def _get_has_fid_data(self):
         has = (np.any(self.mri_origin) and np.any(self.hsp.nasion))
         return has
@@ -181,26 +226,19 @@ class CoregModel(HasPrivateTraits):
             return np.array(1)
         elif self.n_scale_params == 1:
             return np.array(self.scale_x)
-        else:
+        else:  # if self.n_scale_params == 3:
             return np.array([self.scale_x, self.scale_y, self.scale_z])
 
     @cached_property
     def _get_mri_scale_trans(self):
-        if np.isscalar(self.scale) or self.scale.ndim == 0:
-            if self.scale == 1:
-                return np.eye(4)
-            else:
-                s = self.scale
-                return scaling(s, s, s)
+        if self.scale.ndim == 0:
+            return scaling(self.scale, self.scale, self.scale)
         else:
             return scaling(*self.scale)
 
     @cached_property
     def _get_mri_origin(self):
-        if np.isscalar(self.scale) and self.scale == 1:
-            return self.mri.nasion
-        else:
-            return self.mri.nasion * self.scale
+        return self.mri.nasion * self.scale
 
     @cached_property
     def _get_head_mri_trans(self):
@@ -230,23 +268,20 @@ class CoregModel(HasPrivateTraits):
     def _get_processed_mri_points(self):
         if self.grow_hair:
             if len(self.mri.norms):
-                if self.n_scale_params == 0:
-                    scaled_hair_dist = self.grow_hair / 1000
-                else:
-                    scaled_hair_dist = self.grow_hair / self.scale / 1000
-
+                scaled_hair_dist = self.grow_hair / (self.scale * 1000)
                 points = self.mri.points.copy()
                 hair = points[:, 2] > points[:, 1]
                 points[hair] += self.mri.norms[hair] * scaled_hair_dist
                 return points
             else:
-                error(None, "Norms missing form bem, can't grow hair")
+                error(None, "Norms missing from bem, can't grow hair")
                 self.grow_hair = 0
         return self.mri.points
 
     @cached_property
     def _get_transformed_mri_points(self):
-        points = apply_trans(self.mri_scale_trans, self.processed_mri_points)
+        points = apply_trans(self.mri_scale_trans,
+                             self.processed_mri_points)
         return points
 
     @cached_property
@@ -306,35 +341,33 @@ class CoregModel(HasPrivateTraits):
     def _get_fid_eval_str(self):
         d = (self.lpa_distance * 1000, self.nasion_distance * 1000,
              self.rpa_distance * 1000)
-        txt = ("Fiducials Error: LPA %.1f mm, NAS %.1f mm, RPA %.1f mm" % d)
-        return txt
+        return 'Error: LPA=%.1f NAS=%.1f RPA=%.1f mm' % d
 
     @cached_property
     def _get_points_eval_str(self):
         if self.point_distance is None:
             return ""
-        av_dist = np.mean(self.point_distance)
-        return "Average Points Error: %.1f mm" % (av_dist * 1000)
+        av_dist = 1000 * np.mean(self.point_distance)
+        std_dist = 1000 * np.std(self.point_distance)
+        return u"Points: μ=%.1f, σ=%.1f mm" % (av_dist, std_dist)
 
     def _get_raw_subject(self):
         # subject name guessed based on the inst file name
         if '_' in self.hsp.inst_fname:
             subject, _ = self.hsp.inst_fname.split('_', 1)
-            if not subject:
-                subject = None
-        else:
-            subject = None
-        return subject
+            if subject:
+                return subject
 
     @on_trait_change('raw_subject')
     def _on_raw_subject_change(self, subject):
-        if subject in self.mri.subject_source.subjects:
-            self.mri.subject = subject
-        elif 'fsaverage' in self.mri.subject_source.subjects:
-            self.mri.subject = 'fsaverage'
+        if self.guess_mri_subject:
+            if subject in self.mri.subject_source.subjects:
+                self.mri.subject = subject
+            elif 'fsaverage' in self.mri.subject_source.subjects:
+                self.mri.subject = 'fsaverage'
 
     def omit_hsp_points(self, distance=0, reset=False):
-        """Exclude head shape points that are far away from the MRI head
+        """Exclude head shape points that are far away from the MRI head.
 
         Parameters
         ----------
@@ -360,7 +393,7 @@ class CoregModel(HasPrivateTraits):
         mri_pts = self.transformed_mri_points
         point_distance = _point_cloud_error(hsp_pts, mri_pts)
         new_sub_filter = point_distance <= distance
-        n_excluded = np.sum(new_sub_filter == False)  # noqa
+        n_excluded = np.sum(new_sub_filter == False)  # noqa: E712
         logger.info("Coregistration: Excluding %i head shape points with "
                     "distance >= %.3f m.", n_excluded, distance)
 
@@ -369,7 +402,7 @@ class CoregModel(HasPrivateTraits):
         if old_filter is None:
             new_filter = new_sub_filter
         else:
-            new_filter = np.ones(len(self.hsp.raw_points), np.bool8)
+            new_filter = np.ones(len(self.hsp.points), np.bool8)
             new_filter[old_filter] = new_sub_filter
 
         # set the filter
@@ -377,7 +410,7 @@ class CoregModel(HasPrivateTraits):
             self.hsp.points_filter = new_filter
 
     def fit_auricular_points(self):
-        "Find rotation to fit LPA and RPA"
+        """Find rotation to fit LPA and RPA."""
         src_fid = np.vstack((self.hsp.lpa, self.hsp.rpa))
         src_fid -= self.hsp.nasion
 
@@ -393,7 +426,7 @@ class CoregModel(HasPrivateTraits):
         self.rot_x, self.rot_y, self.rot_z = rot
 
     def fit_fiducials(self):
-        "Find rotation and translation to fit all 3 fiducials"
+        """Find rotation and translation to fit all 3 fiducials."""
         src_fid = np.vstack((self.hsp.lpa, self.hsp.nasion, self.hsp.rpa))
         src_fid -= self.hsp.nasion
 
@@ -409,7 +442,7 @@ class CoregModel(HasPrivateTraits):
         self.trans_x, self.trans_y, self.trans_z = est[3:]
 
     def fit_hsp_points(self):
-        "Find rotation to fit head shapes"
+        """Find rotation to fit head shapes."""
         src_pts = self.hsp.points - self.hsp.nasion
 
         tgt_pts = self.processed_mri_points - self.mri.nasion
@@ -423,7 +456,7 @@ class CoregModel(HasPrivateTraits):
         self.rot_x, self.rot_y, self.rot_z = rot
 
     def fit_scale_auricular_points(self):
-        "Find rotation and MRI scaling based on LPA and RPA"
+        """Find rotation and MRI scaling based on LPA and RPA."""
         src_fid = np.vstack((self.hsp.lpa, self.hsp.rpa))
         src_fid -= self.hsp.nasion
 
@@ -439,7 +472,7 @@ class CoregModel(HasPrivateTraits):
         self.rot_x, self.rot_y, self.rot_z = x[:3]
 
     def fit_scale_fiducials(self):
-        "Find translation, rotation and scaling based on the three fiducials"
+        """Find translation, rotation, scaling based on the three fiducials."""
         src_fid = np.vstack((self.hsp.lpa, self.hsp.nasion, self.hsp.rpa))
         src_fid -= self.hsp.nasion
 
@@ -456,32 +489,29 @@ class CoregModel(HasPrivateTraits):
         self.trans_x, self.trans_y, self.trans_z = est[3:6]
 
     def fit_scale_hsp_points(self):
-        "Find MRI scaling and rotation to match head shape points"
+        """Find MRI scaling and rotation to match head shape points."""
         src_pts = self.hsp.points - self.hsp.nasion
-
         tgt_pts = self.processed_mri_points - self.mri.nasion
-
         if self.n_scale_params == 1:
             x0 = (self.rot_x, self.rot_y, self.rot_z, 1. / self.scale_x)
             est = fit_point_cloud(src_pts, tgt_pts, rotate=True,
                                   translate=False, scale=1, x0=x0)
 
             self.scale_x = 1. / est[3]
-        else:
+        else:  # if self.n_scale_params == 3:
             x0 = (self.rot_x, self.rot_y, self.rot_z, 1. / self.scale_x,
                   1. / self.scale_y, 1. / self.scale_z)
             est = fit_point_cloud(src_pts, tgt_pts, rotate=True,
                                   translate=False, scale=3, x0=x0)
             self.scale_x, self.scale_y, self.scale_z = 1. / est[3:]
-
         self.rot_x, self.rot_y, self.rot_z = est[:3]
 
-    def get_scaling_job(self, subject_to, skip_fiducials, do_bem_sol):
-        "Find all arguments needed for the scaling worker"
+    def get_scaling_job(self, subject_to, skip_fiducials):
+        """Find all arguments needed for the scaling worker."""
         subjects_dir = self.mri.subjects_dir
         subject_from = self.mri.subject
         bem_names = []
-        if do_bem_sol:
+        if self.can_prepare_bem_model and self.prepare_bem_model:
             pattern = bem_fname.format(subjects_dir=subjects_dir,
                                        subject=subject_from, name='(.+-bem)')
             bem_dir, pattern = os.path.split(pattern)
@@ -491,10 +521,10 @@ class CoregModel(HasPrivateTraits):
                     bem_names.append(match.group(1))
 
         return (subjects_dir, subject_from, subject_to, self.scale,
-                skip_fiducials, bem_names)
+                skip_fiducials, self.scale_labels, self.copy_annot, bem_names)
 
     def load_trans(self, fname):
-        """Load the head-mri transform from a fif file
+        """Load the head-mri transform from a fif file.
 
         Parameters
         ----------
@@ -502,17 +532,21 @@ class CoregModel(HasPrivateTraits):
             File path.
         """
         info = read_trans(fname)
+        # XXX this should really ensure that its a head->MRI trans. We should
+        # add from/to logic inside read_trans, which can also then invert it
+        # if necessary. This can then be used in a number of places
+        # (maxwell_filter, forward, viz._3d, etc.)
         head_mri_trans = info['trans']
         self.set_trans(head_mri_trans)
 
     def reset(self):
-        """Reset all the parameters affecting the coregistration"""
+        """Reset all the parameters affecting the coregistration."""
         self.reset_traits(('grow_hair', 'n_scaling_params', 'scale_x',
                            'scale_y', 'scale_z', 'rot_x', 'rot_y', 'rot_z',
                            'trans_x', 'trans_y', 'trans_z'))
 
     def set_trans(self, head_mri_trans):
-        """Set rotation and translation parameters from a transformation matrix
+        """Set rotation and translation params from a transformation matrix.
 
         Parameters
         ----------
@@ -538,7 +572,7 @@ class CoregModel(HasPrivateTraits):
         self.trans_z = z
 
     def save_trans(self, fname):
-        """Save the head-mri transform as a fif file
+        """Save the head-mri transform as a fif file.
 
         Parameters
         ----------
@@ -551,121 +585,76 @@ class CoregModel(HasPrivateTraits):
 
 
 class CoregFrameHandler(Handler):
-    """Handler that checks for unfinished processes before closing its window
-    """
+    """Check for unfinished processes before closing its window."""
+
+    def object_title_changed(self, info):
+        """Set the title when it gets changed."""
+        info.ui.title = info.object.title
+
     def close(self, info, is_ok):
+        """Handle the close event."""
         if info.object.queue.unfinished_tasks:
             information(None, "Can not close the window while saving is still "
                         "in progress. Please wait until all MRIs are "
                         "processed.", "Saving Still in Progress")
             return False
         else:
+            # store configuration, but don't prevent from closing on error
+            try:
+                info.object.save_config()
+            except Exception as exc:
+                warnings.warn("Error saving GUI configuration:\n%s" % (exc,))
             return True
 
 
-class CoregPanel(HasPrivateTraits):
-    model = Instance(CoregModel)
-
-    # parameters
-    reset_params = Button(label='Reset')
-    grow_hair = DelegatesTo('model')
-    n_scale_params = DelegatesTo('model')
-    scale_step = Float(0.01)
-    scale_x = DelegatesTo('model')
-    scale_x_dec = Button('-')
-    scale_x_inc = Button('+')
-    scale_y = DelegatesTo('model')
-    scale_y_dec = Button('-')
-    scale_y_inc = Button('+')
-    scale_z = DelegatesTo('model')
-    scale_z_dec = Button('-')
-    scale_z_inc = Button('+')
-    rot_step = Float(0.01)
-    rot_x = DelegatesTo('model')
-    rot_x_dec = Button('-')
-    rot_x_inc = Button('+')
-    rot_y = DelegatesTo('model')
-    rot_y_dec = Button('-')
-    rot_y_inc = Button('+')
-    rot_z = DelegatesTo('model')
-    rot_z_dec = Button('-')
-    rot_z_inc = Button('+')
-    trans_step = Float(0.001)
-    trans_x = DelegatesTo('model')
-    trans_x_dec = Button('-')
-    trans_x_inc = Button('+')
-    trans_y = DelegatesTo('model')
-    trans_y_dec = Button('-')
-    trans_y_inc = Button('+')
-    trans_z = DelegatesTo('model')
-    trans_z_dec = Button('-')
-    trans_z_inc = Button('+')
-
-    # fitting
-    has_fid_data = DelegatesTo('model')
-    has_pts_data = DelegatesTo('model')
-    # fitting with scaling
-    fits_hsp_points = Button(label='Fit Head Shape')
-    fits_fid = Button(label='Fit Fiducials')
-    fits_ap = Button(label='Fit LPA/RPA')
-    # fitting without scaling
-    fit_hsp_points = Button(label='Fit Head Shape')
-    fit_fid = Button(label='Fit Fiducials')
-    fit_ap = Button(label='Fit LPA/RPA')
-
-    # fit info
-    fid_eval_str = DelegatesTo('model')
-    points_eval_str = DelegatesTo('model')
-
-    # saving
-    can_prepare_bem_model = DelegatesTo('model')
-    can_save = DelegatesTo('model')
-    prepare_bem_model = DelegatesTo('model')
-    save = Button(label="Save As...")
-    load_trans = Button
-    queue = Instance(queue.Queue, ())
-    queue_feedback = Str('')
-    queue_current = Str('')
-    queue_len = Int(0)
-    queue_len_str = Property(Str, depends_on=['queue_len'])
-
+def _make_view_coreg_panel(scrollable=False):
+    """Generate View for CoregPanel."""
     view = View(VGroup(Item('grow_hair', show_label=True),
                        Item('n_scale_params', label='MRI Scaling',
                             style='custom', show_label=True,
-                            editor=EnumEditor(values={0: '1:No Scaling',
-                                                      1: '2:1 Param',
-                                                      3: '3:3 Params'},
-                                              cols=3)),
+                            editor=EnumEditor(values={0: '1:None',
+                                                      1: '2:Uniform',
+                                                      3: '3:3-axis'},
+                                              cols=4)),
                        VGrid(Item('scale_x', editor=laggy_float_editor,
                                   show_label=True, tooltip="Scale along "
                                   "right-left axis",
-                                  enabled_when='n_scale_params > 0'),
+                                  enabled_when='n_scale_params > 0',
+                                  width=+50),
                              Item('scale_x_dec',
-                                  enabled_when='n_scale_params > 0'),
+                                  enabled_when='n_scale_params > 0',
+                                  width=-50),
                              Item('scale_x_inc',
-                                  enabled_when='n_scale_params > 0'),
+                                  enabled_when='n_scale_params > 0',
+                                  width=-50),
                              Item('scale_step', tooltip="Scaling step",
-                                  enabled_when='n_scale_params > 0'),
+                                  enabled_when='n_scale_params > 0',
+                                  width=+50),
                              Item('scale_y', editor=laggy_float_editor,
                                   show_label=True,
                                   enabled_when='n_scale_params > 1',
                                   tooltip="Scale along anterior-posterior "
-                                  "axis"),
+                                  "axis", width=+50),
                              Item('scale_y_dec',
-                                  enabled_when='n_scale_params > 1'),
+                                  enabled_when='n_scale_params > 1',
+                                  width=-50),
                              Item('scale_y_inc',
-                                  enabled_when='n_scale_params > 1'),
-                             Label('(Step)'),
+                                  enabled_when='n_scale_params > 1',
+                                  width=-50),
+                             Label('(Step)', width=+50),
                              Item('scale_z', editor=laggy_float_editor,
                                   show_label=True,
                                   enabled_when='n_scale_params > 1',
                                   tooltip="Scale along anterior-posterior "
-                                  "axis"),
+                                  "axis", width=+50),
                              Item('scale_z_dec',
-                                  enabled_when='n_scale_params > 1'),
+                                  enabled_when='n_scale_params > 1',
+                                  width=-50),
                              Item('scale_z_inc',
-                                  enabled_when='n_scale_params > 1'),
-                             show_labels=False, columns=4),
+                                  enabled_when='n_scale_params > 1',
+                                  width=-50),
+                             show_labels=False, show_border=True,
+                             label='Scaling', columns=4),
                        HGroup(Item('fits_hsp_points',
                                    enabled_when='n_scale_params',
                                    tooltip="Rotate the digitizer head shape "
@@ -685,63 +674,81 @@ class CoregPanel(HasPrivateTraits):
                                    "minimize the distance of the three "
                                    "fiducials."),
                               show_labels=False),
-                       '_',
-                       Label("Translation:"),
                        VGrid(Item('trans_x', editor=laggy_float_editor,
                                   show_label=True, tooltip="Move along "
-                                  "right-left axis"),
-                             'trans_x_dec', 'trans_x_inc',
-                             Item('trans_step', tooltip="Movement step"),
+                                  "right-left axis", width=+50),
+                             Item('trans_x_dec', width=-50),
+                             Item('trans_x_inc', width=-50),
+                             Item('trans_step', tooltip="Movement step",
+                                  width=+50),
                              Item('trans_y', editor=laggy_float_editor,
                                   show_label=True, tooltip="Move along "
-                                  "anterior-posterior axis"),
-                             'trans_y_dec', 'trans_y_inc',
-                             Label('(Step)'),
+                                  "anterior-posterior axis", width=+50),
+                             Item('trans_y_dec', width=-50),
+                             Item('trans_y_inc', width=-50),
+                             Label('(Step)', width=+50),
                              Item('trans_z', editor=laggy_float_editor,
                                   show_label=True, tooltip="Move along "
-                                  "anterior-posterior axis"),
-                             'trans_z_dec', 'trans_z_inc',
-                             show_labels=False, columns=4),
-                       Label("Rotation:"),
+                                  "anterior-posterior axis", width=+50),
+                             Item('trans_z_dec', width=-50),
+                             Item('trans_z_inc', width=-50),
+                             show_labels=False, show_border=True,
+                             label='Translation', columns=4),
                        VGrid(Item('rot_x', editor=laggy_float_editor,
                                   show_label=True, tooltip="Rotate along "
-                                  "right-left axis"),
-                             'rot_x_dec', 'rot_x_inc',
-                             Item('rot_step', tooltip="Rotation step"),
+                                  "right-left axis", width=+50),
+                             Item('rot_x_dec', width=-50),
+                             Item('rot_x_inc', width=-50),
+                             Item('rot_step', tooltip="Rotation step",
+                                  width=+50),
                              Item('rot_y', editor=laggy_float_editor,
                                   show_label=True, tooltip="Rotate along "
-                                  "anterior-posterior axis"),
-                             'rot_y_dec', 'rot_y_inc',
-                             Label('(Step)'),
+                                  "anterior-posterior axis", width=+50),
+                             Item('rot_y_dec', width=-50),
+                             Item('rot_y_inc', width=-50),
+                             Label('(Step)', width=+50),
                              Item('rot_z', editor=laggy_float_editor,
                                   show_label=True, tooltip="Rotate along "
-                                  "anterior-posterior axis"),
-                             'rot_z_dec', 'rot_z_inc',
-                             show_labels=False, columns=4),
+                                  "anterior-posterior axis", width=+50),
+                             Item('rot_z_dec', width=-50),
+                             Item('rot_z_inc', width=-50),
+                             show_labels=False, show_border=True,
+                             label='Rotation', columns=4),
                        # buttons
                        HGroup(Item('fit_hsp_points',
                                    enabled_when='has_pts_data',
                                    tooltip="Rotate the head shape (around the "
                                    "nasion) so as to minimize the distance "
                                    "from each head shape point to its closest "
-                                   "MRI point"),
+                                   "MRI point", width=10),
                               Item('fit_ap', enabled_when='has_fid_data',
                                    tooltip="Try to match the LPA and the RPA, "
-                                   "leaving the Nasion in place"),
+                                   "leaving the Nasion in place", width=10),
                               Item('fit_fid', enabled_when='has_fid_data',
                                    tooltip="Move and rotate the head shape so "
                                    "as to minimize the distance between the "
-                                   "MRI and head shape fiducials"),
-                              Item('load_trans', enabled_when='has_fid_data'),
+                                   "MRI and head shape fiducials", width=10),
                               show_labels=False),
+                       HGroup(Item('load_trans', width=10),
+                              Spring(), show_labels=False),
                        '_',
                        Item('fid_eval_str', style='readonly'),
                        Item('points_eval_str', style='readonly'),
                        '_',
-                       HGroup(Item('prepare_bem_model'),
-                              Label("Run mne_prepare_bem_model"),
-                              show_labels=False,
-                              enabled_when='can_prepare_bem_model'),
+                       VGroup(
+                           Item('scale_labels',
+                                label="Scale *.label files",
+                                enabled_when='n_scale_params > 0'),
+                           Item('copy_annot',
+                                label="Copy annotation files",
+                                enabled_when='n_scale_params > 0'),
+                           Item('prepare_bem_model',
+                                label="Run mne_prepare_bem_model",
+                                enabled_when='can_prepare_bem_model'),
+                           show_left=False,
+                           label='Scaling options',
+                           show_border=True),
+                       '_',
                        HGroup(Item('save', enabled_when='can_save',
                                    tooltip="Save the trans file and (if "
                                    "scaling is enabled) the scaled MRI"),
@@ -752,24 +759,100 @@ class CoregPanel(HasPrivateTraits):
                        Item('queue_current', style='readonly'),
                        Item('queue_len_str', style='readonly'),
                        show_labels=False),
-                kind='panel', buttons=[UndoButton])
+                kind='panel', buttons=[UndoButton], scrollable=scrollable)
+    return view
+
+
+class CoregPanel(HasPrivateTraits):
+    """Coregistration panel for Head<->MRI with scaling."""
+
+    model = Instance(CoregModel)
+
+    # parameters
+    reset_params = Button(label='Reset')
+    grow_hair = DelegatesTo('model')
+    n_scale_params = DelegatesTo('model')
+    scale_step = Float(0.01)
+    scale_x = DelegatesTo('model')
+    scale_x_dec = Button('-')
+    scale_x_inc = Button('+')
+    scale_y = DelegatesTo('model')
+    scale_y_dec = Button('-')
+    scale_y_inc = Button('+')
+    scale_z = DelegatesTo('model')
+    scale_z_dec = Button('-')
+    scale_z_inc = Button('+')
+    rot_step = Float(0.01)
+    rot_x = DelegatesTo('model')
+    rot_x_dec = Button('-')
+    rot_x_inc = Button('+')
+    rot_y = DelegatesTo('model')
+    rot_y_dec = Button('-')
+    rot_y_inc = Button('+')
+    rot_z = DelegatesTo('model')
+    rot_z_dec = Button('-')
+    rot_z_inc = Button('+')
+    trans_step = Float(0.001)
+    trans_x = DelegatesTo('model')
+    trans_x_dec = Button('-')
+    trans_x_inc = Button('+')
+    trans_y = DelegatesTo('model')
+    trans_y_dec = Button('-')
+    trans_y_inc = Button('+')
+    trans_z = DelegatesTo('model')
+    trans_z_dec = Button('-')
+    trans_z_inc = Button('+')
+
+    # fitting
+    has_fid_data = DelegatesTo('model')
+    has_pts_data = DelegatesTo('model')
+    has_eeg_data = DelegatesTo('model')
+    # fitting with scaling
+    fits_hsp_points = Button(label='Fit Head Shape')
+    fits_fid = Button(label='Fit Fiducials')
+    fits_ap = Button(label='Fit LPA/RPA')
+    # fitting without scaling
+    fit_hsp_points = Button(label='Fit Head Shape')
+    fit_fid = Button(label='Fit Fiducials')
+    fit_ap = Button(label='Fit LPA/RPA')
+
+    # fit info
+    fid_eval_str = DelegatesTo('model')
+    points_eval_str = DelegatesTo('model')
+
+    # saving
+    can_prepare_bem_model = DelegatesTo('model')
+    can_save = DelegatesTo('model')
+    scale_labels = DelegatesTo('model')
+    copy_annot = DelegatesTo('model')
+    prepare_bem_model = DelegatesTo('model')
+    save = Button(label="Save As...")
+    load_trans = Button(label='Load trans...')
+    queue = Instance(queue.Queue, ())
+    queue_feedback = Str('')
+    queue_current = Str('')
+    queue_len = Int(0)
+    queue_len_str = Property(Str, depends_on=['queue_len'])
 
-    def __init__(self, *args, **kwargs):
+    view = _make_view_coreg_panel()
+
+    def __init__(self, *args, **kwargs):  # noqa: D102
         super(CoregPanel, self).__init__(*args, **kwargs)
 
         # Setup scaling worker
         def worker():
             while True:
                 (subjects_dir, subject_from, subject_to, scale, skip_fiducials,
-                 bem_names) = self.queue.get()
+                 include_labels, include_annot, bem_names) = self.queue.get()
                 self.queue_len -= 1
 
                 # Scale MRI files
                 self.queue_current = 'Scaling %s...' % subject_to
                 try:
                     scale_mri(subject_from, subject_to, scale, True,
-                              subjects_dir, skip_fiducials)
-                except:
+                              subjects_dir, skip_fiducials, include_labels,
+                              include_annot)
+                except Exception:
                     logger.error('Error scaling %s:\n' % subject_to +
                                  traceback.format_exc())
                     self.queue_feedback = ('Error scaling %s (see Terminal)' %
@@ -788,7 +871,7 @@ class CoregPanel(HasPrivateTraits):
                                                     name=bem_name)
                         bemsol = make_bem_solution(bem_file)
                         write_bem_solution(bem_file[:-4] + '-sol.fif', bemsol)
-                    except:
+                    except Exception:
                         logger.error('Error computing %s solution:\n' %
                                      bem_name + traceback.format_exc())
                         self.queue_feedback = ('Error computing %s solution '
@@ -908,7 +991,12 @@ class CoregPanel(HasPrivateTraits):
         if dlg.return_code != OK:
             return
         trans_file = dlg.path
-        self.model.load_trans(trans_file)
+        try:
+            self.model.load_trans(trans_file)
+        except Exception as e:
+            error(None, "Error loading trans file %s: %s (See terminal "
+                  "for details)" % (trans_file, e), "Error Loading Trans File")
+            raise
 
     def _save_fired(self):
         subjects_dir = self.model.mri.subjects_dir
@@ -971,14 +1059,12 @@ class CoregPanel(HasPrivateTraits):
             self.model.save_trans(trans_file)
         except Exception as e:
             error(None, "Error saving -trans.fif file: %s (See terminal for "
-                  "details)" % str(e), "Error Saving Trans File")
+                  "details)" % (e,), "Error Saving Trans File")
             raise
 
         # save the scaled MRI
         if self.n_scale_params:
-            do_bem_sol = self.can_prepare_bem_model and self.prepare_bem_model
-            job = self.model.get_scaling_job(subject_to, skip_fiducials,
-                                             do_bem_sol)
+            job = self.model.get_scaling_job(subject_to, skip_fiducials)
             self.queue.put(job)
             self.queue_len += 1
 
@@ -988,24 +1074,17 @@ class CoregPanel(HasPrivateTraits):
     def _scale_x_inc_fired(self):
         self.scale_x += self.scale_step
 
-    def _scale_x_changed(self, old, new):
-        if self.n_scale_params == 1:
-            self.scale_y = new
-            self.scale_z = new
-
     def _scale_y_dec_fired(self):
-        step = 1. / self.scale_step
-        self.scale_y *= step
+        self.scale_y -= self.scale_step
 
     def _scale_y_inc_fired(self):
-        self.scale_y *= self.scale_step
+        self.scale_y += self.scale_step
 
     def _scale_z_dec_fired(self):
-        step = 1. / self.scale_step
-        self.scale_z *= step
+        self.scale_z -= self.scale_step
 
     def _scale_z_inc_fired(self):
-        self.scale_z *= self.scale_step
+        self.scale_z += self.scale_step
 
     def _trans_x_dec_fired(self):
         self.trans_x -= self.trans_step
@@ -1027,6 +1106,8 @@ class CoregPanel(HasPrivateTraits):
 
 
 class NewMriDialog(HasPrivateTraits):
+    """New MRI dialog."""
+
     # Dialog to determine target subject name for a scaled MRI
     subjects_dir = Directory
     subject_to = Str
@@ -1046,7 +1127,6 @@ class NewMriDialog(HasPrivateTraits):
                 Item('overwrite', enabled_when='can_overwrite', tooltip="If a "
                      "subject with the chosen name exists, delete the old "
                      "subject"),
-                width=500,
                 buttons=[CancelButton,
                          Action(name='OK', enabled_when='can_save')])
 
@@ -1095,8 +1175,9 @@ class NewMriDialog(HasPrivateTraits):
             self.can_overwrite = False
 
 
-def _make_view(tabbed=False, split=False, scene_width=500):
-    """Create a view for the CoregFrame
+def _make_view(tabbed=False, split=False, scene_width=500, scene_height=400,
+               scrollable=True):
+    """Create a view for the CoregFrame.
 
     Parameters
     ----------
@@ -1108,53 +1189,59 @@ def _make_view(tabbed=False, split=False, scene_width=500):
         unnecessary for wx backend).
     scene_width : int
         Specify a minimum width for the 3d scene (in pixels).
+    scrollable : bool
+        Make the coregistration panel vertically scrollable (default True).
 
-    returns
+    Returns
     -------
     view : traits View
         View object for the CoregFrame.
     """
-    view_options = VGroup(Item('headview', style='custom'), 'view_options',
-                          show_border=True, show_labels=False, label='View')
-
-    scene = VGroup(Item('scene', show_label=False,
-                        editor=SceneEditor(scene_class=MayaviScene),
-                        dock='vertical', width=scene_width),
-                   view_options)
-
-    data_panel = VGroup(VGroup(Item('subject_panel', style='custom'),
-                               label="MRI Subject", show_border=True,
-                               show_labels=False),
-                        VGroup(Item('lock_fiducials', style='custom',
-                                    editor=EnumEditor(cols=2,
-                                                      values={False: '2:Edit',
-                                                              True: '1:Lock'}),
-                                    enabled_when='fid_ok'),
-                               HGroup('hsp_always_visible',
-                                      Label("Always Show Head Shape Points"),
-                                      show_labels=False),
-                               Item('fid_panel', style='custom'),
-                               label="MRI Fiducials", show_border=True,
-                               show_labels=False),
-                        VGroup(Item('raw_src', style="custom"),
-                               HGroup(Item('distance', show_label=True),
-                                      'omit_points', 'reset_omit_points',
-                                      show_labels=False),
-                               Item('omitted_info', style='readonly',
-                                    show_label=False),
-                               label='Head Shape Source (Raw/Epochs/Evoked)',
-                               show_border=True, show_labels=False),
-                        show_labels=False, label="Data Source")
-
-    coreg_panel = VGroup(Item('coreg_panel', style='custom'),
-                         label="Coregistration", show_border=True,
-                         show_labels=False,
-                         enabled_when="fid_panel.locked")
-
-    if split:
-        main_layout = 'split'
-    else:
-        main_layout = 'normal'
+    scene = VGroup(
+        Item('scene', show_label=False,
+             editor=SceneEditor(scene_class=MayaviScene),
+             dock='vertical', width=scene_width, height=scene_height),
+        VGroup(
+            Item('headview', style='custom'),
+            'view_options',
+            show_border=True, show_labels=False, label='View'))
+
+    data_panel = VGroup(
+        VGroup(Item('subject_panel', style='custom'), label="MRI Subject",
+               show_border=True, show_labels=False),
+        VGroup(Item('lock_fiducials', style='custom',
+                    editor=EnumEditor(cols=2, values={False: '2:Edit',
+                                                      True: '1:Lock'}),
+                    enabled_when='fid_ok'),
+               HGroup('hsp_always_visible',
+                      Label("Always Show Head Shape Points"),
+                      show_labels=False),
+               Item('fid_panel', style='custom'),
+               label="MRI Fiducials",  show_border=True, show_labels=False),
+        VGroup(Item('raw_src', style="custom"),
+               HGroup('guess_mri_subject',
+                      Label('Guess MRI Subject from File Name'),
+                      show_labels=False),
+               HGroup(Item('distance', show_label=False, width=20),
+                      'omit_points', 'reset_omit_points', show_labels=False),
+               Item('omitted_info', style='readonly', show_label=False),
+               label='Head Shape Source (Raw/Epochs/Evoked/DigMontage)',
+               show_border=True, show_labels=False),
+        show_labels=False, label="Data Source")
+
+    # Setting `scrollable=True` for a Group does not seem to have any effect
+    # (macOS), in order to be effective the parameter has to be set for a View
+    # object; hence we use a special InstanceEditor to set the parameter
+    # programmatically:
+    coreg_panel = VGroup(
+        # width=410 is optimized for macOS to avoid a horizontal scroll-bar;
+        # might benefit from platform-specific values
+        Item('coreg_panel', style='custom', width=410 if scrollable else 1,
+             editor=InstanceEditor(view=_make_view_coreg_panel(scrollable))),
+        label="Coregistration", show_border=not scrollable, show_labels=False,
+        enabled_when="fid_panel.locked")
+
+    main_layout = 'split' if split else 'normal'
 
     if tabbed:
         main = HGroup(scene,
@@ -1165,25 +1252,32 @@ def _make_view(tabbed=False, split=False, scene_width=500):
         main = HGroup(data_panel, scene, coreg_panel, show_labels=False,
                       layout=main_layout)
 
+    # Here we set the width and height to impossibly small numbers to force the
+    # window to be as tight as possible
     view = View(main, resizable=True, handler=CoregFrameHandler(),
-                buttons=NoButtons)
+                buttons=NoButtons, width=scene_width, height=scene_height)
     return view
 
 
 class ViewOptionsPanel(HasTraits):
+    """View options panel."""
+
     mri_obj = Instance(SurfaceObject)
     hsp_obj = Instance(PointObject)
-    view = View(VGroup(Item('mri_obj', style='custom',  # show_border=True,
-                            label="MRI Head Surface"),
-                       Item('hsp_obj', style='custom',  # show_border=True,
-                            label="Head Shape Points")),
+    eeg_obj = Instance(PointObject)
+    view = View(VGroup(Item('mri_obj', style='custom',
+                            label="MRI head"),
+                       Item('hsp_obj', style='custom',
+                            label="Head shape"),
+                       Item('eeg_obj', style='custom',
+                            label='EEG')),
                 title="View Options")
 
 
 class CoregFrame(HasTraits):
-    """GUI for head-MRI coregistration
-    """
-    model = Instance(CoregModel, ())
+    """GUI for head-MRI coregistration."""
+
+    model = Instance(CoregModel)
 
     scene = Instance(MlabSceneModel, ())
     headview = Instance(HeadViewController)
@@ -1191,24 +1285,29 @@ class CoregFrame(HasTraits):
     subject_panel = Instance(SubjectSelectorPanel)
     fid_panel = Instance(FiducialsPanel)
     coreg_panel = Instance(CoregPanel)
+    view_options_panel = Instance(ViewOptionsPanel)
+
     raw_src = DelegatesTo('model', 'hsp')
+    guess_mri_subject = DelegatesTo('model')
 
     # Omit Points
-    distance = Float(5., label="Distance [mm]", desc="Maximal distance for "
-                     "head shape points from MRI in mm")
-    omit_points = Button(label='Omit Points', desc="Omit head shape points "
+    distance = Float(5., desc="maximal distance for head shape points from "
+                     "MRI in mm")
+    omit_points = Button(label='Omit [mm]', desc="to omit head shape points "
                          "for the purpose of the automatic coregistration "
                          "procedure.")
-    reset_omit_points = Button(label='Reset Omission', desc="Reset the "
+    reset_omit_points = Button(label='Reset', desc="to reset the "
                                "omission of head shape points to include all.")
     omitted_info = Property(Str, depends_on=['model.hsp.n_omitted'])
 
     fid_ok = DelegatesTo('model', 'mri.fid_ok')
     lock_fiducials = DelegatesTo('model')
     hsp_always_visible = Bool(False, label="Always Show Head Shape")
+    title = Str('MNE Coreg')
 
     # visualization
     hsp_obj = Instance(PointObject)
+    eeg_obj = Instance(PointObject)
     mri_obj = Instance(SurfaceObject)
     lpa_obj = Instance(PointObject)
     nasion_obj = Instance(PointObject)
@@ -1222,53 +1321,95 @@ class CoregFrame(HasTraits):
 
     picker = Instance(object)
 
-    view_options_panel = Instance(ViewOptionsPanel)
-
     # Processing
     queue = DelegatesTo('coreg_panel')
 
     view = _make_view()
 
+    def _model_default(self):
+        return CoregModel(
+            scale_labels=self._config.get(
+                'MNE_COREG_SCALE_LABELS', 'true') == 'true',
+            copy_annot=self._config.get(
+                'MNE_COREG_COPY_ANNOT', 'true') == 'true',
+            prepare_bem_model=self._config.get(
+                'MNE_COREG_PREPARE_BEM', 'true') == 'true')
+
     def _subject_panel_default(self):
         return SubjectSelectorPanel(model=self.model.mri.subject_source)
 
     def _fid_panel_default(self):
-        panel = FiducialsPanel(model=self.model.mri, headview=self.headview)
-        return panel
+        return FiducialsPanel(model=self.model.mri, headview=self.headview)
 
     def _coreg_panel_default(self):
-        panel = CoregPanel(model=self.model)
-        return panel
+        return CoregPanel(model=self.model)
 
     def _headview_default(self):
         return HeadViewController(scene=self.scene, system='RAS')
 
-    def __init__(self, raw=None, subject=None, subjects_dir=None):
-        super(CoregFrame, self).__init__()
+    def __init__(self, raw=None, subject=None, subjects_dir=None,
+                 guess_mri_subject=True, head_opacity=1.,
+                 head_high_res=True, trans=None, config=None):  # noqa: D102
+        self._config = config or {}
+        super(CoregFrame, self).__init__(guess_mri_subject=guess_mri_subject)
+        self.subject_panel.model.use_high_res_head = head_high_res
+        if not 0 <= head_opacity <= 1:
+            raise ValueError(
+                "head_opacity needs to be a floating point number between 0 "
+                "and 1, got %r" % (head_opacity,))
+        self._initial_head_opacity = head_opacity
 
-        subjects_dir = get_subjects_dir(subjects_dir)
         if (subjects_dir is not None) and os.path.isdir(subjects_dir):
             self.model.mri.subjects_dir = subjects_dir
 
+        if raw is not None:
+            self.model.hsp.file = raw
+
         if subject is not None:
+            if subject not in self.model.mri.subject_source.subjects:
+                msg = "%s is not a valid subject. " % subject
+                # no subjects -> ['']
+                if any(self.model.mri.subject_source.subjects):
+                    ss = ', '.join(self.model.mri.subject_source.subjects)
+                    msg += ("The following subjects have been found: %s "
+                            "(subjects_dir=%s). " %
+                            (ss, self.model.mri.subjects_dir))
+                else:
+                    msg += ("No subjects were found in subjects_dir=%s. " %
+                            self.model.mri.subjects_dir)
+                msg += ("Make sure all MRI subjects have head shape files "
+                        "(run $ mne make_scalp_surfaces).")
+                raise ValueError(msg)
             self.model.mri.subject = subject
+        if trans is not None:
+            try:
+                self.model.load_trans(trans)
+            except Exception as e:
+                error(None, "Error loading trans file %s: %s (See terminal "
+                      "for details)" % (trans, e), "Error Loading Trans File")
 
-        if raw is not None:
-            self.model.hsp.file = raw
+    @on_trait_change('subject_panel.subject')
+    def _set_title(self):
+        self.title = '%s - MNE Coreg' % self.model.mri.subject
 
     @on_trait_change('scene.activated')
     def _init_plot(self):
-        self.scene.disable_render = True
+        _toggle_mlab_render(self, False)
 
         lpa_color = defaults['lpa_color']
         nasion_color = defaults['nasion_color']
         rpa_color = defaults['rpa_color']
 
         # MRI scalp
-        color = defaults['mri_color']
+        color = defaults['head_color']
         self.mri_obj = SurfaceObject(points=self.model.transformed_mri_points,
                                      color=color, tri=self.model.mri.tris,
-                                     scene=self.scene, name="MRI Scalp")
+                                     scene=self.scene, name="MRI Scalp",
+                                     # opacity=self._initial_head_opacity,
+                                     # setting opacity here causes points to be
+                                     # [[0, 0, 0]] -- why??
+                                     )
+        self.mri_obj.opacity = self._initial_head_opacity
         # on_trait_change was unreliable, so link it another way:
         self.model.mri.on_trait_change(self._on_mri_src_change, 'tris')
         self.model.sync_trait('transformed_mri_points', self.mri_obj, 'points',
@@ -1294,8 +1435,8 @@ class CoregFrame(HasTraits):
         self.model.sync_trait('scale', self.rpa_obj, 'trans', mutual=False)
 
         # Digitizer Head Shape
-        color = defaults['hsp_point_color']
-        point_scale = defaults['hsp_points_scale']
+        color = defaults['extra_color']
+        point_scale = defaults['extra_scale']
         p = PointObject(view='cloud', scene=self.scene, color=color,
                         point_scale=point_scale, resolution=5, name='HSP')
         self.hsp_obj = p
@@ -1303,9 +1444,19 @@ class CoregFrame(HasTraits):
         self.model.sync_trait('head_mri_trans', p, 'trans', mutual=False)
         self.sync_trait('hsp_visible', p, 'visible', mutual=False)
 
+        # Digitizer EEG
+        color = defaults['eeg_color']
+        point_scale = defaults['eeg_scale']
+        p = PointObject(view='cloud', scene=self.scene, color=color,
+                        point_scale=point_scale, resolution=5, name='EEG')
+        self.eeg_obj = p
+        self.model.hsp.sync_trait('eeg_points', p, 'points', mutual=False)
+        self.model.sync_trait('head_mri_trans', p, 'trans', mutual=False)
+        self.sync_trait('hsp_visible', p, 'visible', mutual=False)
+
         # Digitizer Fiducials
-        point_scale = defaults['hsp_fid_scale']
-        opacity = defaults['hsp_fid_opacity']
+        point_scale = defaults['dig_fid_scale']
+        opacity = defaults['dig_fid_opacity']
         p = PointObject(scene=self.scene, color=lpa_color, opacity=opacity,
                         point_scale=point_scale, name='HSP-LPA')
         self.hsp_lpa_obj = p
@@ -1328,14 +1479,15 @@ class CoregFrame(HasTraits):
         self.sync_trait('hsp_visible', p, 'visible', mutual=False)
 
         on_pick = self.scene.mayavi_scene.on_mouse_pick
-        if not _testing_mode():
-            self.picker = on_pick(self.fid_panel._on_pick, type='cell')
+        self.picker = on_pick(self.fid_panel._on_pick, type='cell')
 
         self.headview.left = True
-        self.scene.disable_render = False
-
+        _toggle_mlab_render(self, True)
+        self.scene.render()
+        self.scene.camera.focal_point = (0., 0., 0.)
         self.view_options_panel = ViewOptionsPanel(mri_obj=self.mri_obj,
-                                                   hsp_obj=self.hsp_obj)
+                                                   hsp_obj=self.hsp_obj,
+                                                   eeg_obj=self.eeg_obj)
 
     @cached_property
     def _get_hsp_visible(self):
@@ -1379,3 +1531,28 @@ class CoregFrame(HasTraits):
 
     def _view_options_fired(self):
         self.view_options_panel.edit_traits()
+
+    def save_config(self, home_dir=None):
+        """Write configuration values."""
+        set_config('MNE_COREG_GUESS_MRI_SUBJECT',
+                   str(self.model.guess_mri_subject).lower(),
+                   home_dir, set_env=False)
+        set_config('MNE_COREG_HEAD_HIGH_RES',
+                   str(self.model.mri.use_high_res_head).lower(),
+                   home_dir, set_env=False)
+        set_config('MNE_COREG_HEAD_OPACITY',
+                   str(self.mri_obj.opacity),
+                   home_dir, set_env=False)
+        set_config('MNE_COREG_SCALE_LABELS',
+                   str(self.model.scale_labels).lower(),
+                   home_dir, set_env=False)
+        set_config('MNE_COREG_COPY_ANNOT',
+                   str(self.model.copy_annot).lower(),
+                   home_dir, set_env=False)
+        set_config('MNE_COREG_PREPARE_BEM',
+                   str(self.model.prepare_bem_model).lower(),
+                   home_dir, set_env=False)
+        if self.model.mri.subjects_dir:
+            set_config('MNE_COREG_SUBJECTS_DIR',
+                       self.model.mri.subjects_dir,
+                       home_dir, set_env=False)
diff --git a/mne/gui/_fiducials_gui.py b/mne/gui/_fiducials_gui.py
index 635fbb3..a00bbc4 100644
--- a/mne/gui/_fiducials_gui.py
+++ b/mne/gui/_fiducials_gui.py
@@ -1,4 +1,4 @@
-"""Mayavi/traits GUI for setting MRI fiducials"""
+"""Mayavi/traits GUI for setting MRI fiducials."""
 
 # Authors: Christian Brodbeck <christianbrodbeck at nyu.edu>
 #
@@ -7,39 +7,37 @@
 import os
 from ..externals.six.moves import map
 
-# allow import without traits
-try:
-    from mayavi.core.ui.mayavi_scene import MayaviScene
-    from mayavi.tools.mlab_scene_model import MlabSceneModel
-    import numpy as np
-    from pyface.api import confirm, error, FileDialog, OK, YES
-    from traits.api import (HasTraits, HasPrivateTraits, on_trait_change,
-                            cached_property, DelegatesTo, Event, Instance,
-                            Property, Array, Bool, Button, Enum)
-    from traitsui.api import HGroup, Item, VGroup, View
-    from traitsui.menu import NoButtons
-    from tvtk.pyface.scene_editor import SceneEditor
-except Exception:
-    from ..utils import trait_wraith
-    HasTraits = HasPrivateTraits = object
-    cached_property = on_trait_change = MayaviScene = MlabSceneModel = \
-        Array = Bool = Button = DelegatesTo = Enum = Event = Instance = \
-        Property = View = Item = HGroup = VGroup = SceneEditor = \
-        NoButtons = error = trait_wraith
-
-from ..coreg import (fid_fname, head_bem_fname, _find_fiducials_files,
-                     _find_high_res_head)
+from mayavi.core.ui.mayavi_scene import MayaviScene
+from mayavi.tools.mlab_scene_model import MlabSceneModel
+import numpy as np
+from pyface.api import confirm, error, FileDialog, OK, YES
+from traits.api import (HasTraits, HasPrivateTraits, on_trait_change,
+                        cached_property, DelegatesTo, Event, Instance,
+                        Property, Array, Bool, Button, Enum)
+from traitsui.api import HGroup, Item, VGroup, View, ArrayEditor
+from traitsui.menu import NoButtons
+from tvtk.pyface.scene_editor import SceneEditor
+
+from ..coreg import fid_fname, _find_fiducials_files, _find_head_bem
+from ..defaults import DEFAULTS
 from ..io import write_fiducials
 from ..io.constants import FIFF
 from ..utils import get_subjects_dir, logger
+from ..viz._3d import _toggle_mlab_render
 from ._file_traits import (SurfaceSource, fid_wildcard, FiducialsSource,
                            MRISubjectSource, SubjectSelectorPanel)
-from ._viewer import (defaults, HeadViewController, PointObject, SurfaceObject,
+from ._viewer import (HeadViewController, PointObject, SurfaceObject,
                       headview_borders)
+defaults = DEFAULTS['coreg']
+
+
+def _mm_fmt(x):
+    """Format mm data."""
+    return '%0.5f' % x
 
 
 class MRIHeadWithFiducialsModel(HasPrivateTraits):
-    """Represent an MRI head shape with fiducials
+    """Represent an MRI head shape with fiducials.
 
     Attributes
     ----------
@@ -54,6 +52,7 @@ class MRIHeadWithFiducialsModel(HasPrivateTraits):
     rpa : array (1, 3)
         Right peri-auricular point coordinates.
     """
+
     subject_source = Instance(MRISubjectSource, ())
     bem = Instance(SurfaceSource, ())
     fid = Instance(FiducialsSource, ())
@@ -90,14 +89,14 @@ class MRIHeadWithFiducialsModel(HasPrivateTraits):
                           "model.")
 
     @on_trait_change('fid_points')
-    def reset_fiducials(self):
+    def reset_fiducials(self):  # noqa: D102
         if self.fid_points is not None:
             self.lpa = self.fid_points[0:1]
             self.nasion = self.fid_points[1:2]
             self.rpa = self.fid_points[2:3]
 
     def save(self, fname=None):
-        """Save the current fiducials to a file
+        """Save the current fiducials to a file.
 
         Parameters
         ----------
@@ -110,9 +109,15 @@ class MRIHeadWithFiducialsModel(HasPrivateTraits):
         if not fname:
             fname = self.default_fid_fname
 
-        dig = [{'kind': 1, 'ident': 1, 'r': np.array(self.lpa[0])},
-               {'kind': 1, 'ident': 2, 'r': np.array(self.nasion[0])},
-               {'kind': 1, 'ident': 3, 'r': np.array(self.rpa[0])}]
+        dig = [{'kind': FIFF.FIFFV_POINT_CARDINAL,
+                'ident': FIFF.FIFFV_POINT_LPA,
+                'r': np.array(self.lpa[0])},
+               {'kind': FIFF.FIFFV_POINT_CARDINAL,
+                'ident': FIFF.FIFFV_POINT_NASION,
+                'r': np.array(self.nasion[0])},
+               {'kind': FIFF.FIFFV_POINT_CARDINAL,
+                'ident': FIFF.FIFFV_POINT_RPA,
+                'r': np.array(self.rpa[0])}]
         write_fiducials(fname, dig, FIFF.FIFFV_COORD_MRI)
         self.fid_file = fname
 
@@ -168,20 +173,23 @@ class MRIHeadWithFiducialsModel(HasPrivateTraits):
         if not subjects_dir or not subject:
             return
 
-        path = None
+        # find head model
         if self.use_high_res_head:
-            path = _find_high_res_head(subjects_dir=subjects_dir,
-                                       subject=subject)
+            path = _find_head_bem(subject, subjects_dir, high_res=True)
             if not path:
                 error(None, "No high resolution head model was found for "
                       "subject {0}, using standard head instead. In order to "
                       "generate a high resolution head model, run:\n\n"
                       "    $ mne make_scalp_surfaces -s {0}"
                       "\n\n".format(subject), "No High Resolution Head")
-
-        if not path:
-            path = head_bem_fname.format(subjects_dir=subjects_dir,
-                                         subject=subject)
+                path = _find_head_bem(subject, subjects_dir)
+        else:
+            path = _find_head_bem(subject, subjects_dir)
+            if not path:
+                error(None, "No standard head model was found for subject "
+                      "{0}, using high resolution head model instead."
+                      .format(subject), "No Standard Resolution Head")
+                path = _find_head_bem(subject, subjects_dir, high_res=True)
         self.bem.file = path
 
         # find fiducials file
@@ -199,7 +207,8 @@ class MRIHeadWithFiducialsModel(HasPrivateTraits):
 
 
 class FiducialsPanel(HasPrivateTraits):
-    """Set fiducials on an MRI surface"""
+    """Set fiducials on an MRI surface."""
+
     model = Instance(MRIHeadWithFiducialsModel)
 
     fid_file = DelegatesTo('model')
@@ -214,7 +223,7 @@ class FiducialsPanel(HasPrivateTraits):
     locked = DelegatesTo('model', 'lock_fiducials')
 
     set = Enum('LPA', 'Nasion', 'RPA')
-    current_pos = Array(float, (1, 3))  # for editing
+    current_pos = Array(float, (1, 3), editor=ArrayEditor(width=50))
 
     save_as = Button(label='Save As...')
     save = Button(label='Save')
@@ -226,20 +235,25 @@ class FiducialsPanel(HasPrivateTraits):
     picker = Instance(object)
 
     # the layout of the dialog created
-    view = View(VGroup(Item('fid_file', label='Fiducials File'),
+    view = View(VGroup(Item('fid_file', label='File'),
                        Item('fid_fname', show_label=False, style='readonly'),
-                       Item('set', style='custom'),
-                       Item('current_pos', label='Pos'),
+                       Item('set', style='custom', width=50,
+                            format_func=lambda x: x),
+                       Item('current_pos', label='Pos', width=50,
+                            format_func=_mm_fmt),
                        HGroup(Item('save', enabled_when='can_save',
                                    tooltip="If a filename is currently "
                                    "specified, save to that file, otherwise "
-                                   "save to the default file name"),
-                              Item('save_as', enabled_when='can_save_as'),
-                              Item('reset_fid', enabled_when='can_reset'),
+                                   "save to the default file name",
+                                   width=10),
+                              Item('save_as', enabled_when='can_save_as',
+                                   width=10),
+                              Item('reset_fid', enabled_when='can_reset',
+                                   width=10),
                               show_labels=False),
                        enabled_when="locked==False"))
 
-    def __init__(self, *args, **kwargs):
+    def __init__(self, *args, **kwargs):  # noqa: D102
         super(FiducialsPanel, self).__init__(*args, **kwargs)
         self.sync_trait('lpa', self, 'current_pos', mutual=True)
 
@@ -350,7 +364,7 @@ view2 = View(VGroup(Item('fid_file', label='Fiducials File'),
 
 
 class FiducialsFrame(HasTraits):
-    """GUI for interpolating between two KIT marker files
+    """GUI for interpolating between two KIT marker files.
 
     Parameters
     ----------
@@ -359,6 +373,7 @@ class FiducialsFrame(HasTraits):
     subjects_dir : None | str
         Override the SUBJECTS_DIR environment variable.
     """
+
     model = Instance(MRIHeadWithFiducialsModel, ())
 
     scene = Instance(MlabSceneModel, ())
@@ -399,7 +414,8 @@ class FiducialsFrame(HasTraits):
                 resizable=True,
                 buttons=NoButtons)
 
-    def __init__(self, subject=None, subjects_dir=None, **kwargs):
+    def __init__(self, subject=None, subjects_dir=None,
+                 **kwargs):  # noqa: D102
         super(FiducialsFrame, self).__init__(**kwargs)
 
         subjects_dir = get_subjects_dir(subjects_dir)
@@ -412,7 +428,7 @@ class FiducialsFrame(HasTraits):
 
     @on_trait_change('scene.activated')
     def _init_plot(self):
-        self.scene.disable_render = True
+        _toggle_mlab_render(self, False)
 
         lpa_color = defaults['lpa_color']
         nasion_color = defaults['nasion_color']
@@ -443,7 +459,7 @@ class FiducialsFrame(HasTraits):
         self.sync_trait('point_scale', self.rpa_obj, mutual=False)
 
         self.headview.left = True
-        self.scene.disable_render = False
+        _toggle_mlab_render(self, True)
 
         # picker
         self.scene.mayavi_scene.on_mouse_pick(self.panel._on_pick, type='cell')
diff --git a/mne/gui/_file_traits.py b/mne/gui/_file_traits.py
index 4dc0714..8ce60b6 100644
--- a/mne/gui/_file_traits.py
+++ b/mne/gui/_file_traits.py
@@ -1,36 +1,32 @@
-"""File data sources for traits GUIs"""
+"""File data sources for traits GUIs."""
 
 # Authors: Christian Brodbeck <christianbrodbeck at nyu.edu>
 #
 # License: BSD (3-clause)
 
 import os
+import os.path as op
 
 import numpy as np
-from ..externals.six.moves import map
-
-# allow import without traits
-try:
-    from traits.api import (Any, HasTraits, HasPrivateTraits, cached_property,
-                            on_trait_change, Array, Bool, Button, DelegatesTo,
-                            Directory, Enum, Event, File, Instance, Int, List,
-                            Property, Str)
-    from traitsui.api import View, Item, VGroup, HGroup
-    from pyface.api import (DirectoryDialog, OK, ProgressDialog, error,
-                            information)
-except Exception:
-    from ..utils import trait_wraith
-    HasTraits = HasPrivateTraits = object
-    cached_property = on_trait_change = Any = Array = Bool = Button = \
-        DelegatesTo = Directory = Enum = Event = File = Instance = HGroup = \
-        Int = List = Property = Str = View = Item = VGroup = trait_wraith
 
+from traits.api import (Any, HasTraits, HasPrivateTraits, cached_property,
+                        on_trait_change, Array, Bool, Button, DelegatesTo,
+                        Directory, Enum, Event, File, Instance, Int, List,
+                        Property, Str)
+from traitsui.api import View, Item, VGroup, HGroup, Label
+from pyface.api import DirectoryDialog, OK, ProgressDialog, error, information
+
+from ..bem import read_bem_surfaces
 from ..io.constants import FIFF
 from ..io import read_info, read_fiducials
-from ..surface import read_bem_surfaces, read_surface
+from ..io.meas_info import _empty_info
+from ..io.open import fiff_open, dir_tree_find
+from ..surface import read_surface
 from ..coreg import (_is_mri_subject, _mri_subject_has_bem,
                      create_default_subject)
 from ..utils import get_config, set_config
+from ..viz._3d import _fiducial_coords
+from ..channels import read_dig_montage, DigMontage
 
 
 fid_wildcard = "*.fif"
@@ -41,11 +37,11 @@ trans_wildcard = "*.fif"
 
 
 def _expand_path(p):
-    return os.path.abspath(os.path.expandvars(os.path.expanduser(p)))
+    return op.abspath(op.expandvars(op.expanduser(p)))
 
 
 def get_fs_home():
-    """Get the FREESURFER_HOME directory
+    """Get the FREESURFER_HOME directory.
 
     Returns
     -------
@@ -61,23 +57,6 @@ def get_fs_home():
     return _get_root_home('FREESURFER_HOME', 'freesurfer', _fs_home_problem)
 
 
-def get_mne_root():
-    """Get the MNE_ROOT directory
-
-    Returns
-    -------
-    mne_root : None | str
-        The MNE_ROOT path or None if the user cancels.
-
-    Notes
-    -----
-    If MNE_ROOT can't be found, the user is prompted with a file dialog.
-    If specified successfully, the resulting path is stored with
-    mne.set_config().
-    """
-    return _get_root_home('MNE_ROOT', 'MNE', _mne_root_problem)
-
-
 def _get_root_home(cfg, name, check_fun):
     root = get_config(cfg)
     problem = check_fun(root)
@@ -99,7 +78,7 @@ def _get_root_home(cfg, name, check_fun):
 
 
 def set_fs_home():
-    """Set the FREESURFER_HOME environment variable
+    """Set the FREESURFER_HOME environment variable.
 
     Returns
     -------
@@ -122,71 +101,39 @@ def set_fs_home():
 
 
 def _fs_home_problem(fs_home):
-    """Check FREESURFER_HOME path
+    """Check FREESURFER_HOME path.
 
     Return str describing problem or None if the path is okay.
     """
     if fs_home is None:
         return "FREESURFER_HOME is not set."
-    elif not os.path.exists(fs_home):
+    elif not op.exists(fs_home):
         return "FREESURFER_HOME (%s) does not exist." % fs_home
     else:
-        test_dir = os.path.join(fs_home, 'subjects', 'fsaverage')
-        if not os.path.exists(test_dir):
+        test_dir = op.join(fs_home, 'subjects', 'fsaverage')
+        if not op.exists(test_dir):
             return ("FREESURFER_HOME (%s) does not contain the fsaverage "
                     "subject." % fs_home)
 
 
-def set_mne_root(set_mne_bin=False):
-    """Set the MNE_ROOT environment variable
-
-    Parameters
-    ----------
-    set_mne_bin : bool
-        Also add the MNE binary directory to the PATH (default: False).
-
-    Returns
-    -------
-    success : bool
-        True if the environment variable could be set, False if MNE_ROOT
-        could not be found.
-
-    Notes
-    -----
-    If MNE_ROOT can't be found, the user is prompted with a file dialog.
-    If specified successfully, the resulting path is stored with
-    mne.set_config().
-    """
-    mne_root = get_mne_root()
-    if mne_root is None:
-        return False
-    else:
-        os.environ['MNE_ROOT'] = mne_root
-        if set_mne_bin:
-            mne_bin = os.path.realpath(os.path.join(mne_root, 'bin'))
-            if mne_bin not in map(_expand_path, os.environ['PATH'].split(':')):
-                os.environ['PATH'] += ':' + mne_bin
-        return True
-
-
 def _mne_root_problem(mne_root):
-    """Check MNE_ROOT path
+    """Check MNE_ROOT path.
 
     Return str describing problem or None if the path is okay.
     """
     if mne_root is None:
         return "MNE_ROOT is not set."
-    elif not os.path.exists(mne_root):
+    elif not op.exists(mne_root):
         return "MNE_ROOT (%s) does not exist." % mne_root
     else:
-        test_dir = os.path.join(mne_root, 'share', 'mne', 'mne_analyze')
-        if not os.path.exists(test_dir):
+        test_dir = op.join(mne_root, 'share', 'mne', 'mne_analyze')
+        if not op.exists(test_dir):
             return ("MNE_ROOT (%s) is missing files. If this is your MNE "
                     "installation, consider reinstalling." % mne_root)
 
 
 class SurfaceSource(HasTraits):
-    """Expose points and tris of a file storing a surface
+    """Expose points and tris of a file storing a surface.
 
     Parameters
     ----------
@@ -205,6 +152,7 @@ class SurfaceSource(HasTraits):
     tri is always updated after pts, so in case downstream objects depend on
     both, they should sync to a change in tris.
     """
+
     file = File(exists=True, filter=['*.fif', '*.*'])
     points = Array(shape=(None, 3), value=np.empty((0, 3)))
     norms = Array
@@ -212,9 +160,10 @@ class SurfaceSource(HasTraits):
 
     @on_trait_change('file')
     def read_file(self):
-        if os.path.exists(self.file):
+        """Read the file."""
+        if op.exists(self.file):
             if self.file.endswith('.fif'):
-                bem = read_bem_surfaces(self.file)[0]
+                bem = read_bem_surfaces(self.file, verbose=False)[0]
                 self.points = bem['rr']
                 self.norms = bem['nn']
                 self.tris = bem['tris']
@@ -238,7 +187,7 @@ class SurfaceSource(HasTraits):
 
 
 class FiducialsSource(HasTraits):
-    """Expose points of a given fiducials fif file
+    """Expose points of a given fiducials fif file.
 
     Parameters
     ----------
@@ -250,31 +199,25 @@ class FiducialsSource(HasTraits):
     points : Array, shape = (n_points, 3)
         Fiducials file points.
     """
+
     file = File(filter=[fid_wildcard])
     fname = Property(depends_on='file')
     points = Property(depends_on='file')
 
     @cached_property
     def _get_fname(self):
-        fname = os.path.basename(self.file)
+        fname = op.basename(self.file)
         return fname
 
     @cached_property
     def _get_points(self):
-        if not os.path.exists(self.file):
+        if not op.exists(self.file):
             return None
 
         try:
-            points = np.zeros((3, 3))
-            fids, _ = read_fiducials(self.file)
-            for fid in fids:
-                ident = fid['ident']
-                if ident == FIFF.FIFFV_POINT_LPA:
-                    points[0] = fid['r']
-                elif ident == FIFF.FIFFV_POINT_NASION:
-                    points[1] = fid['r']
-                elif ident == FIFF.FIFFV_POINT_RPA:
-                    points[2] = fid['r']
+            fids, coord_frame = read_fiducials(self.file)
+            points = _fiducial_coords(fids, coord_frame)
+            assert points.shape == (3, 3)
             return points
         except Exception as err:
             error(None, "Error reading fiducials from %s: %s (See terminal "
@@ -284,8 +227,8 @@ class FiducialsSource(HasTraits):
             raise
 
 
-class InstSource(HasPrivateTraits):
-    """Expose measurement information from a inst file
+class DigSource(HasPrivateTraits):
+    """Expose digitization information from a file.
 
     Parameters
     ----------
@@ -298,33 +241,35 @@ class InstSource(HasPrivateTraits):
         Each row contains the coordinates for one fiducial point, in the order
         Nasion, RAP, LAP. If no file is set all values are 0.
     """
+
     file = File(exists=True, filter=['*.fif'])
 
     inst_fname = Property(Str, depends_on='file')
     inst_dir = Property(depends_on='file')
-    inst = Property(depends_on='file')
+    _info = Property(depends_on='file')
 
     points_filter = Any(desc="Index to select a subset of the head shape "
-                        "points")
+                             "points")
     n_omitted = Property(Int, depends_on=['points_filter'])
 
     # head shape
-    inst_points = Property(depends_on='inst', desc="Head shape points in the "
-                           "inst file(n x 3 array)")
-    points = Property(depends_on=['inst_points', 'points_filter'], desc="Head "
-                      "shape points selected by the filter (n x 3 array)")
+    _hsp_points = Property(depends_on='_info',
+                           desc="Head shape points in the file (n x 3 array)")
+    points = Property(depends_on=['_hsp_points', 'points_filter'],
+                      desc="Head shape points selected by the filter (n x 3 "
+                           "array)")
 
     # fiducials
-    fid_dig = Property(depends_on='inst', desc="Fiducial points "
-                       "(list of dict)")
-    fid_points = Property(depends_on='fid_dig', desc="Fiducial points {ident: "
-                          "point} dict}")
-    lpa = Property(depends_on='fid_points', desc="LPA coordinates (1 x 3 "
-                   "array)")
-    nasion = Property(depends_on='fid_points', desc="Nasion coordinates (1 x "
-                      "3 array)")
-    rpa = Property(depends_on='fid_points', desc="RPA coordinates (1 x 3 "
-                   "array)")
+    lpa = Property(depends_on='_info',
+                   desc="LPA coordinates (1 x 3 array)")
+    nasion = Property(depends_on='_info',
+                      desc="Nasion coordinates (1 x 3 array)")
+    rpa = Property(depends_on='_info',
+                   desc="RPA coordinates (1 x 3 array)")
+
+    # EEG
+    eeg_points = Property(depends_on='_info',
+                          desc="EEG sensor coordinates (N x 3 array)")
 
     view = View(VGroup(Item('file'),
                        Item('inst_fname', show_label=False, style='readonly')))
@@ -334,90 +279,144 @@ class InstSource(HasPrivateTraits):
         if self.points_filter is None:
             return 0
         else:
-            return np.sum(self.points_filter == False)  # noqa
+            return np.sum(self.points_filter == False)  # noqa: E712
 
     @cached_property
-    def _get_inst(self):
+    def _get__info(self):
         if self.file:
-            info = read_info(self.file)
-            if info['dig'] is None:
+            info = None
+            fid, tree, _ = fiff_open(self.file)
+            fid.close()
+            if len(dir_tree_find(tree, FIFF.FIFFB_MEAS_INFO)) > 0:
+                info = read_info(self.file, verbose=False)
+            elif len(dir_tree_find(tree, FIFF.FIFFB_ISOTRAK)) > 0:
+                info = read_dig_montage(fif=self.file)
+
+            if info is None:
                 error(None, "The selected FIFF file does not contain "
                       "digitizer information. Please select a different "
                       "file.", "Error Reading FIFF File")
                 self.reset_traits(['file'])
+                return
+            elif isinstance(info, DigMontage):
+                info.transform_to_head()
+                digs = list()
+                _append_fiducials(digs, info.lpa, info.nasion, info.rpa)
+                for idx, pos in enumerate(info.hsp):
+                    dig = {'coord_frame': FIFF.FIFFV_COORD_HEAD,
+                           'ident': idx,
+                           'kind': FIFF.FIFFV_POINT_EXTRA,
+                           'r': pos}
+                    digs.append(dig)
+                info = _empty_info(1)
+                info['dig'] = digs
             else:
-                return info
+                # check that all fiducial points are present
+                has_point = {FIFF.FIFFV_POINT_LPA: False,
+                             FIFF.FIFFV_POINT_NASION: False,
+                             FIFF.FIFFV_POINT_RPA: False}
+                for d in info['dig']:
+                    if d['kind'] == FIFF.FIFFV_POINT_CARDINAL:
+                        has_point[d['ident']] = True
+                if not all(has_point.values()):
+                    points = _fiducial_coords(info['dig'])
+                    if len(points) == 3:
+                        _append_fiducials(info['dig'], *points.T)
+                    else:
+                        missing = []
+                        if not has_point[FIFF.FIFFV_POINT_LPA]:
+                            missing.append('LPA')
+                        if not has_point[FIFF.FIFFV_POINT_NASION]:
+                            missing.append('Nasion')
+                        if not has_point[FIFF.FIFFV_POINT_RPA]:
+                            missing.append('RPA')
+                        error(None, "The selected FIFF file does not contain "
+                              "all cardinal points (missing: %s). Please "
+                              "select a different file." % ', '.join(missing),
+                              "Error Reading FIFF File")
+                        self.reset_traits(['file'])
+                        return
+
+            return info
 
     @cached_property
     def _get_inst_dir(self):
-        return os.path.dirname(self.file)
+        return op.dirname(self.file)
 
     @cached_property
     def _get_inst_fname(self):
         if self.file:
-            return os.path.basename(self.file)
+            return op.basename(self.file)
         else:
             return '-'
 
     @cached_property
-    def _get_inst_points(self):
-        if not self.inst:
-            return np.zeros((1, 3))
+    def _get__hsp_points(self):
+        if not self._info:
+            return np.zeros((0, 3))
 
-        points = np.array([d['r'] for d in self.inst['dig']
+        points = np.array([d['r'] for d in self._info['dig']
                            if d['kind'] == FIFF.FIFFV_POINT_EXTRA])
+        points = np.empty((0, 3)) if len(points) == 0 else points
         return points
 
     @cached_property
     def _get_points(self):
         if self.points_filter is None:
-            return self.inst_points
+            return self._hsp_points
         else:
-            return self.inst_points[self.points_filter]
+            return self._hsp_points[self.points_filter]
 
-    @cached_property
-    def _get_fid_dig(self):
-        """Fiducials for info['dig']"""
-        if not self.inst:
-            return []
-        dig = self.inst['dig']
-        dig = [d for d in dig if d['kind'] == FIFF.FIFFV_POINT_CARDINAL]
-        return dig
-
-    @cached_property
-    def _get_fid_points(self):
-        if not self.inst:
-            return {}
-        digs = dict((d['ident'], d) for d in self.fid_dig)
-        return digs
+    def _cardinal_point(self, ident):
+        """Coordinates for a cardinal point."""
+        if self._info:
+            for d in self._info['dig']:
+                if (d['kind'] == FIFF.FIFFV_POINT_CARDINAL and
+                        d['ident'] == ident):
+                    return d['r'][None, :]
+        return np.zeros((1, 3))
 
     @cached_property
     def _get_nasion(self):
-        if self.fid_points:
-            return self.fid_points[FIFF.FIFFV_POINT_NASION]['r'][None, :]
-        else:
-            return np.zeros((1, 3))
+        return self._cardinal_point(FIFF.FIFFV_POINT_NASION)
 
     @cached_property
     def _get_lpa(self):
-        if self.fid_points:
-            return self.fid_points[FIFF.FIFFV_POINT_LPA]['r'][None, :]
-        else:
-            return np.zeros((1, 3))
+        return self._cardinal_point(FIFF.FIFFV_POINT_LPA)
 
     @cached_property
     def _get_rpa(self):
-        if self.fid_points:
-            return self.fid_points[FIFF.FIFFV_POINT_RPA]['r'][None, :]
+        return self._cardinal_point(FIFF.FIFFV_POINT_RPA)
+
+    @cached_property
+    def _get_eeg_points(self):
+        if self._info:
+            return np.array([d['r'] for d in self._info['dig'] if
+                             d['kind'] == FIFF.FIFFV_POINT_EEG])
         else:
-            return np.zeros((1, 3))
+            return np.empty((0, 3))
 
     def _file_changed(self):
         self.reset_traits(('points_filter',))
 
 
+def _append_fiducials(dig, lpa, nasion, rpa):
+    dig.append({'coord_frame': FIFF.FIFFV_COORD_HEAD,
+                'ident': FIFF.FIFFV_POINT_LPA,
+                'kind': FIFF.FIFFV_POINT_CARDINAL,
+                'r': lpa})
+    dig.append({'coord_frame': FIFF.FIFFV_COORD_HEAD,
+                'ident': FIFF.FIFFV_POINT_NASION,
+                'kind': FIFF.FIFFV_POINT_CARDINAL,
+                'r': nasion})
+    dig.append({'coord_frame': FIFF.FIFFV_COORD_HEAD,
+                'ident': FIFF.FIFFV_POINT_RPA,
+                'kind': FIFF.FIFFV_POINT_CARDINAL,
+                'r': rpa})
+
+
 class MRISubjectSource(HasPrivateTraits):
-    """Find subjects in SUBJECTS_DIR and select one
+    """Find subjects in SUBJECTS_DIR and select one.
 
     Parameters
     ----------
@@ -426,6 +425,7 @@ class MRISubjectSource(HasPrivateTraits):
     subject : str
         Subject, corresponding to a folder in SUBJECTS_DIR.
     """
+
     refresh = Event(desc="Refresh the subject list based on the directory "
                     "structure of subjects_dir.")
 
@@ -445,9 +445,7 @@ class MRISubjectSource(HasPrivateTraits):
 
     @cached_property
     def _get_can_create_fsaverage(self):
-        if not os.path.exists(self.subjects_dir):
-            return False
-        if 'fsaverage' in self.subjects:
+        if not op.exists(self.subjects_dir) or 'fsaverage' in self.subjects:
             return False
         return True
 
@@ -458,12 +456,12 @@ class MRISubjectSource(HasPrivateTraits):
         elif not self.subjects_dir:
             return
         else:
-            return os.path.join(self.subjects_dir, self.subject)
+            return op.join(self.subjects_dir, self.subject)
 
     @cached_property
     def _get_subjects(self):
         sdir = self.subjects_dir
-        is_dir = sdir and os.path.isdir(sdir)
+        is_dir = sdir and op.isdir(sdir)
         if is_dir:
             dir_content = os.listdir(sdir)
             subjects = [s for s in dir_content if _is_mri_subject(s, sdir)]
@@ -472,7 +470,7 @@ class MRISubjectSource(HasPrivateTraits):
         else:
             subjects = ['']
 
-        return subjects
+        return sorted(subjects)
 
     @cached_property
     def _get_subject_has_bem(self):
@@ -480,30 +478,33 @@ class MRISubjectSource(HasPrivateTraits):
             return False
         return _mri_subject_has_bem(self.subject, self.subjects_dir)
 
-    def create_fsaverage(self):
+    def create_fsaverage(self):  # noqa: D102
         if not self.subjects_dir:
             err = ("No subjects directory is selected. Please specify "
                    "subjects_dir first.")
             raise RuntimeError(err)
 
-        mne_root = get_mne_root()
-        if mne_root is None:
-            err = ("MNE contains files that are needed for copying the "
-                   "fsaverage brain. Please install MNE and try again.")
-            raise RuntimeError(err)
         fs_home = get_fs_home()
         if fs_home is None:
             err = ("FreeSurfer contains files that are needed for copying the "
                    "fsaverage brain. Please install FreeSurfer and try again.")
             raise RuntimeError(err)
 
-        create_default_subject(mne_root, fs_home,
-                               subjects_dir=self.subjects_dir)
+        create_default_subject(fs_home=fs_home, subjects_dir=self.subjects_dir)
         self.refresh = True
+        self.use_high_res_head = False
         self.subject = 'fsaverage'
 
+    @on_trait_change('subjects_dir')
+    def _emit_subject(self):
+        # This silliness is the only way I could figure out to get the
+        # on_trait_change('subject_panel.subject') in CoregFrame to work!
+        self.subject = self.subject
+
 
 class SubjectSelectorPanel(HasPrivateTraits):
+    """Subject selector panel."""
+
     model = Instance(MRISubjectSource)
 
     can_create_fsaverage = DelegatesTo('model')
@@ -512,16 +513,22 @@ class SubjectSelectorPanel(HasPrivateTraits):
     subjects = DelegatesTo('model')
     use_high_res_head = DelegatesTo('model')
 
-    create_fsaverage = Button("Copy FsAverage to Subjects Folder",
-                              desc="Copy the files for the fsaverage subject "
-                              "to the subjects directory.")
+    create_fsaverage = Button(
+        "Copy 'fsaverage' to subjects directory",
+        desc="Copy the files for the fsaverage subject to the subjects "
+             "directory. This button is disabled if a subject called "
+             "fsaverage already exists in the selected subjects-directory.")
 
-    view = View(VGroup(Item('subjects_dir', label='subjects_dir'),
-                       'subject',
+    view = View(VGroup(Label('Subjects directory and subject:',
+                             show_label=True),
+                       HGroup('subjects_dir', show_labels=False),
+                       HGroup('subject', show_labels=False),
                        HGroup(Item('use_high_res_head',
-                                   label='High Resolution Head')),
-                       Item('create_fsaverage', show_label=False,
-                            enabled_when='can_create_fsaverage')))
+                                   label='High Resolution Head',
+                                   show_label=True)),
+                       Item('create_fsaverage',
+                            enabled_when='can_create_fsaverage'),
+                       show_labels=False))
 
     def _create_fsaverage_fired(self):
         # progress dialog with indefinite progress bar
@@ -534,8 +541,7 @@ class SubjectSelectorPanel(HasPrivateTraits):
         try:
             self.model.create_fsaverage()
         except Exception as err:
-            msg = str(err)
-            error(None, msg, "Error Creating FsAverage")
+            error(None, str(err), "Error Creating FsAverage")
             raise
         finally:
             prog.close()
@@ -543,8 +549,9 @@ class SubjectSelectorPanel(HasPrivateTraits):
     def _subjects_dir_changed(self, old, new):
         if new and self.subjects == ['']:
             information(None, "The directory selected as subjects-directory "
-                        "(%s) does not contain any valid MRI subjects. MRI "
-                        "subjects need to contain head surface models which "
+                        "(%s) does not contain any valid MRI subjects. If "
+                        "this is not expected make sure all MRI subjects have "
+                        "head surface model files which "
                         "can be created by running:\n\n    $ mne "
                         "make_scalp_surfaces" % self.subjects_dir,
                         "No Subjects Found")
diff --git a/mne/gui/_help.py b/mne/gui/_help.py
index c888e1a..d39e0f1 100644
--- a/mne/gui/_help.py
+++ b/mne/gui/_help.py
@@ -7,7 +7,7 @@ from textwrap import TextWrapper
 
 
 def read_tooltips(gui_name):
-    "Read and format tooltips, return a dict"
+    """Read and format tooltips, return a dict."""
     dirname = os.path.dirname(__file__)
     help_path = os.path.join(dirname, 'help', gui_name + '.json')
     with open(help_path) as fid:
diff --git a/mne/gui/_kit2fiff_gui.py b/mne/gui/_kit2fiff_gui.py
index d77b6b0..4b38def 100644
--- a/mne/gui/_kit2fiff_gui.py
+++ b/mne/gui/_kit2fiff_gui.py
@@ -1,4 +1,4 @@
-"""Mayavi/traits GUI for converting data from KIT systems"""
+"""Mayavi/traits GUI for converting data from KIT systems."""
 
 # Authors: Christian Brodbeck <christianbrodbeck at nyu.edu>
 #
@@ -6,6 +6,8 @@
 
 from collections import Counter
 import os
+import sys
+from warnings import warn
 
 import numpy as np
 from scipy.linalg import inv
@@ -13,40 +15,31 @@ from threading import Thread
 
 from ..externals.six.moves import queue
 from ..io.meas_info import _read_dig_points, _make_dig_points
-from ..utils import logger
-
-
-# allow import without traits
-try:
-    from mayavi.core.ui.mayavi_scene import MayaviScene
-    from mayavi.tools.mlab_scene_model import MlabSceneModel
-    from pyface.api import (confirm, error, FileDialog, OK, YES, information,
-                            ProgressDialog)
-    from traits.api import (HasTraits, HasPrivateTraits, cached_property,
-                            Instance, Property, Bool, Button, Enum, File,
-                            Float, Int, List, Str, Array, DelegatesTo)
-    from traitsui.api import (View, Item, HGroup, VGroup, spring, TextEditor,
-                              CheckListEditor, EnumEditor, Handler)
-    from traitsui.menu import NoButtons
-    from tvtk.pyface.scene_editor import SceneEditor
-except Exception:
-    from ..utils import trait_wraith
-    HasTraits = HasPrivateTraits = Handler = object
-    cached_property = MayaviScene = MlabSceneModel = Bool = Button = Float = \
-        DelegatesTo = Enum = File = Instance = Int = List = Property = \
-        Str = Array = spring = View = Item = HGroup = VGroup = EnumEditor = \
-        NoButtons = CheckListEditor = SceneEditor = TextEditor = trait_wraith
+from ..utils import get_config, set_config, logger
+
+from mayavi.core.ui.mayavi_scene import MayaviScene
+from mayavi.tools.mlab_scene_model import MlabSceneModel
+from pyface.api import (confirm, error, FileDialog, OK, YES, information,
+                        ProgressDialog, warning)
+from traits.api import (HasTraits, HasPrivateTraits, cached_property, Instance,
+                        Property, Bool, Button, Enum, File, Float, Int, List,
+                        Str, Array, DelegatesTo)
+from traits.trait_base import ETSConfig
+from traitsui.api import (View, Item, HGroup, VGroup, spring, TextEditor,
+                          CheckListEditor, EnumEditor, Handler)
+from traitsui.menu import NoButtons
+from tvtk.pyface.scene_editor import SceneEditor
 
 from ..io.constants import FIFF
-from ..io.kit.kit import RawKIT, KIT, _make_stim_channel, _default_stim_chs
+from ..io.kit.kit import (RawKIT, KIT, _make_stim_channel, _default_stim_chs,
+                          UnsupportedKITFormat)
 from ..transforms import (apply_trans, als_ras_trans,
                           get_ras_to_neuromag_trans, Transform)
 from ..coreg import _decimate_points, fit_matched_points
 from ..event import _find_events
 from ._marker_gui import CombineMarkersPanel, CombineMarkersModel
 from ._help import read_tooltips
-from ._viewer import (HeadViewController, headview_item, PointObject,
-                      _testing_mode)
+from ._viewer import HeadViewController, PointObject
 
 
 use_editor = CheckListEditor(cols=5, values=[(i, str(i)) for i in range(5)])
@@ -56,6 +49,11 @@ if backend_is_wx:
     hsp_wildcard = ['Head Shape Points (*.hsp;*.txt)|*.hsp;*.txt']
     elp_wildcard = ['Head Shape Fiducials (*.elp;*.txt)|*.elp;*.txt']
     kit_con_wildcard = ['Continuous KIT Files (*.sqd;*.con)|*.sqd;*.con']
+elif sys.platform in ('win32',  'linux2'):
+    # on Windows and Ubuntu, multiple wildcards does not seem to work
+    hsp_wildcard = ['*.hsp', '*.txt']
+    elp_wildcard = ['*.elp', '*.txt']
+    kit_con_wildcard = ['*.sqd', '*.con']
 else:
     hsp_wildcard = ['*.hsp;*.txt']
     elp_wildcard = ['*.elp;*.txt']
@@ -66,16 +64,17 @@ tooltips = read_tooltips('kit2fiff')
 
 
 class Kit2FiffModel(HasPrivateTraits):
-    """Data Model for Kit2Fiff conversion
-
-     - Markers are transformed into RAS coordinate system (as are the sensor
-       coordinates).
-     - Head shape digitizer data is transformed into neuromag-like space.
+    """Data Model for Kit2Fiff conversion.
 
+    - Markers are transformed into RAS coordinate system (as are the sensor
+      coordinates).
+    - Head shape digitizer data is transformed into neuromag-like space.
     """
+
     # Input Traits
     markers = Instance(CombineMarkersModel, ())
     sqd_file = File(exists=True, filter=kit_con_wildcard)
+    allow_unknown_format = Bool(False)
     hsp_file = File(exists=True, filter=hsp_wildcard)
     fid_file = File(exists=True, filter=elp_wildcard)
     stim_coding = Enum(">", "<", "channel")
@@ -126,7 +125,7 @@ class Kit2FiffModel(HasPrivateTraits):
 
     @cached_property
     def _get_can_save(self):
-        "Only allow saving when either all or no head shape elements are set."
+        """Only allow saving when all or no head shape elements are set."""
         if not self.stim_chs_ok:
             return False
 
@@ -185,7 +184,7 @@ class Kit2FiffModel(HasPrivateTraits):
             if self.show_gui:
                 error(None, str(err), "Error Reading Fiducials")
             self.reset_traits(['fid_file'])
-            raise err
+            raise
         else:
             return pts
 
@@ -285,8 +284,10 @@ class Kit2FiffModel(HasPrivateTraits):
             data, times = self.raw[self.misc_chs]
         except Exception as err:
             if self.show_gui:
-                error(None, str(err), "Error Creating FsAverage")
-            raise err
+                error(None, "Error reading SQD data file: %s (Check the "
+                      "terminal output for details)" % str(err),
+                      "Error Reading SQD File")
+            raise
         finally:
             if self.show_gui:
                 prog.close()
@@ -309,14 +310,25 @@ class Kit2FiffModel(HasPrivateTraits):
         if not self.sqd_file:
             return
         try:
-            return RawKIT(self.sqd_file, stim=None)
+            return RawKIT(self.sqd_file, stim=None,
+                          allow_unknown_format=self.allow_unknown_format)
+        except UnsupportedKITFormat as exception:
+            warning(
+                None,
+                "The selected SQD file is written in an old file format (%s) "
+                "that is not officially supported. Confirm that the results "
+                "are as expected. This warning is displayed only once per "
+                "session." % (exception.sqd_version,),
+                "Unsupported SQD File Format")
+            self.allow_unknown_format = True
+            return self._get_raw()
         except Exception as err:
             self.reset_traits(['sqd_file'])
             if self.show_gui:
                 error(None, "Error reading SQD data file: %s (Check the "
                       "terminal output for details)" % str(err),
-                      "Error Reading SQD file")
-            raise err
+                      "Error Reading SQD File")
+            raise
 
     @cached_property
     def _get_sqd_fname(self):
@@ -360,12 +372,12 @@ class Kit2FiffModel(HasPrivateTraits):
         return self.stim_chs_array is not None
 
     def clear_all(self):
-        """Clear all specified input parameters"""
+        """Clear all specified input parameters."""
         self.markers.clear = True
         self.reset_traits(['sqd_file', 'hsp_file', 'fid_file', 'use_mrk'])
 
     def get_event_info(self):
-        """Count events with current stim channel settings
+        """Count events with current stim channel settings.
 
         Returns
         -------
@@ -388,8 +400,7 @@ class Kit2FiffModel(HasPrivateTraits):
         return Counter(events[:, 2])
 
     def get_raw(self, preload=False):
-        """Create a raw object based on the current model settings
-        """
+        """Create a raw object based on the current model settings."""
         if not self.can_save:
             raise ValueError("Not all necessary parameters are set")
 
@@ -418,9 +429,9 @@ class Kit2FiffModel(HasPrivateTraits):
 
 
 class Kit2FiffFrameHandler(Handler):
-    """Handler that checks for unfinished processes before closing its window
-    """
-    def close(self, info, is_ok):
+    """Check for unfinished processes before closing its window."""
+
+    def close(self, info, is_ok):  # noqa: D102
         if info.object.kit2fiff_panel.queue.unfinished_tasks:
             msg = ("Can not close the window while saving is still in "
                    "progress. Please wait until all files are processed.")
@@ -428,11 +439,17 @@ class Kit2FiffFrameHandler(Handler):
             information(None, msg, title)
             return False
         else:
+            # store configuration, but don't prevent from closing on error
+            try:
+                info.object.save_config()
+            except Exception as exc:
+                warn("Error saving GUI configuration:\n%s" % (exc,))
             return True
 
 
 class Kit2FiffPanel(HasPrivateTraits):
-    """Control panel for kit2fiff conversion"""
+    """Control panel for kit2fiff conversion."""
+
     model = Instance(Kit2FiffModel)
 
     # model copies for view
@@ -527,11 +544,11 @@ class Kit2FiffPanel(HasPrivateTraits):
                )
     )
 
-    def __init__(self, *args, **kwargs):
+    def __init__(self, *args, **kwargs):  # noqa: D102
         super(Kit2FiffPanel, self).__init__(*args, **kwargs)
 
         # setup save worker
-        def worker():
+        def worker():  # noqa: D102
             while True:
                 raw, fname = self.queue.get()
                 basename = os.path.basename(fname)
@@ -557,20 +574,19 @@ class Kit2FiffPanel(HasPrivateTraits):
         t.start()
 
         # setup mayavi visualization
-        m = self.model
-        self.fid_obj = PointObject(scene=self.scene, color=(25, 225, 25),
+        self.fid_obj = PointObject(scene=self.scene, color=(0.1, 1., 0.1),
                                    point_scale=5e-3, name='Fiducials')
-        self.elp_obj = PointObject(scene=self.scene, color=(50, 50, 220),
+        self.elp_obj = PointObject(scene=self.scene,
+                                   color=(0.196, 0.196, 0.863),
                                    point_scale=1e-2, opacity=.2, name='ELP')
-        self.hsp_obj = PointObject(scene=self.scene, color=(200, 200, 200),
+        self.hsp_obj = PointObject(scene=self.scene, color=(0.784,) * 3,
                                    point_scale=2e-3, name='HSP')
-        if not _testing_mode():
-            for name, obj in zip(['fid', 'elp', 'hsp'],
-                                 [self.fid_obj, self.elp_obj, self.hsp_obj]):
-                m.sync_trait(name, obj, 'points', mutual=False)
-                m.sync_trait('head_dev_trans', obj, 'trans', mutual=False)
-            self.scene.camera.parallel_scale = 0.15
-            self.scene.mlab.view(0, 0, .15)
+        for name in ('fid', 'elp', 'hsp'):
+            obj = getattr(self, name + '_obj')
+            self.model.sync_trait(name, obj, 'points', mutual=False)
+            self.model.sync_trait('head_dev_trans', obj, 'trans', mutual=False)
+        self.scene.camera.parallel_scale = 0.15
+        self.scene.mlab.view(0, 0, .15)
 
     def _clear_all_fired(self):
         self.model.clear_all()
@@ -629,7 +645,7 @@ class Kit2FiffPanel(HasPrivateTraits):
             error(None, "Error reading events from SQD data file: %s (Check "
                   "the terminal output for details)" % str(err),
                   "Error Reading events from SQD file")
-            raise err
+            raise
 
         if len(events) == 0:
             information(None, "No events were found with the current "
@@ -642,8 +658,9 @@ class Kit2FiffPanel(HasPrivateTraits):
 
 
 class Kit2FiffFrame(HasTraits):
-    """GUI for interpolating between two KIT marker files"""
-    model = Instance(Kit2FiffModel, kw={'show_gui': True})
+    """GUI for interpolating between two KIT marker files."""
+
+    model = Instance(Kit2FiffModel)
     scene = Instance(MlabSceneModel, ())
     headview = Instance(HeadViewController)
     marker_panel = Instance(CombineMarkersPanel)
@@ -654,7 +671,8 @@ class Kit2FiffFrame(HasTraits):
                        VGroup(Item('scene',
                                    editor=SceneEditor(scene_class=MayaviScene),
                                    dock='vertical', show_label=False),
-                              VGroup(headview_item, show_labels=False),
+                              VGroup(Item('headview', style='custom'),
+                                     show_labels=False),
                               ),
                        VGroup(Item('kit2fiff_panel', style='custom'),
                               show_labels=False),
@@ -663,6 +681,44 @@ class Kit2FiffFrame(HasTraits):
                 handler=Kit2FiffFrameHandler(),
                 height=700, resizable=True, buttons=NoButtons)
 
+    def __init__(self, *args, **kwargs):  # noqa: D102
+        logger.debug(
+            "Initializing Kit2fiff-GUI with %s backend", ETSConfig.toolkit)
+        HasTraits.__init__(self, *args, **kwargs)
+
+    # can't be static method due to Traits
+    def _model_default(self):
+        # load configuration values and make sure they're valid
+        config = get_config(home_dir=os.environ.get('_MNE_FAKE_HOME_DIR'))
+        stim_threshold = 1.
+        if 'MNE_KIT2FIFF_STIM_CHANNEL_THRESHOLD' in config:
+            try:
+                stim_threshold = float(
+                    config['MNE_KIT2FIFF_STIM_CHANNEL_THRESHOLD'])
+            except ValueError:
+                warn("Ignoring invalid configuration value for "
+                     "MNE_KIT2FIFF_STIM_CHANNEL_THRESHOLD: %r (expected "
+                     "float)" %
+                     (config['MNE_KIT2FIFF_STIM_CHANNEL_THRESHOLD'],))
+        stim_slope = config.get('MNE_KIT2FIFF_STIM_CHANNEL_SLOPE', '-')
+        if stim_slope not in '+-':
+            warn("Ignoring invalid configuration value for "
+                 "MNE_KIT2FIFF_STIM_CHANNEL_THRESHOLD: %s (expected + or -)" %
+                 stim_slope)
+            stim_slope = '-'
+        stim_coding = config.get('MNE_KIT2FIFF_STIM_CHANNEL_CODING', '>')
+        if stim_coding not in ('<', '>', 'channel'):
+            warn("Ignoring invalid configuration value for "
+                 "MNE_KIT2FIFF_STIM_CHANNEL_CODING: %s (expected <, > or "
+                 "channel)" % stim_coding)
+            stim_coding = '>'
+        return Kit2FiffModel(
+            stim_chs=config.get('MNE_KIT2FIFF_STIM_CHANNELS', ''),
+            stim_coding=stim_coding,
+            stim_slope=stim_slope,
+            stim_threshold=stim_threshold,
+            show_gui=True)
+
     def _headview_default(self):
         return HeadViewController(scene=self.scene, scale=160, system='RAS')
 
@@ -672,3 +728,14 @@ class Kit2FiffFrame(HasTraits):
     def _marker_panel_default(self):
         return CombineMarkersPanel(scene=self.scene, model=self.model.markers,
                                    trans=als_ras_trans)
+
+    def save_config(self, home_dir=None):
+        """Write configuration values."""
+        set_config('MNE_KIT2FIFF_STIM_CHANNELS', self.model.stim_chs, home_dir,
+                   set_env=False)
+        set_config('MNE_KIT2FIFF_STIM_CHANNEL_CODING', self.model.stim_coding,
+                   home_dir, set_env=False)
+        set_config('MNE_KIT2FIFF_STIM_CHANNEL_SLOPE', self.model.stim_slope,
+                   home_dir, set_env=False)
+        set_config('MNE_KIT2FIFF_STIM_CHANNEL_THRESHOLD',
+                   str(self.model.stim_threshold), home_dir, set_env=False)
diff --git a/mne/gui/_marker_gui.py b/mne/gui/_marker_gui.py
index ebe4436..c27b99f 100644
--- a/mne/gui/_marker_gui.py
+++ b/mne/gui/_marker_gui.py
@@ -1,31 +1,23 @@
-"""Mayavi/traits GUI for averaging two sets of KIT marker points"""
+"""Mayavi/traits GUI for averaging two sets of KIT marker points."""
 
 # Authors: Christian Brodbeck <christianbrodbeck at nyu.edu>
 #
 # License: BSD (3-clause)
 
 import os
+import sys
 
 import numpy as np
 
-# allow import without traits
-try:
-    from mayavi.core.ui.mayavi_scene import MayaviScene
-    from mayavi.tools.mlab_scene_model import MlabSceneModel
-    from pyface.api import confirm, error, FileDialog, OK, YES
-    from traits.api import (HasTraits, HasPrivateTraits, on_trait_change,
-                            cached_property, Instance, Property, Array, Bool,
-                            Button, Enum, File, Float, List, Str)
-    from traitsui.api import View, Item, HGroup, VGroup, CheckListEditor
-    from traitsui.menu import NoButtons
-    from tvtk.pyface.scene_editor import SceneEditor
-except Exception:
-    from ..utils import trait_wraith
-    HasTraits = HasPrivateTraits = object
-    cached_property = on_trait_change = MayaviScene = MlabSceneModel = \
-        Array = Bool = Button = Enum = File = Float = Instance = Int = \
-        List = Property = Str = View = Item = HGroup = VGroup = \
-        CheckListEditor = NoButtons = SceneEditor = trait_wraith
+from mayavi.core.ui.mayavi_scene import MayaviScene
+from mayavi.tools.mlab_scene_model import MlabSceneModel
+from pyface.api import confirm, error, FileDialog, OK, YES
+from traits.api import (HasTraits, HasPrivateTraits, on_trait_change,
+                        cached_property, Instance, Property, Array, Bool,
+                        Button, Enum, File, Float, List, Str)
+from traitsui.api import View, Item, HGroup, VGroup, CheckListEditor
+from traitsui.menu import NoButtons
+from tvtk.pyface.scene_editor import SceneEditor
 
 from ..transforms import apply_trans, rotation, translation
 from ..coreg import fit_matched_points
@@ -43,7 +35,11 @@ if backend_is_wx:
                     'Pickled markers (*.pickled)|*.pickled']
     mrk_out_wildcard = ["Tab separated values file (*.txt)|*.txt"]
 else:
-    mrk_wildcard = ["*.sqd;*.mrk;*.txt;*.pickled"]
+    if sys.platform in ('win32',  'linux2'):
+        # on Windows and Ubuntu, multiple wildcards does not seem to work
+        mrk_wildcard = ["*.sqd", "*.mrk", "*.txt", "*.pickled"]
+    else:
+        mrk_wildcard = ["*.sqd;*.mrk;*.txt;*.pickled"]
     mrk_out_wildcard = "*.txt"
 out_ext = '.txt'
 
@@ -79,7 +75,8 @@ mrk_view_edit = View(VGroup('points'))
 
 
 class MarkerPoints(HasPrivateTraits):
-    """Represent 5 marker points"""
+    """Represent 5 marker points."""
+
     points = Array(float, (5, 3))
 
     can_save = Property(depends_on='points')
@@ -113,7 +110,7 @@ class MarkerPoints(HasPrivateTraits):
         self.save(path)
 
     def save(self, path):
-        """Save the marker points
+        """Save the marker points.
 
         Parameters
         ----------
@@ -125,8 +122,9 @@ class MarkerPoints(HasPrivateTraits):
         _write_dig_points(path, self.points)
 
 
-class MarkerPointSource(MarkerPoints):
-    """MarkerPoints subclass for source files"""
+class MarkerPointSource(MarkerPoints):  # noqa: D401
+    """MarkerPoints subclass for source files."""
+
     file = File(filter=mrk_wildcard, exists=True)
     name = Property(Str, depends_on='file')
     dir = Property(Str, depends_on='file')
@@ -174,8 +172,9 @@ class MarkerPointSource(MarkerPoints):
         self.edit_traits(view=mrk_view_edit)
 
 
-class MarkerPointDest(MarkerPoints):
-    """MarkerPoints subclass that serves for derived points"""
+class MarkerPointDest(MarkerPoints):  # noqa: D401
+    """MarkerPoints subclass that serves for derived points."""
+
     src1 = Instance(MarkerPointSource)
     src2 = Instance(MarkerPointSource)
 
@@ -288,6 +287,8 @@ class MarkerPointDest(MarkerPoints):
 
 
 class CombineMarkersModel(HasPrivateTraits):
+    """Combine markers model."""
+
     mrk1_file = Instance(File)
     mrk2_file = Instance(File)
     mrk1 = Instance(MarkerPointSource)
@@ -305,22 +306,19 @@ class CombineMarkersModel(HasPrivateTraits):
         self.mrk3.reset_traits(['method'])
 
     def _mrk1_default(self):
-        mrk = MarkerPointSource()
-        return mrk
+        return MarkerPointSource()
 
     def _mrk1_file_default(self):
         return self.mrk1.trait('file')
 
     def _mrk2_default(self):
-        mrk = MarkerPointSource()
-        return mrk
+        return MarkerPointSource()
 
     def _mrk2_file_default(self):
         return self.mrk2.trait('file')
 
     def _mrk3_default(self):
-        mrk = MarkerPointDest(src1=self.mrk1, src2=self.mrk2)
-        return mrk
+        return MarkerPointDest(src1=self.mrk1, src2=self.mrk2)
 
     @cached_property
     def _get_distance(self):
@@ -334,8 +332,9 @@ class CombineMarkersModel(HasPrivateTraits):
         return desc
 
 
-class CombineMarkersPanel(HasTraits):
-    """Has two marker points sources and interpolates to a third one"""
+class CombineMarkersPanel(HasTraits):  # noqa: D401
+    """Has two marker points sources and interpolates to a third one."""
+
     model = Instance(CombineMarkersModel, ())
 
     # model references for UI
@@ -377,27 +376,30 @@ class CombineMarkersPanel(HasTraits):
     def _mrk3_default(self):
         return self.model.mrk3
 
-    def __init__(self, *args, **kwargs):
+    def __init__(self, *args, **kwargs):  # noqa: D102
         super(CombineMarkersPanel, self).__init__(*args, **kwargs)
 
         m = self.model
         m.sync_trait('distance', self, 'distance', mutual=False)
 
-        self.mrk1_obj = PointObject(scene=self.scene, color=(155, 55, 55),
+        self.mrk1_obj = PointObject(scene=self.scene,
+                                    color=(0.608, 0.216, 0.216),
                                     point_scale=self.scale)
         self.sync_trait('trans', self.mrk1_obj, mutual=False)
         m.mrk1.sync_trait('points', self.mrk1_obj, 'points', mutual=False)
         m.mrk1.sync_trait('enabled', self.mrk1_obj, 'visible',
                           mutual=False)
 
-        self.mrk2_obj = PointObject(scene=self.scene, color=(55, 155, 55),
+        self.mrk2_obj = PointObject(scene=self.scene,
+                                    color=(0.216, 0.608, 0.216),
                                     point_scale=self.scale)
         self.sync_trait('trans', self.mrk2_obj, mutual=False)
         m.mrk2.sync_trait('points', self.mrk2_obj, 'points', mutual=False)
         m.mrk2.sync_trait('enabled', self.mrk2_obj, 'visible',
                           mutual=False)
 
-        self.mrk3_obj = PointObject(scene=self.scene, color=(150, 200, 255),
+        self.mrk3_obj = PointObject(scene=self.scene,
+                                    color=(0.588, 0.784, 1.),
                                     point_scale=self.scale)
         self.sync_trait('trans', self.mrk3_obj, mutual=False)
         m.mrk3.sync_trait('points', self.mrk3_obj, 'points', mutual=False)
@@ -405,13 +407,14 @@ class CombineMarkersPanel(HasTraits):
 
 
 class CombineMarkersFrame(HasTraits):
-    """GUI for interpolating between two KIT marker files
+    """GUI for interpolating between two KIT marker files.
 
     Parameters
     ----------
     mrk1, mrk2 : str
         Path to pre- and post measurement marker files (*.sqd) or empty string.
     """
+
     model = Instance(CombineMarkersModel, ())
     scene = Instance(MlabSceneModel, ())
     headview = Instance(HeadViewController)
diff --git a/mne/gui/_viewer.py b/mne/gui/_viewer.py
index 4f9bf75..3edc888 100644
--- a/mne/gui/_viewer.py
+++ b/mne/gui/_viewer.py
@@ -1,53 +1,35 @@
-"""Mayavi/traits GUI visualization elements"""
+"""Mayavi/traits GUI visualization elements."""
 
 # Authors: Christian Brodbeck <christianbrodbeck at nyu.edu>
 #
 # License: BSD (3-clause)
 
-import os
 import numpy as np
 
-# allow import without traits
-try:
-    from mayavi.mlab import pipeline, text3d
-    from mayavi.modules.glyph import Glyph
-    from mayavi.modules.surface import Surface
-    from mayavi.sources.vtk_data_source import VTKDataSource
-    from mayavi.tools.mlab_scene_model import MlabSceneModel
-    from pyface.api import error
-    from traits.api import (HasTraits, HasPrivateTraits, on_trait_change,
-                            cached_property, Instance, Property, Array, Bool,
-                            Button, Color, Enum, Float, Int, List, Range, Str)
-    from traitsui.api import View, Item, Group, HGroup, VGrid, VGroup
-except Exception:
-    from ..utils import trait_wraith
-    HasTraits = HasPrivateTraits = object
-    cached_property = on_trait_change = MlabSceneModel = Array = Bool = \
-        Button = Color = Enum = Float = Instance = Int = List = Property = \
-        Range = Str = View = Item = Group = HGroup = VGrid = VGroup = \
-        Glyph = Surface = VTKDataSource = trait_wraith
-
+from mayavi.mlab import pipeline, text3d
+from mayavi.modules.glyph import Glyph
+from mayavi.modules.surface import Surface
+from mayavi.sources.vtk_data_source import VTKDataSource
+from mayavi.tools.mlab_scene_model import MlabSceneModel
+from pyface.api import error
+from traits.api import (HasTraits, HasPrivateTraits, on_trait_change,
+                        Instance, Array, Bool, Button, Enum, Float, Int, List,
+                        Range, Str, RGBColor)
+from traitsui.api import View, Item, HGroup, VGrid, VGroup
+from tvtk.api import tvtk
+
+from ..surface import complete_surface_info
 from ..transforms import apply_trans
+from ..utils import SilenceStdout
+from ..viz._3d import _create_mesh_surf, _toggle_mlab_render
 
 
-headview_item = Item('headview', style='custom', show_label=False)
 headview_borders = VGroup(Item('headview', style='custom', show_label=False),
                           show_border=True, label='View')
-defaults = {'mri_fid_scale': 1e-2, 'hsp_fid_scale': 3e-2,
-            'hsp_fid_opacity': 0.3, 'hsp_points_scale': 4e-3,
-            'mri_color': (252, 227, 191), 'hsp_point_color': (255, 255, 255),
-            'lpa_color': (255, 0, 0), 'nasion_color': (0, 255, 0),
-            'rpa_color': (0, 0, 255)}
-
-
-def _testing_mode():
-    """Helper to determine if we're running tests"""
-    return (os.getenv('_MNE_GUI_TESTING_MODE', '') == 'true')
 
 
 class HeadViewController(HasTraits):
-    """
-    Set head views for Anterior-Left-Superior coordinate system
+    """Set head views for the given coordinate system.
 
     Parameters
     ----------
@@ -56,6 +38,7 @@ class HeadViewController(HasTraits):
         the x, y, and z axes. Relevant terms are: Anterior, Right, Left,
         Superior, Inferior.
     """
+
     system = Enum("RAS", "ALS", "ARI", desc="Coordinate system: directions of "
                   "the x, y, and z axis.")
 
@@ -63,6 +46,7 @@ class HeadViewController(HasTraits):
     front = Button()
     left = Button()
     top = Button()
+    interaction = Enum('Trackball', 'Terrain')
 
     scale = Float(0.16)
 
@@ -70,11 +54,13 @@ class HeadViewController(HasTraits):
 
     view = View(VGrid('0', 'top', '0', Item('scale', label='Scale',
                                             show_label=True),
-                      'right', 'front', 'left', show_labels=False, columns=4))
+                      'right', 'front', 'left', 'interaction',
+                      show_labels=False, columns=4))
 
     @on_trait_change('scene.activated')
     def _init_view(self):
         self.scene.parallel_projection = True
+        self._trackball_interactor = None
 
         # apparently scene,activated happens several times
         if self.scene.renderer:
@@ -82,54 +68,57 @@ class HeadViewController(HasTraits):
             # and apparently this does not happen by default:
             self.on_trait_change(self.scene.render, 'scale')
 
+    @on_trait_change('interaction')
+    def on_set_interaction(self, _, interaction):
+        if self.scene is None:
+            return
+        if interaction == 'Terrain':
+            # Ensure we're in the correct orientatino for the
+            # InteractorStyleTerrain to have the correct "up"
+            if self._trackball_interactor is None:
+                self._trackball_interactor = \
+                    self.scene.interactor.interactor_style
+            self.on_set_view('front', '')
+            self.scene.mlab.draw()
+            self.scene.interactor.interactor_style = \
+                tvtk.InteractorStyleTerrain()
+            self.on_set_view('front', '')
+            self.scene.mlab.draw()
+        else:  # interaction == 'trackball'
+            self.scene.interactor.interactor_style = self._trackball_interactor
+
     @on_trait_change('top,left,right,front')
     def on_set_view(self, view, _):
         if self.scene is None:
             return
 
         system = self.system
-        kwargs = None
-
-        if system == 'ALS':
-            if view == 'front':
-                kwargs = dict(azimuth=0, elevation=90, roll=-90)
-            elif view == 'left':
-                kwargs = dict(azimuth=90, elevation=90, roll=180)
-            elif view == 'right':
-                kwargs = dict(azimuth=-90, elevation=90, roll=0)
-            elif view == 'top':
-                kwargs = dict(azimuth=0, elevation=0, roll=-90)
-        elif system == 'RAS':
-            if view == 'front':
-                kwargs = dict(azimuth=90, elevation=90, roll=180)
-            elif view == 'left':
-                kwargs = dict(azimuth=180, elevation=90, roll=90)
-            elif view == 'right':
-                kwargs = dict(azimuth=0, elevation=90, roll=270)
-            elif view == 'top':
-                kwargs = dict(azimuth=90, elevation=0, roll=180)
-        elif system == 'ARI':
-            if view == 'front':
-                kwargs = dict(azimuth=0, elevation=90, roll=90)
-            elif view == 'left':
-                kwargs = dict(azimuth=-90, elevation=90, roll=180)
-            elif view == 'right':
-                kwargs = dict(azimuth=90, elevation=90, roll=0)
-            elif view == 'top':
-                kwargs = dict(azimuth=0, elevation=180, roll=90)
-        else:
+        kwargs = dict(ALS=dict(front=(0, 90, -90),
+                               left=(90, 90, 180),
+                               right=(-90, 90, 0),
+                               top=(0, 0, -90)),
+                      RAS=dict(front=(90., 90., 180),
+                               left=(180, 90, 90),
+                               right=(0., 90, 270),
+                               top=(90, 0, 180)),
+                      ARI=dict(front=(0, 90, 90),
+                               left=(-90, 90, 180),
+                               right=(90, 90, 0),
+                               top=(0, 180, 90)))
+        if system not in kwargs:
             raise ValueError("Invalid system: %r" % system)
-
-        if kwargs is None:
+        if view not in kwargs[system]:
             raise ValueError("Invalid view: %r" % view)
-
-        if not _testing_mode():
+        kwargs = dict(zip(('azimuth', 'elevation', 'roll'),
+                          kwargs[system][view]))
+        with SilenceStdout():
             self.scene.mlab.view(distance=None, reset_roll=True,
                                  figure=self.scene.mayavi_scene, **kwargs)
 
 
 class Object(HasPrivateTraits):
-    """Represents a 3d object in a mayavi scene"""
+    """Represent a 3d object in a mayavi scene."""
+
     points = Array(float, shape=(None, 3))
     trans = Array()
     name = Str
@@ -137,23 +126,15 @@ class Object(HasPrivateTraits):
     scene = Instance(MlabSceneModel, ())
     src = Instance(VTKDataSource)
 
-    color = Color()
-    rgbcolor = Property(depends_on='color')
+    # This should be Tuple, but it is broken on Anaconda as of 2016/12/16
+    color = RGBColor((1., 1., 1.))
     point_scale = Float(10, label='Point Scale')
     opacity = Range(low=0., high=1., value=1.)
     visible = Bool(True)
 
-    @cached_property
-    def _get_rgbcolor(self):
-        if hasattr(self.color, 'Get'):  # wx
-            color = tuple(v / 255. for v in self.color.Get())
-        else:
-            color = self.color.getRgbF()[:3]
-        return color
-
     @on_trait_change('trans,points')
     def _update_points(self):
-        """Update the location of the plotted points"""
+        """Update the location of the plotted points."""
         if not hasattr(self.src, 'data'):
             return
 
@@ -175,18 +156,25 @@ class Object(HasPrivateTraits):
             pts = self.points
 
         self.src.data.points = pts
+        return True
 
 
 class PointObject(Object):
-    """Represents a group of individual points in a mayavi scene"""
+    """Represent a group of individual points in a mayavi scene."""
+
     label = Bool(False, enabled_when='visible')
     text3d = List
 
     glyph = Instance(Glyph)
     resolution = Int(8)
 
+    view = View(HGroup(Item('visible', show_label=False),
+                       Item('color', show_label=False),
+                       Item('opacity')))
+
     def __init__(self, view='points', *args, **kwargs):
-        """
+        """Init.
+
         Parameters
         ----------
         view : 'points' | 'cloud'
@@ -196,7 +184,7 @@ class PointObject(Object):
         self._view = view
         super(PointObject, self).__init__(*args, **kwargs)
 
-    def default_traits_view(self):
+    def default_traits_view(self):  # noqa: D102
         color = Item('color', show_label=False)
         scale = Item('point_scale', label='Size')
         if self._view == 'points':
@@ -211,7 +199,7 @@ class PointObject(Object):
 
     @on_trait_change('label')
     def _show_labels(self, show):
-        self.scene.disable_render = True
+        _toggle_mlab_render(self, False)
         while self.text3d:
             text = self.text3d.pop()
             text.remove()
@@ -220,11 +208,10 @@ class PointObject(Object):
             fig = self.scene.mayavi_scene
             for i, pt in enumerate(np.array(self.src.data.points)):
                 x, y, z = pt
-                t = text3d(x, y, z, ' %i' % i, scale=.01, color=self.rgbcolor,
+                t = text3d(x, y, z, ' %i' % i, scale=.01, color=self.color,
                            figure=fig)
                 self.text3d.append(t)
-
-        self.scene.disable_render = False
+        _toggle_mlab_render(self, True)
 
     @on_trait_change('visible')
     def _on_hide(self):
@@ -234,32 +221,35 @@ class PointObject(Object):
     @on_trait_change('scene.activated')
     def _plot_points(self):
         """Add the points to the mayavi pipeline"""
-#         _scale = self.scene.camera.parallel_scale
-
+        if self.scene is None:
+            return
         if hasattr(self.glyph, 'remove'):
             self.glyph.remove()
         if hasattr(self.src, 'remove'):
             self.src.remove()
 
-        if not _testing_mode():
-            fig = self.scene.mayavi_scene
-        else:
-            fig = None
-
+        _toggle_mlab_render(self, False)
         x, y, z = self.points.T
-        scatter = pipeline.scalar_scatter(x, y, z)
-        glyph = pipeline.glyph(scatter, color=self.rgbcolor, figure=fig,
+        fig = self.scene.mayavi_scene
+        scatter = pipeline.scalar_scatter(x, y, z, fig=fig)
+        if not scatter.running:
+            # this can occur sometimes during testing w/ui.dispose()
+            return
+        # fig.scene.engine.current_object is scatter
+        glyph = pipeline.glyph(scatter, color=self.color,
+                               figure=fig,
                                scale_factor=self.point_scale, opacity=1.,
                                resolution=self.resolution)
+        glyph.actor.property.backface_culling = True
         self.src = scatter
         self.glyph = glyph
 
         self.sync_trait('point_scale', self.glyph.glyph.glyph, 'scale_factor')
-        self.sync_trait('rgbcolor', self.glyph.actor.property, 'color',
-                        mutual=False)
+        self.sync_trait('color', self.glyph.actor.property, mutual=False)
         self.sync_trait('visible', self.glyph)
         self.sync_trait('opacity', self.glyph.actor.property)
         self.on_trait_change(self._update_points, 'points')
+        _toggle_mlab_render(self, True)
 
 #         self.scene.camera.parallel_scale = _scale
 
@@ -272,24 +262,25 @@ class PointObject(Object):
 
 
 class SurfaceObject(Object):
-    """Represents a solid object in a mayavi scene
+    """Represent a solid object in a mayavi scene.
 
     Notes
     -----
     Doesn't automatically update plot because update requires both
     :attr:`points` and :attr:`tri`. Call :meth:`plot` after updateing both
     attributes.
-
     """
+
     rep = Enum("Surface", "Wireframe")
     tri = Array(int, shape=(None, 3))
 
     surf = Instance(Surface)
 
     view = View(HGroup(Item('visible', show_label=False),
-                       Item('color', show_label=False), Item('opacity')))
+                       Item('color', show_label=False),
+                       Item('opacity')))
 
-    def clear(self):
+    def clear(self):  # noqa: D102
         if hasattr(self.src, 'remove'):
             self.src.remove()
         if hasattr(self.surf, 'remove'):
@@ -299,33 +290,31 @@ class SurfaceObject(Object):
     @on_trait_change('scene.activated')
     def plot(self):
         """Add the points to the mayavi pipeline"""
-        _scale = self.scene.camera.parallel_scale if not _testing_mode() else 1
+        _scale = self.scene.camera.parallel_scale
         self.clear()
 
         if not np.any(self.tri):
             return
 
         fig = self.scene.mayavi_scene
-
-        x, y, z = self.points.T
-
-        if self.rep == 'Wireframe':
-            rep = 'wireframe'
-        else:
-            rep = 'surface'
-
-        src = pipeline.triangular_mesh_source(x, y, z, self.tri, figure=fig)
-        surf = pipeline.surface(src, figure=fig, color=self.rgbcolor,
-                                opacity=self.opacity,
+        surf = complete_surface_info(dict(rr=self.points, tris=self.tri),
+                                     verbose='error')
+        src = _create_mesh_surf(surf, fig=fig)
+        rep = 'wireframe' if self.rep == 'Wireframe' else 'surface'
+        surf = pipeline.surface(src, figure=fig, color=self.color,
                                 representation=rep, line_width=1)
+        surf.actor.property.backface_culling = True
 
         self.src = src
         self.surf = surf
 
         self.sync_trait('visible', self.surf, 'visible')
-        self.sync_trait('rgbcolor', self.surf.actor.property, 'color',
-                        mutual=False)
-        self.sync_trait('opacity', self.surf.actor.property, 'opacity')
+        self.sync_trait('color', self.surf.actor.property, mutual=False)
+        self.sync_trait('opacity', self.surf.actor.property)
+
+        self.scene.camera.parallel_scale = _scale
 
-        if not _testing_mode():
-            self.scene.camera.parallel_scale = _scale
+    @on_trait_change('trans,points')
+    def _update_points(self):
+        if Object._update_points(self):
+            self.src.update()  # necessary for SurfaceObject since Mayavi 4.5.0
diff --git a/mne/gui/tests/test_coreg_gui.py b/mne/gui/tests/test_coreg_gui.py
index 4bdd276..74e5568 100644
--- a/mne/gui/tests/test_coreg_gui.py
+++ b/mne/gui/tests/test_coreg_gui.py
@@ -3,7 +3,10 @@
 # License: BSD (3-clause)
 
 import os
+import os.path as op
 import re
+import sys
+from unittest import SkipTest
 
 import numpy as np
 from numpy.testing import assert_allclose
@@ -14,8 +17,7 @@ import warnings
 import mne
 from mne.datasets import testing
 from mne.io.kit.tests import data_dir as kit_data_dir
-from mne.utils import (_TempDir, requires_traits, requires_mne,
-                       requires_freesurfer, run_tests_if_main, requires_mayavi)
+from mne.utils import _TempDir, run_tests_if_main, requires_mayavi
 from mne.externals.six import string_types
 
 # backend needs to be set early
@@ -28,22 +30,21 @@ else:
 
 
 data_path = testing.data_path(download=False)
-raw_path = os.path.join(data_path, 'MEG', 'sample',
-                        'sample_audvis_trunc_raw.fif')
-fname_trans = os.path.join(data_path, 'MEG', 'sample',
-                           'sample_audvis_trunc-trans.fif')
-kit_raw_path = os.path.join(kit_data_dir, 'test_bin_raw.fif')
-subjects_dir = os.path.join(data_path, 'subjects')
+raw_path = op.join(data_path, 'MEG', 'sample', 'sample_audvis_trunc_raw.fif')
+fname_trans = op.join(data_path, 'MEG', 'sample',
+                      'sample_audvis_trunc-trans.fif')
+kit_raw_path = op.join(kit_data_dir, 'test_bin_raw.fif')
+subjects_dir = op.join(data_path, 'subjects')
 warnings.simplefilter('always')
 
 
 @testing.requires_testing_data
- at requires_traits
+ at requires_mayavi
 def test_coreg_model():
-    """Test CoregModel"""
+    """Test CoregModel."""
     from mne.gui._coreg_gui import CoregModel
     tempdir = _TempDir()
-    trans_dst = os.path.join(tempdir, 'test-trans.fif')
+    trans_dst = op.join(tempdir, 'test-trans.fif')
 
     model = CoregModel()
     assert_raises(RuntimeError, model.save_trans, 'blah.fif')
@@ -113,8 +114,12 @@ def test_coreg_model():
     assert_true(isinstance(model.points_eval_str, string_types))
 
     # scaling job
-    sdir, sfrom, sto, scale, skip_fiducials, bemsol = \
-        model.get_scaling_job('sample2', False, True)
+    assert_false(model.can_prepare_bem_model)
+    model.n_scale_params = 1
+    assert_true(model.can_prepare_bem_model)
+    model.prepare_bem_model = True
+    sdir, sfrom, sto, scale, skip_fiducials, labels, annot, bemsol = \
+        model.get_scaling_job('sample2', False)
     assert_equal(sdir, subjects_dir)
     assert_equal(sfrom, 'sample')
     assert_equal(sto, 'sample2')
@@ -122,39 +127,98 @@ def test_coreg_model():
     assert_equal(skip_fiducials, False)
     # find BEM files
     bems = set()
-    for fname in os.listdir(os.path.join(subjects_dir, 'sample', 'bem')):
-        match = re.match('sample-(.+-bem)\.fif', fname)
+    for fname in os.listdir(op.join(subjects_dir, 'sample', 'bem')):
+        match = re.match(r'sample-(.+-bem)\.fif', fname)
         if match:
             bems.add(match.group(1))
     assert_equal(set(bemsol), bems)
-    sdir, sfrom, sto, scale, skip_fiducials, bemsol = \
-        model.get_scaling_job('sample2', True, False)
+    model.prepare_bem_model = False
+    sdir, sfrom, sto, scale, skip_fiducials, labels, annot, bemsol = \
+        model.get_scaling_job('sample2', True)
     assert_equal(bemsol, [])
     assert_true(skip_fiducials)
 
     model.load_trans(fname_trans)
 
-    from mne.gui._coreg_gui import CoregFrame
-    x = CoregFrame(raw_path, 'sample', subjects_dir)
+
+def _check_ci():
+    if os.getenv('TRAVIS', 'false').lower() == 'true' and \
+            sys.platform == 'darwin':
+        raise SkipTest('Skipping GUI tests on Travis OSX')
+
+
+ at testing.requires_testing_data
+ at requires_mayavi
+def test_coreg_gui():
+    """Test CoregFrame."""
+    _check_ci()
+    home_dir = _TempDir()
     os.environ['_MNE_GUI_TESTING_MODE'] = 'true'
+    os.environ['_MNE_FAKE_HOME_DIR'] = home_dir
     try:
-        with warnings.catch_warnings(record=True):  # traits spews warnings
-            warnings.simplefilter('always')
-            x._init_plot()
+        assert_raises(ValueError, mne.gui.coregistration, subject='Elvis',
+                      subjects_dir=subjects_dir)
+
+        from pyface.api import GUI
+        gui = GUI()
+
+        # avoid modal dialog if SUBJECTS_DIR is set to a directory that
+        # does not contain valid subjects
+        ui, frame = mne.gui.coregistration(subjects_dir='')
+
+        frame.model.mri.subjects_dir = subjects_dir
+        frame.model.mri.subject = 'sample'
+
+        assert_false(frame.model.mri.fid_ok)
+        frame.model.mri.lpa = [[-0.06, 0, 0]]
+        frame.model.mri.nasion = [[0, 0.05, 0]]
+        frame.model.mri.rpa = [[0.08, 0, 0]]
+        assert_true(frame.model.mri.fid_ok)
+        frame.raw_src.file = raw_path
+
+        # grow hair (high-res head has no norms)
+        assert_true(frame.model.mri.use_high_res_head)
+        frame.model.mri.use_high_res_head = False
+        frame.model.grow_hair = 40.
+
+        # scale
+        frame.coreg_panel.n_scale_params = 3
+        frame.coreg_panel.scale_x_inc = True
+        assert_equal(frame.model.scale_x, 1.01)
+        frame.coreg_panel.scale_y_dec = True
+        assert_equal(frame.model.scale_y, 0.99)
+
+        # reset parameters
+        frame.coreg_panel.reset_params = True
+        assert_equal(frame.model.grow_hair, 0)
+        assert_false(frame.model.mri.use_high_res_head)
+
+        # configuration persistence
+        assert_true(frame.model.prepare_bem_model)
+        frame.model.prepare_bem_model = False
+        frame.save_config(home_dir)
+        ui.dispose()
+        gui.process_events()
+
+        ui, frame = mne.gui.coregistration(subjects_dir=subjects_dir)
+        assert_false(frame.model.prepare_bem_model)
+        assert_false(frame.model.mri.use_high_res_head)
+        ui.dispose()
+        gui.process_events()
     finally:
         del os.environ['_MNE_GUI_TESTING_MODE']
+        del os.environ['_MNE_FAKE_HOME_DIR']
 
 
 @testing.requires_testing_data
- at requires_traits
- at requires_mne
- at requires_freesurfer
+ at requires_mayavi
 def test_coreg_model_with_fsaverage():
-    """Test CoregModel with the fsaverage brain data"""
+    """Test CoregModel with the fsaverage brain data."""
     tempdir = _TempDir()
     from mne.gui._coreg_gui import CoregModel
 
-    mne.create_default_subject(subjects_dir=tempdir)
+    mne.create_default_subject(subjects_dir=tempdir,
+                               fs_home=op.join(subjects_dir, '..'))
 
     model = CoregModel()
     model.mri.use_high_res_head = False
@@ -197,15 +261,16 @@ def test_coreg_model_with_fsaverage():
     assert_true(avg_point_distance_1param < avg_point_distance)
 
     # scaling job
-    sdir, sfrom, sto, scale, skip_fiducials, bemsol = \
-        model.get_scaling_job('scaled', False, True)
+    sdir, sfrom, sto, scale, skip_fiducials, labels, annot, bemsol = \
+        model.get_scaling_job('scaled', False)
     assert_equal(sdir, tempdir)
     assert_equal(sfrom, 'fsaverage')
     assert_equal(sto, 'scaled')
     assert_equal(scale, model.scale)
     assert_equal(set(bemsol), set(('inner_skull-bem',)))
-    sdir, sfrom, sto, scale, skip_fiducials, bemsol = \
-        model.get_scaling_job('scaled', False, False)
+    model.prepare_bem_model = False
+    sdir, sfrom, sto, scale, skip_fiducials, labels, annot, bemsol = \
+        model.get_scaling_job('scaled', False)
     assert_equal(bemsol, [])
 
     # scale with 3 parameters
@@ -220,23 +285,4 @@ def test_coreg_model_with_fsaverage():
     assert_equal(model.hsp.n_omitted, 0)
 
 
- at testing.requires_testing_data
- at requires_mayavi
-def test_coreg_gui():
-    """Test Coregistration GUI"""
-    from mne.gui._coreg_gui import CoregFrame
-
-    frame = CoregFrame()
-    frame.edit_traits()
-
-    frame.model.mri.subjects_dir = subjects_dir
-    frame.model.mri.subject = 'sample'
-
-    assert_false(frame.model.mri.fid_ok)
-    frame.model.mri.lpa = [[-0.06, 0, 0]]
-    frame.model.mri.nasion = [[0, 0.05, 0]]
-    frame.model.mri.rpa = [[0.08, 0, 0]]
-    assert_true(frame.model.mri.fid_ok)
-
-
 run_tests_if_main()
diff --git a/mne/gui/tests/test_fiducials_gui.py b/mne/gui/tests/test_fiducials_gui.py
index 5cabefb..e29f74f 100644
--- a/mne/gui/tests/test_fiducials_gui.py
+++ b/mne/gui/tests/test_fiducials_gui.py
@@ -8,14 +8,14 @@ from numpy.testing import assert_array_equal
 from nose.tools import assert_true, assert_false, assert_equal
 
 from mne.datasets import testing
-from mne.utils import _TempDir, requires_traits
+from mne.utils import _TempDir, requires_mayavi, run_tests_if_main
 
 sample_path = testing.data_path(download=False)
 subjects_dir = os.path.join(sample_path, 'subjects')
 
 
 @testing.requires_testing_data
- at requires_traits
+ at requires_mayavi
 def test_mri_model():
     """Test MRIHeadWithFiducialsModel Traits Model"""
     from mne.gui._fiducials_gui import MRIHeadWithFiducialsModel
@@ -35,7 +35,7 @@ def test_mri_model():
 
     bem_fname = os.path.basename(model.bem.file)
     assert_false(model.can_reset)
-    assert_equal(bem_fname, 'lh.seghead')
+    assert_equal(bem_fname, 'sample-head-dense.fif')
 
     model.save(tgt_fname)
     assert_equal(model.fid_file, tgt_fname)
@@ -65,3 +65,6 @@ def test_mri_model():
     assert_true(model.can_reset)
     model.reset = True
     assert_array_equal(model.nasion, [[0, 1, 0]])
+
+
+run_tests_if_main()
diff --git a/mne/gui/tests/test_file_traits.py b/mne/gui/tests/test_file_traits.py
index 038fe65..31789a9 100644
--- a/mne/gui/tests/test_file_traits.py
+++ b/mne/gui/tests/test_file_traits.py
@@ -10,8 +10,9 @@ from nose.tools import assert_equal, assert_false, assert_raises, assert_true
 
 from mne.datasets import testing
 from mne.io.tests import data_dir as fiff_data_dir
-from mne.utils import (_TempDir, requires_mne, requires_freesurfer,
-                       requires_traits)
+from mne.utils import (_TempDir, requires_freesurfer, requires_mayavi,
+                       run_tests_if_main)
+from mne.channels import read_dig_montage
 
 data_path = testing.data_path(download=False)
 subjects_dir = os.path.join(data_path, 'subjects')
@@ -22,9 +23,9 @@ fid_path = os.path.join(fiff_data_dir, 'fsaverage-fiducials.fif')
 
 
 @testing.requires_testing_data
- at requires_traits
+ at requires_mayavi
 def test_bem_source():
-    """Test SurfaceSource"""
+    """Test SurfaceSource."""
     from mne.gui._file_traits import SurfaceSource
 
     bem = SurfaceSource()
@@ -37,9 +38,9 @@ def test_bem_source():
 
 
 @testing.requires_testing_data
- at requires_traits
+ at requires_mayavi
 def test_fiducials_source():
-    """Test FiducialsSource"""
+    """Test FiducialsSource."""
     from mne.gui._file_traits import FiducialsSource
 
     fid = FiducialsSource()
@@ -55,12 +56,13 @@ def test_fiducials_source():
 
 
 @testing.requires_testing_data
- at requires_traits
+ at requires_mayavi
 def test_inst_source():
-    """Test InstSource"""
-    from mne.gui._file_traits import InstSource
+    """Test DigSource."""
+    from mne.gui._file_traits import DigSource
+    tempdir = _TempDir()
 
-    inst = InstSource()
+    inst = DigSource()
     assert_equal(inst.inst_fname, '-')
 
     inst.file = inst_path
@@ -73,11 +75,19 @@ def test_inst_source():
     assert_allclose(inst.nasion, nasion)
     assert_allclose(inst.rpa, rpa)
 
+    montage = read_dig_montage(fif=inst_path)  # test reading DigMontage
+    montage_path = os.path.join(tempdir, 'temp_montage.fif')
+    montage.save(montage_path)
+    inst.file = montage_path
+    assert_allclose(inst.lpa, lpa)
+    assert_allclose(inst.nasion, nasion)
+    assert_allclose(inst.rpa, rpa)
+
 
 @testing.requires_testing_data
- at requires_traits
+ at requires_mayavi
 def test_subject_source():
-    """Test SubjectSelector"""
+    """Test SubjectSelector."""
     from mne.gui._file_traits import MRISubjectSource
 
     mri = MRISubjectSource()
@@ -87,11 +97,10 @@ def test_subject_source():
 
 
 @testing.requires_testing_data
- at requires_traits
- at requires_mne
+ at requires_mayavi
 @requires_freesurfer
 def test_subject_source_with_fsaverage():
-    """Test SubjectSelector"""
+    """Test SubjectSelector."""
     from mne.gui._file_traits import MRISubjectSource
     tempdir = _TempDir()
 
@@ -102,3 +111,6 @@ def test_subject_source_with_fsaverage():
     mri.subjects_dir = tempdir
     assert_true(mri.can_create_fsaverage)
     mri.create_fsaverage()
+
+
+run_tests_if_main()
diff --git a/mne/gui/tests/test_kit2fiff_gui.py b/mne/gui/tests/test_kit2fiff_gui.py
index 6e8f688..7d60775 100644
--- a/mne/gui/tests/test_kit2fiff_gui.py
+++ b/mne/gui/tests/test_kit2fiff_gui.py
@@ -3,16 +3,18 @@
 # License: BSD (3-clause)
 
 import os
+import sys
 import warnings
 
 import numpy as np
 from numpy.testing import assert_allclose, assert_array_equal
+from nose import SkipTest
 from nose.tools import assert_true, assert_false, assert_equal
 
 import mne
 from mne.io.kit.tests import data_dir as kit_data_dir
 from mne.io import read_raw_fif
-from mne.utils import _TempDir, requires_traits, run_tests_if_main
+from mne.utils import _TempDir, requires_mayavi, run_tests_if_main
 
 mrk_pre_path = os.path.join(kit_data_dir, 'test_mrk_pre.sqd')
 mrk_post_path = os.path.join(kit_data_dir, 'test_mrk_post.sqd')
@@ -24,10 +26,19 @@ fif_path = os.path.join(kit_data_dir, 'test_bin_raw.fif')
 warnings.simplefilter('always')
 
 
- at requires_traits
+def _check_ci():
+    osx = (os.getenv('TRAVIS', 'false').lower() == 'true' and
+           sys.platform == 'darwin')
+    win = (os.getenv('APPVEYOR', 'false').lower() == 'true' and
+           sys.platform.startswith('win'))
+    if win or osx:
+        raise SkipTest('Skipping GUI tests on Travis OSX and AppVeyor')
+
+
+ at requires_mayavi
 def test_kit2fiff_model():
-    """Test CombineMarkersModel Traits Model."""
-    from mne.gui._kit2fiff_gui import Kit2FiffModel, Kit2FiffPanel
+    """Test Kit2Fiff model."""
+    from mne.gui._kit2fiff_gui import Kit2FiffModel
     tempdir = _TempDir()
     tgt_fname = os.path.join(tempdir, 'test-raw.fif')
 
@@ -64,10 +75,10 @@ def test_kit2fiff_model():
     # export raw
     raw_out = model.get_raw()
     raw_out.save(tgt_fname)
-    raw = read_raw_fif(tgt_fname, add_eeg_ref=False)
+    raw = read_raw_fif(tgt_fname)
 
     # Compare exported raw with the original binary conversion
-    raw_bin = read_raw_fif(fif_path, add_eeg_ref=False)
+    raw_bin = read_raw_fif(fif_path)
     trans_bin = raw.info['dev_head_t']['trans']
     want_keys = list(raw_bin.info.keys())
     assert_equal(sorted(want_keys), sorted(list(raw.info.keys())))
@@ -114,13 +125,43 @@ def test_kit2fiff_model():
     assert_equal(model.use_mrk, [0, 1, 2, 3, 4])
     assert_equal(model.sqd_file, "")
 
+
+ at requires_mayavi
+def test_kit2fiff_gui():
+    """Test Kit2Fiff GUI."""
+    _check_ci()
+    home_dir = _TempDir()
     os.environ['_MNE_GUI_TESTING_MODE'] = 'true'
+    os.environ['_MNE_FAKE_HOME_DIR'] = home_dir
     try:
-        with warnings.catch_warnings(record=True):  # traits warnings
-            warnings.simplefilter('always')
-            Kit2FiffPanel()
+        from pyface.api import GUI
+        gui = GUI()
+        gui.process_events()
+
+        ui, frame = mne.gui.kit2fiff()
+        assert_false(frame.model.can_save)
+        assert_equal(frame.model.stim_threshold, 1.)
+        frame.model.stim_threshold = 10.
+        frame.model.stim_chs = 'save this!'
+        frame.save_config(home_dir)
+        ui.dispose()
+
+        gui.process_events()
+
+        # test setting persistence
+        ui, frame = mne.gui.kit2fiff()
+        assert_equal(frame.model.stim_threshold, 10.)
+        assert_equal(frame.model.stim_chs, 'save this!')
+
+        frame.model.markers.mrk1.file = mrk_pre_path
+        frame.marker_panel.mrk1_obj.label = True
+        frame.marker_panel.mrk1_obj.label = False
+        ui.dispose()
+
+        gui.process_events()
     finally:
         del os.environ['_MNE_GUI_TESTING_MODE']
+        del os.environ['_MNE_FAKE_HOME_DIR']
 
 
 run_tests_if_main()
diff --git a/mne/gui/tests/test_marker_gui.py b/mne/gui/tests/test_marker_gui.py
index 974d965..3ee0847 100644
--- a/mne/gui/tests/test_marker_gui.py
+++ b/mne/gui/tests/test_marker_gui.py
@@ -3,6 +3,8 @@
 # License: BSD (3-clause)
 
 import os
+import sys
+from unittest import SkipTest
 import warnings
 
 import numpy as np
@@ -11,7 +13,7 @@ from nose.tools import assert_true, assert_false
 
 from mne.io.kit.tests import data_dir as kit_data_dir
 from mne.io.kit import read_mrk
-from mne.utils import _TempDir, requires_traits, run_tests_if_main
+from mne.utils import _TempDir, requires_mayavi, run_tests_if_main
 
 mrk_pre_path = os.path.join(kit_data_dir, 'test_mrk_pre.sqd')
 mrk_post_path = os.path.join(kit_data_dir, 'test_mrk_post.sqd')
@@ -20,7 +22,13 @@ mrk_avg_path = os.path.join(kit_data_dir, 'test_mrk.sqd')
 warnings.simplefilter('always')
 
 
- at requires_traits
+def _check_ci():
+    if os.getenv('TRAVIS', 'false').lower() == 'true' and \
+            sys.platform == 'darwin':
+        raise SkipTest('Skipping GUI tests on Travis OSX')
+
+
+ at requires_mayavi
 def test_combine_markers_model():
     """Test CombineMarkersModel Traits Model"""
     from mne.gui._marker_gui import CombineMarkersModel, CombineMarkersPanel
@@ -71,6 +79,7 @@ def test_combine_markers_model():
     model.mrk2.file = mrk_post_path
     assert_array_equal(model.mrk3.points, points_interpolate_mrk1_mrk2)
 
+    _check_ci()
     os.environ['_MNE_GUI_TESTING_MODE'] = 'true'
     try:
         with warnings.catch_warnings(record=True):  # traits warnings
diff --git a/mne/inverse_sparse/__init__.py b/mne/inverse_sparse/__init__.py
index e198907..1c1a7af 100644
--- a/mne/inverse_sparse/__init__.py
+++ b/mne/inverse_sparse/__init__.py
@@ -1,8 +1,9 @@
-"""Non-Linear sparse inverse solvers"""
+"""Non-Linear sparse inverse solvers."""
 
 # Author: Alexandre Gramfort <alexandre.gramfort at telecom-paristech.fr>
 #
 # License: Simplified BSD
 
-from .mxne_inverse import mixed_norm, tf_mixed_norm
+from .mxne_inverse import (mixed_norm, tf_mixed_norm,
+                           make_stc_from_dipoles)
 from ._gamma_map import gamma_map
diff --git a/mne/inverse_sparse/_gamma_map.py b/mne/inverse_sparse/_gamma_map.py
index 8566ff3..a77e386 100644
--- a/mne/inverse_sparse/_gamma_map.py
+++ b/mne/inverse_sparse/_gamma_map.py
@@ -1,24 +1,24 @@
 # Authors: Alexandre Gramfort <alexandre.gramfort at telecom-paristech.fr>
 #          Martin Luessi <mluessi at nmr.mgh.harvard.edu>
 # License: Simplified BSD
-from copy import deepcopy
 
 import numpy as np
 from scipy import linalg
 
-from ..forward import is_fixed_orient, _to_fixed_ori
+from ..forward import is_fixed_orient, convert_forward_solution
 
 from ..minimum_norm.inverse import _check_reference
 from ..utils import logger, verbose, warn
 from ..externals.six.moves import xrange as range
 from .mxne_inverse import (_make_sparse_stc, _prepare_gain,
-                           _reapply_source_weighting, _compute_residual)
+                           _reapply_source_weighting, _compute_residual,
+                           _make_dipoles_sparse, _check_loose_forward)
 
 
 @verbose
 def _gamma_map_opt(M, G, alpha, maxit=10000, tol=1e-6, update_mode=1,
                    group_size=1, gammas=None, verbose=None):
-    """Hierarchical Bayes (Gamma-MAP)
+    """Hierarchical Bayes (Gamma-MAP).
 
     Parameters
     ----------
@@ -40,7 +40,8 @@ def _gamma_map_opt(M, G, alpha, maxit=10000, tol=1e-6, update_mode=1,
         Initial values for posterior variances (gammas). If None, a
         variance of 1.0 is used.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -48,12 +49,6 @@ def _gamma_map_opt(M, G, alpha, maxit=10000, tol=1e-6, update_mode=1,
         Estimated source time courses.
     active_set : array, shape=(n_active,)
         Indices of active sources.
-
-    References
-    ----------
-    [1] Wipf et al. Analysis of Empirical Bayesian Methods for
-    Neuroelectromagnetic Source Localization, Advances in Neural Information
-    Processing Systems (2007).
     """
     G = G.copy()
     M = M.copy()
@@ -89,6 +84,7 @@ def _gamma_map_opt(M, G, alpha, maxit=10000, tol=1e-6, update_mode=1,
         def denom_fun(x):
             return x
 
+    last_size = -1
     for itno in range(maxit):
         gammas[np.isnan(gammas)] = 0.0
 
@@ -125,7 +121,8 @@ def _gamma_map_opt(M, G, alpha, maxit=10000, tol=1e-6, update_mode=1,
             if denom is None:
                 gammas = numer
             else:
-                gammas = numer / denom_fun(denom)
+                gammas = numer / np.maximum(denom_fun(denom),
+                                            np.finfo('float').eps)
         else:
             numer_comb = np.sum(numer.reshape(-1, group_size), axis=1)
             if denom is None:
@@ -145,13 +142,13 @@ def _gamma_map_opt(M, G, alpha, maxit=10000, tol=1e-6, update_mode=1,
 
         gammas_full_old = gammas_full
 
-        logger.info('Iteration: %d\t active set size: %d\t convergence: %0.3e'
-                    % (itno, len(gammas), err))
+        breaking = (err < tol or n_active == 0)
+        if len(gammas) != last_size or breaking:
+            logger.info('Iteration: %d\t active set size: %d\t convergence: '
+                        '%0.3e' % (itno, len(gammas), err))
+            last_size = len(gammas)
 
-        if err < tol:
-            break
-
-        if n_active == 0:
+        if breaking:
             break
 
     if itno < maxit - 1:
@@ -167,15 +164,16 @@ def _gamma_map_opt(M, G, alpha, maxit=10000, tol=1e-6, update_mode=1,
 
 
 @verbose
-def gamma_map(evoked, forward, noise_cov, alpha, loose=0.2, depth=0.8,
+def gamma_map(evoked, forward, noise_cov, alpha, loose="auto", depth=0.8,
               xyz_same_gamma=True, maxit=10000, tol=1e-6, update_mode=1,
               gammas=None, pca=True, return_residual=False,
-              verbose=None):
-    """Hierarchical Bayes (Gamma-MAP) sparse source localization method
+              return_as_dipoles=False, verbose=None):
+    """Hierarchical Bayes (Gamma-MAP) sparse source localization method.
 
     Models each source time course using a zero-mean Gaussian prior with an
     unknown variance (gamma) parameter. During estimation, most gammas are
-    driven to zero, resulting in a sparse source estimate.
+    driven to zero, resulting in a sparse source estimate, as in
+    [1]_ and [2]_.
 
     For fixed-orientation forward operators, a separate gamma is used for each
     source time course, while for free-orientation forward operators, the same
@@ -192,11 +190,13 @@ def gamma_map(evoked, forward, noise_cov, alpha, loose=0.2, depth=0.8,
         Noise covariance to compute whitener.
     alpha : float
         Regularization parameter (noise variance).
-    loose : float in [0, 1]
+    loose : float in [0, 1] | 'auto'
         Value that weights the source variances of the dipole components
         that are parallel (tangential) to the cortical surface. If loose
-        is 0 or None then the solution is computed with fixed orientation.
+        is 0 then the solution is computed with fixed orientation.
         If loose is 1, it corresponds to free orientations.
+        The default value ('auto') is set to 0.2 for surface-oriented source
+        space and set to 1.0 for volumic or discrete source space.
     depth: None | float in [0, 1]
         Depth weighting coefficients. If None, no depth weighting is performed.
     xyz_same_gamma : bool
@@ -215,8 +215,11 @@ def gamma_map(evoked, forward, noise_cov, alpha, loose=0.2, depth=0.8,
         If True the rank of the data is reduced to the true dimension.
     return_residual : bool
         If True, the residual is returned as an Evoked instance.
+    return_as_dipoles : bool
+        If True, the sources are returned as a list of Dipole instances.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -228,18 +231,23 @@ def gamma_map(evoked, forward, noise_cov, alpha, loose=0.2, depth=0.8,
 
     References
     ----------
-    Wipf et al. Analysis of Empirical Bayesian Methods for Neuroelectromagnetic
-    Source Localization, Advances in Neural Information Process. Systems (2007)
-
-    Wipf et al. A unified Bayesian framework for MEG/EEG source imaging,
-    NeuroImage, vol. 44, no. 3, pp. 947-66, Mar. 2009.
+    .. [1] Wipf et al. Analysis of Empirical Bayesian Methods for
+           Neuroelectromagnetic Source Localization, Advances in Neural
+           Information Process. Systems (2007)
+
+    .. [2] D. Wipf, S. Nagarajan
+           "A unified Bayesian framework for MEG/EEG source imaging",
+           Neuroimage, Volume 44, Number 3, pp. 947-966, Feb. 2009.
+           DOI: 10.1016/j.neuroimage.2008.02.059
     """
     _check_reference(evoked)
 
+    loose, forward = _check_loose_forward(loose, forward)
+
     # make forward solution in fixed orientation if necessary
-    if loose is None and not is_fixed_orient(forward):
-        forward = deepcopy(forward)
-        _to_fixed_ori(forward)
+    if loose == 0. and not is_fixed_orient(forward):
+        forward = convert_forward_solution(
+            forward, surf_ori=True, force_fixed=True, copy=True, use_cps=True)
 
     if is_fixed_orient(forward) or not xyz_same_gamma:
         group_size = 1
@@ -265,6 +273,9 @@ def gamma_map(evoked, forward, noise_cov, alpha, loose=0.2, depth=0.8,
     if len(active_set) == 0:
         raise Exception("No active dipoles found. alpha is too big.")
 
+    # Compute estimated whitened sensor data
+    M_estimated = np.dot(gain[:, active_set], X)
+
     # Reapply weights to have correct unit
     n_dip_per_pos = 1 if is_fixed_orient(forward) else 3
     X = _reapply_source_weighting(X, source_weighting,
@@ -291,10 +302,17 @@ def gamma_map(evoked, forward, noise_cov, alpha, loose=0.2, depth=0.8,
 
     tmin = evoked.times[0]
     tstep = 1.0 / evoked.info['sfreq']
-    stc = _make_sparse_stc(X, active_set, forward, tmin, tstep,
-                           active_is_idx=True, verbose=verbose)
 
-    if return_residual:
-        return stc, residual
+    if return_as_dipoles:
+        out = _make_dipoles_sparse(X, active_set, forward, tmin, tstep, M,
+                                   M_estimated, active_is_idx=True)
     else:
-        return stc
+        out = _make_sparse_stc(X, active_set, forward, tmin, tstep,
+                               active_is_idx=True, verbose=verbose)
+
+    logger.info('[done]')
+
+    if return_residual:
+        out = out, residual
+
+    return out
diff --git a/mne/inverse_sparse/mxne_debiasing.py b/mne/inverse_sparse/mxne_debiasing.py
index e3c0b89..2997a44 100755
--- a/mne/inverse_sparse/mxne_debiasing.py
+++ b/mne/inverse_sparse/mxne_debiasing.py
@@ -11,7 +11,7 @@ from ..utils import check_random_state, logger, verbose
 
 
 def power_iteration_kron(A, C, max_iter=1000, tol=1e-3, random_state=0):
-    """Find the largest singular value for the matrix kron(C.T, A)
+    """Find the largest singular value for the matrix kron(C.T, A).
 
     It uses power iterations.
 
@@ -56,7 +56,7 @@ def power_iteration_kron(A, C, max_iter=1000, tol=1e-3, random_state=0):
 
 @verbose
 def compute_bias(M, G, X, max_iter=1000, tol=1e-6, n_orient=1, verbose=None):
-    """Compute scaling to correct amplitude bias
+    """Compute scaling to correct amplitude bias.
 
     It solves the following optimization problem using FISTA:
 
@@ -84,7 +84,8 @@ def compute_bias(M, G, X, max_iter=1000, tol=1e-6, n_orient=1, verbose=None):
     n_orient : int
         The number of orientations (1 for fixed and 3 otherwise).
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
diff --git a/mne/inverse_sparse/mxne_inverse.py b/mne/inverse_sparse/mxne_inverse.py
index 4182ec2..756e845 100644
--- a/mne/inverse_sparse/mxne_inverse.py
+++ b/mne/inverse_sparse/mxne_inverse.py
@@ -3,17 +3,19 @@
 #
 # License: Simplified BSD
 
-from copy import deepcopy
 import numpy as np
 from scipy import linalg, signal
 
-from ..source_estimate import SourceEstimate
-from ..minimum_norm.inverse import combine_xyz, _prepare_forward
-from ..minimum_norm.inverse import _check_reference
-from ..forward import compute_orient_prior, is_fixed_orient, _to_fixed_ori
+from ..source_estimate import (SourceEstimate, VolSourceEstimate,
+                               _BaseSourceEstimate)
+from ..minimum_norm.inverse import (combine_xyz, _prepare_forward,
+                                    _check_reference, _check_loose_forward)
+from ..forward import (compute_orient_prior, is_fixed_orient,
+                       convert_forward_solution)
 from ..io.pick import pick_channels_evoked
 from ..io.proj import deactivate_proj
 from ..utils import logger, verbose
+from ..dipole import Dipole
 from ..externals.six.moves import xrange as range
 
 from .mxne_optim import (mixed_norm_solver, iterative_mixed_norm_solver,
@@ -23,8 +25,7 @@ from .mxne_optim import (mixed_norm_solver, iterative_mixed_norm_solver,
 @verbose
 def _prepare_weights(forward, gain, source_weighting, weights, weights_min):
     mask = None
-    if isinstance(weights, SourceEstimate):
-        # weights = np.sqrt(np.sum(weights.data ** 2, axis=1))
+    if isinstance(weights, _BaseSourceEstimate):
         weights = np.max(np.abs(weights.data), axis=1)
     weights_max = np.max(weights)
     if weights_min > weights_max:
@@ -60,14 +61,24 @@ def _prepare_gain_column(forward, info, noise_cov, pca, depth, loose, weights,
 
     logger.info('Whitening lead field matrix.')
     gain = np.dot(whitener, gain)
+    is_fixed_ori = is_fixed_orient(forward)
 
     if depth is not None:
-        depth_prior = np.sum(gain ** 2, axis=0) ** depth
-        source_weighting = np.sqrt(depth_prior ** -1.)
+        depth_prior = np.sum(gain ** 2, axis=0)
+        if not is_fixed_ori:
+            depth_prior = depth_prior.reshape(-1, 3).sum(axis=1)
+        # Spherical leadfield can be zero at the center
+        depth_prior[depth_prior == 0.] = np.min(
+            depth_prior[depth_prior != 0.])
+        depth_prior **= depth
+        if not is_fixed_ori:
+            depth_prior = np.repeat(depth_prior, 3)
+        source_weighting = np.sqrt(1. / depth_prior)
     else:
         source_weighting = np.ones(gain.shape[1], dtype=gain.dtype)
 
-    if loose is not None and loose != 1.0:
+    assert (is_fixed_ori or (0 <= loose <= 1))
+    if loose is not None and loose < 1.:
         source_weighting *= np.sqrt(compute_orient_prior(forward, loose))
 
     gain *= source_weighting[None, :]
@@ -151,36 +162,132 @@ def _make_sparse_stc(X, active_set, forward, tmin, tstep,
 
     src = forward['src']
 
+    if src.kind != 'surface':
+        vertices = src[0]['vertno'][active_idx]
+        stc = VolSourceEstimate(X, vertices=vertices, tmin=tmin, tstep=tstep)
+    else:
+        vertices = []
+        n_points_so_far = 0
+        for this_src in src:
+            this_n_points_so_far = n_points_so_far + len(this_src['vertno'])
+            this_active_idx = active_idx[(n_points_so_far <= active_idx) &
+                                         (active_idx < this_n_points_so_far)]
+            this_active_idx -= n_points_so_far
+            this_vertno = this_src['vertno'][this_active_idx]
+            n_points_so_far = this_n_points_so_far
+            vertices.append(this_vertno)
+
+        stc = SourceEstimate(X, vertices=vertices, tmin=tmin, tstep=tstep)
+
+    return stc
+
+
+ at verbose
+def _make_dipoles_sparse(X, active_set, forward, tmin, tstep, M, M_est,
+                         active_is_idx=False, verbose=None):
+    times = tmin + tstep * np.arange(X.shape[1])
+
+    if not active_is_idx:
+        active_idx = np.where(active_set)[0]
+    else:
+        active_idx = active_set
+
+    n_dip_per_pos = 1 if is_fixed_orient(forward) else 3
+    if n_dip_per_pos > 1:
+        active_idx = np.unique(active_idx // n_dip_per_pos)
+
+    gof = np.zeros(M_est.shape[1])
+    M_norm2 = np.sum(M ** 2, axis=0)
+    R_norm2 = np.sum((M - M_est) ** 2, axis=0)
+    gof[M_norm2 > 0.0] = 1. - R_norm2[M_norm2 > 0.0] / M_norm2[M_norm2 > 0.0]
+    gof *= 100.
+
+    dipoles = []
+    for k, i_dip in enumerate(active_idx):
+        i_pos = forward['source_rr'][i_dip][np.newaxis, :]
+        i_pos = i_pos.repeat(len(times), axis=0)
+        X_ = X[k * n_dip_per_pos: (k + 1) * n_dip_per_pos]
+        if n_dip_per_pos == 1:
+            amplitude = X_[0]
+            i_ori = forward['source_nn'][i_dip][np.newaxis, :]
+            i_ori = i_ori.repeat(len(times), axis=0)
+        else:
+            if forward['surf_ori']:
+                X_ = np.dot(forward['source_nn'][i_dip *
+                            n_dip_per_pos:(i_dip + 1) * n_dip_per_pos].T, X_)
+
+            amplitude = np.sqrt(np.sum(X_ ** 2, axis=0))
+            i_ori = np.zeros((len(times), 3))
+            i_ori[amplitude > 0.] = (X_[:, amplitude > 0.] /
+                                     amplitude[amplitude > 0.]).T
+
+        dipoles.append(Dipole(times, i_pos, amplitude, i_ori, gof))
+
+    return dipoles
+
+
+ at verbose
+def make_stc_from_dipoles(dipoles, src, verbose=None):
+    """Convert a list of spatio-temporal dipoles into a SourceEstimate.
+
+    Parameters
+    ----------
+    dipoles : Dipole | list of instances of Dipole
+        The dipoles to convert.
+    src : instance of SourceSpaces
+        The source space used to generate the forward operator.
+    verbose : bool, str, int, or None
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
+
+    Returns
+    -------
+    stc : SourceEstimate
+        The source estimate.
+    """
+    logger.info('Converting dipoles into a SourceEstimate.')
+    if isinstance(dipoles, Dipole):
+        dipoles = [dipoles]
+    if not isinstance(dipoles, list):
+        raise ValueError('Dipoles must be an instance of Dipole or '
+                         'a list of instances of Dipole. '
+                         'Got %s!' % type(dipoles))
+    tmin = dipoles[0].times[0]
+    tstep = dipoles[0].times[1] - tmin
+    X = np.zeros((len(dipoles), len(dipoles[0].times)))
+    source_rr = np.concatenate([_src['rr'][_src['vertno'], :] for _src in src],
+                               axis=0)
     n_lh_points = len(src[0]['vertno'])
-    lh_vertno = src[0]['vertno'][active_idx[active_idx < n_lh_points]]
-    rh_vertno = src[1]['vertno'][active_idx[active_idx >= n_lh_points] -
-                                 n_lh_points]
-    vertices = [lh_vertno, rh_vertno]
+    lh_vertno = list()
+    rh_vertno = list()
+    for i in range(len(dipoles)):
+        if not np.all(dipoles[i].pos == dipoles[i].pos[0]):
+            raise ValueError('Only dipoles with fixed position over time '
+                             'are supported!')
+        X[i] = dipoles[i].amplitude
+        idx = np.all(source_rr == dipoles[i].pos[0], axis=1)
+        idx = np.where(idx)[0][0]
+        if idx < n_lh_points:
+            lh_vertno.append(src[0]['vertno'][idx])
+        else:
+            rh_vertno.append(src[1]['vertno'][idx - n_lh_points])
+    vertices = [np.array(lh_vertno).astype(int),
+                np.array(rh_vertno).astype(int)]
     stc = SourceEstimate(X, vertices=vertices, tmin=tmin, tstep=tstep)
+    logger.info('[done]')
     return stc
 
 
 @verbose
-def mixed_norm(evoked, forward, noise_cov, alpha, loose=0.2, depth=0.8,
+def mixed_norm(evoked, forward, noise_cov, alpha, loose='auto', depth=0.8,
                maxit=3000, tol=1e-4, active_set_size=10, pca=True,
                debias=True, time_pca=True, weights=None, weights_min=None,
                solver='auto', n_mxne_iter=1, return_residual=False,
-               verbose=None):
-    """Mixed-norm estimate (MxNE) and iterative reweighted MxNE (irMxNE)
-
-    Compute L1/L2 mixed-norm solution or L0.5/L2 mixed-norm solution
-    on evoked data.
+               return_as_dipoles=False, verbose=None):
+    """Mixed-norm estimate (MxNE) and iterative reweighted MxNE (irMxNE).
 
-    References:
-    Gramfort A., Kowalski M. and Hamalainen, M.,
-    Mixed-norm estimates for the M/EEG inverse problem using accelerated
-    gradient methods, Physics in Medicine and Biology, 2012
-    http://dx.doi.org/10.1088/0031-9155/57/7/1937
-
-    Strohmeier D., Haueisen J., and Gramfort A.,
-    Improved MEG/EEG source localization with reweighted mixed-norms,
-    4th International Workshop on Pattern Recognition in Neuroimaging,
-    Tuebingen, 2014
+    Compute L1/L2 mixed-norm solution [1]_ or L0.5/L2 [2]_ mixed-norm
+    solution on evoked data.
 
     Parameters
     ----------
@@ -192,11 +299,13 @@ def mixed_norm(evoked, forward, noise_cov, alpha, loose=0.2, depth=0.8,
         Noise covariance to compute whitener.
     alpha : float
         Regularization parameter.
-    loose : float in [0, 1]
+    loose : float in [0, 1] | 'auto'
         Value that weights the source variances of the dipole components
         that are parallel (tangential) to the cortical surface. If loose
-        is 0 or None then the solution is computed with fixed orientation.
+        is 0 then the solution is computed with fixed orientation.
         If loose is 1, it corresponds to free orientations.
+        The default value ('auto') is set to 0.2 for surface-oriented source
+        space and set to 1.0 for volumic or discrete source space.
     depth: None | float in [0, 1]
         Depth weighting coefficients. If None, no depth weighting is performed.
     maxit : int
@@ -213,9 +322,9 @@ def mixed_norm(evoked, forward, noise_cov, alpha, loose=0.2, depth=0.8,
         If True the rank of the concatenated epochs is reduced to
         its true dimension. If is 'int' the rank is limited to this value.
     weights : None | array | SourceEstimate
-        Weight for penalty in mixed_norm. Can be None or
-        1d array of length n_sources or a SourceEstimate e.g. obtained
-        with wMNE or dSPM or fMRI.
+        Weight for penalty in mixed_norm. Can be None, a
+        1d array with shape (n_sources,), or a SourceEstimate (e.g. obtained
+        with wMNE, dSPM, or fMRI).
     weights_min : float
         Do not consider in the estimation sources for which weights
         is less than weights_min.
@@ -229,8 +338,11 @@ def mixed_norm(evoked, forward, noise_cov, alpha, loose=0.2, depth=0.8,
         is applied.
     return_residual : bool
         If True, the residual is returned as an Evoked instance.
+    return_as_dipoles : bool
+        If True, the sources are returned as a list of Dipole instances.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -243,11 +355,22 @@ def mixed_norm(evoked, forward, noise_cov, alpha, loose=0.2, depth=0.8,
     See Also
     --------
     tf_mixed_norm
+
+    References
+    ----------
+    .. [1] A. Gramfort, M. Kowalski, M. Hamalainen,
+       "Mixed-norm estimates for the M/EEG inverse problem using accelerated
+       gradient methods", Physics in Medicine and Biology, 2012.
+       http://dx.doi.org/10.1088/0031-9155/57/7/1937
+
+    .. [2] D. Strohmeier, Y. Bekhti, J. Haueisen, A. Gramfort,
+       "The Iterative Reweighted Mixed-Norm Estimate for Spatio-Temporal
+       MEG/EEG Source Reconstruction", IEEE Transactions of Medical Imaging,
+       Volume 35 (10), pp. 2218-2228, 2016.
     """
     if n_mxne_iter < 1:
         raise ValueError('MxNE has to be computed at least 1 time. '
-                         'Requires n_mxne_iter > 0. '
-                         'Got n_mxne_iter = %d.' % n_mxne_iter)
+                         'Requires n_mxne_iter >= 1, got %d' % n_mxne_iter)
 
     if not isinstance(evoked, list):
         evoked = [evoked]
@@ -259,10 +382,12 @@ def mixed_norm(evoked, forward, noise_cov, alpha, loose=0.2, depth=0.8,
                for i in range(1, len(evoked))):
         raise Exception('All the datasets must have the same good channels.')
 
+    loose, forward = _check_loose_forward(loose, forward)
+
     # put the forward solution in fixed orientation if it's not already
-    if loose is None and not is_fixed_orient(forward):
-        forward = deepcopy(forward)
-        _to_fixed_ori(forward)
+    if loose == 0. and not is_fixed_orient(forward):
+        forward = convert_forward_solution(
+            forward, surf_ori=True, force_fixed=True, copy=True, use_cps=True)
 
     gain, gain_info, whitener, source_weighting, mask = _prepare_gain(
         forward, evoked[0].info, noise_cov, pca, depth, loose, weights,
@@ -301,15 +426,19 @@ def mixed_norm(evoked, forward, noise_cov, alpha, loose=0.2, depth=0.8,
             n_orient=n_dip_per_pos, active_set_size=active_set_size,
             debias=debias, solver=solver, verbose=verbose)
 
+    if time_pca:
+        X = np.dot(X, Vh)
+        M = np.dot(M, Vh)
+
+    # Compute estimated whitened sensor data
+    M_estimated = np.dot(gain[:, active_set], X)
+
     if mask is not None:
         active_set_tmp = np.zeros(len(mask), dtype=np.bool)
         active_set_tmp[mask] = active_set
         active_set = active_set_tmp
         del active_set_tmp
 
-    if time_pca:
-        X = np.dot(X, Vh)
-
     if active_set.sum() == 0:
         raise Exception("No active dipoles found. alpha is too big.")
 
@@ -317,15 +446,21 @@ def mixed_norm(evoked, forward, noise_cov, alpha, loose=0.2, depth=0.8,
     X = _reapply_source_weighting(X, source_weighting,
                                   active_set, n_dip_per_pos)
 
-    stcs = list()
+    outs = list()
     residual = list()
     cnt = 0
     for e in evoked:
         tmin = e.times[0]
         tstep = 1.0 / e.info['sfreq']
         Xe = X[:, cnt:(cnt + len(e.times))]
-        stc = _make_sparse_stc(Xe, active_set, forward, tmin, tstep)
-        stcs.append(stc)
+        if return_as_dipoles:
+            out = _make_dipoles_sparse(
+                Xe, active_set, forward, tmin, tstep,
+                M[:, cnt:(cnt + len(e.times))],
+                M_estimated[:, cnt:(cnt + len(e.times))], verbose=None)
+        else:
+            out = _make_sparse_stc(Xe, active_set, forward, tmin, tstep)
+        outs.append(out)
         cnt += len(e.times)
 
         if return_residual:
@@ -334,12 +469,12 @@ def mixed_norm(evoked, forward, noise_cov, alpha, loose=0.2, depth=0.8,
 
     logger.info('[done]')
 
-    if len(stcs) == 1:
-        out = stcs[0]
+    if len(outs) == 1:
+        out = outs[0]
         if return_residual:
             residual = residual[0]
     else:
-        out = stcs
+        out = outs
 
     if return_residual:
         out = out, residual
@@ -348,7 +483,7 @@ def mixed_norm(evoked, forward, noise_cov, alpha, loose=0.2, depth=0.8,
 
 
 def _window_evoked(evoked, size):
-    """Window evoked (size in seconds)"""
+    """Window evoked (size in seconds)."""
     if isinstance(size, (float, int)):
         lsize = rsize = float(size)
     else:
@@ -368,30 +503,14 @@ def _window_evoked(evoked, size):
 
 @verbose
 def tf_mixed_norm(evoked, forward, noise_cov, alpha_space, alpha_time,
-                  loose=0.2, depth=0.8, maxit=3000, tol=1e-4,
+                  loose='auto', depth=0.8, maxit=3000, tol=1e-4,
                   weights=None, weights_min=None, pca=True, debias=True,
                   wsize=64, tstep=4, window=0.02, return_residual=False,
-                  verbose=None):
-    """Time-Frequency Mixed-norm estimate (TF-MxNE)
+                  return_as_dipoles=False, verbose=None):
+    """Time-Frequency Mixed-norm estimate (TF-MxNE).
 
     Compute L1/L2 + L1 mixed-norm solution on time-frequency
-    dictionary. Works with evoked data.
-
-    References:
-
-    A. Gramfort, D. Strohmeier, J. Haueisen, M. Hamalainen, M. Kowalski
-    Time-Frequency Mixed-Norm Estimates: Sparse M/EEG imaging with
-    non-stationary source activations
-    Neuroimage, Volume 70, 15 April 2013, Pages 410-422, ISSN 1053-8119,
-    DOI: 10.1016/j.neuroimage.2012.12.051.
-
-    A. Gramfort, D. Strohmeier, J. Haueisen, M. Hamalainen, M. Kowalski
-    Functional Brain Imaging with M/EEG Using Structured Sparsity in
-    Time-Frequency Dictionaries
-    Proceedings Information Processing in Medical Imaging
-    Lecture Notes in Computer Science, 2011, Volume 6801/2011,
-    600-611, DOI: 10.1007/978-3-642-22092-0_49
-    http://dx.doi.org/10.1007/978-3-642-22092-0_49
+    dictionary. Works with evoked data [1]_ [2]_.
 
     Parameters
     ----------
@@ -408,11 +527,13 @@ def tf_mixed_norm(evoked, forward, noise_cov, alpha_space, alpha_time,
         Regularization parameter for temporal sparsity. It set to 0,
         no temporal regularization is applied. It this case, TF-MxNE is
         equivalent to MxNE with L21 norm.
-    loose : float in [0, 1]
+    loose : float in [0, 1] | 'auto'
         Value that weights the source variances of the dipole components
         that are parallel (tangential) to the cortical surface. If loose
-        is 0 or None then the solution is computed with fixed orientation.
+        is 0 then the solution is computed with fixed orientation.
         If loose is 1, it corresponds to free orientations.
+        The default value ('auto') is set to 0.2 for surface-oriented source
+        space and set to 1.0 for volumic or discrete source space.
     depth: None | float in [0, 1]
         Depth weighting coefficients. If None, no depth weighting is performed.
     maxit : int
@@ -441,8 +562,11 @@ def tf_mixed_norm(evoked, forward, noise_cov, alpha_space, alpha_time,
         and right window length.
     return_residual : bool
         If True, the residual is returned as an Evoked instance.
+    return_as_dipoles : bool
+        If True, the sources are returned as a list of Dipole instances.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -455,6 +579,21 @@ def tf_mixed_norm(evoked, forward, noise_cov, alpha_space, alpha_time,
     See Also
     --------
     mixed_norm
+
+    References
+    ----------
+    .. [1] A. Gramfort, D. Strohmeier, J. Haueisen, M. Hamalainen, M. Kowalski
+       "Time-Frequency Mixed-Norm Estimates: Sparse M/EEG imaging with
+       non-stationary source activations",
+       Neuroimage, Volume 70, pp. 410-422, 15 April 2013.
+       DOI: 10.1016/j.neuroimage.2012.12.051
+
+    .. [2] A. Gramfort, D. Strohmeier, J. Haueisen, M. Hamalainen, M. Kowalski
+       "Functional Brain Imaging with M/EEG Using Structured Sparsity in
+       Time-Frequency Dictionaries",
+       Proceedings Information Processing in Medical Imaging
+       Lecture Notes in Computer Science, Volume 6801/2011, pp. 600-611, 2011.
+       DOI: 10.1007/978-3-642-22092-0_49
     """
     _check_reference(evoked)
 
@@ -469,10 +608,12 @@ def tf_mixed_norm(evoked, forward, noise_cov, alpha_space, alpha_time,
         raise Exception('alpha_time must be in range [0, 100].'
                         ' Got alpha_time = %f' % alpha_time)
 
+    loose, forward = _check_loose_forward(loose, forward)
+
     # put the forward solution in fixed orientation if it's not already
-    if loose is None and not is_fixed_orient(forward):
-        forward = deepcopy(forward)
-        _to_fixed_ori(forward)
+    if loose == 0. and not is_fixed_orient(forward):
+        forward = convert_forward_solution(
+            forward, surf_ori=True, force_fixed=True, copy=True, use_cps=True)
 
     n_dip_per_pos = 1 if is_fixed_orient(forward) else 3
 
@@ -499,12 +640,15 @@ def tf_mixed_norm(evoked, forward, noise_cov, alpha_space, alpha_time,
     X, active_set, E = tf_mixed_norm_solver(
         M, gain, alpha_space, alpha_time, wsize=wsize, tstep=tstep,
         maxit=maxit, tol=tol, verbose=verbose, n_orient=n_dip_per_pos,
-        log_objective=False, debias=debias)
+        log_objective=True, debias=debias)
 
     if active_set.sum() == 0:
         raise Exception("No active dipoles found. "
                         "alpha_space/alpha_time are too big.")
 
+    # Compute estimated whitened sensor data
+    M_estimated = np.dot(gain[:, active_set], X)
+
     if mask is not None:
         active_set_tmp = np.zeros(len(mask), dtype=np.bool)
         active_set_tmp[mask] = active_set
@@ -518,14 +662,17 @@ def tf_mixed_norm(evoked, forward, noise_cov, alpha_space, alpha_time,
         residual = _compute_residual(
             forward, evoked, X, active_set, gain_info)
 
-    stc = _make_sparse_stc(
-        X, active_set, forward, evoked.times[0], 1.0 / info['sfreq'])
+    if return_as_dipoles:
+        out = _make_dipoles_sparse(
+            X, active_set, forward, evoked.times[0], 1.0 / info['sfreq'],
+            M, M_estimated, verbose=None)
+    else:
+        out = _make_sparse_stc(
+            X, active_set, forward, evoked.times[0], 1.0 / info['sfreq'])
 
     logger.info('[done]')
 
     if return_residual:
-        out = stc, residual
-    else:
-        out = stc
+        out = out, residual
 
     return out
diff --git a/mne/inverse_sparse/mxne_optim.py b/mne/inverse_sparse/mxne_optim.py
index ce54817..eeba6f4 100644
--- a/mne/inverse_sparse/mxne_optim.py
+++ b/mne/inverse_sparse/mxne_optim.py
@@ -4,7 +4,6 @@ from __future__ import print_function
 #
 # License: Simplified BSD
 
-from copy import deepcopy
 from math import sqrt, ceil
 
 import numpy as np
@@ -17,13 +16,13 @@ from ..externals.six.moves import xrange as range
 
 
 def groups_norm2(A, n_orient):
-    """compute squared L2 norms of groups inplace"""
+    """Compute squared L2 norms of groups inplace."""
     n_positions = A.shape[0] // n_orient
     return np.sum(np.power(A, 2, A).reshape(n_positions, -1), axis=1)
 
 
 def norm_l2inf(A, n_orient, copy=True):
-    """L2-inf norm"""
+    """L2-inf norm."""
     if A.size == 0:
         return 0.0
     if copy:
@@ -32,7 +31,7 @@ def norm_l2inf(A, n_orient, copy=True):
 
 
 def norm_l21(A, n_orient, copy=True):
-    """L21 norm"""
+    """L21 norm."""
     if A.size == 0:
         return 0.0
     if copy:
@@ -41,13 +40,33 @@ def norm_l21(A, n_orient, copy=True):
 
 
 def prox_l21(Y, alpha, n_orient, shape=None, is_stft=False):
-    """proximity operator for l21 norm
+    """Proximity operator for l21 norm.
 
     L2 over columns and L1 over rows => groups contain n_orient rows.
 
     It can eventually take into account the negative frequencies
     when a complex value is passed and is_stft=True.
 
+    Parameters
+    ----------
+    Y : array, shape (n_sources, n_coefs)
+        The input data.
+    alpha : float
+        The regularization parameter.
+    n_orient : int
+        Number of dipoles per locations (typically 1 or 3).
+    shape : None | tuple
+        Shape of TF coefficients matrix.
+    is_stft : bool
+        If True, Y contains TF coefficients.
+
+    Returns
+    -------
+    Y : array, shape (n_sources, n_coefs)
+        The output data.
+    active_set : array of bool, shape (n_sources, )
+        Mask of active sources
+
     Example
     -------
     >>> Y = np.tile(np.array([0, 4, 3, 0, 0], dtype=np.float), (2, 1))
@@ -92,9 +111,35 @@ def prox_l21(Y, alpha, n_orient, shape=None, is_stft=False):
 
 
 def prox_l1(Y, alpha, n_orient):
-    """proximity operator for l1 norm with multiple orientation support
+    """Proximity operator for l1 norm with multiple orientation support.
+
+    Please note that this function computes a soft-thresholding if
+    n_orient == 1 and a block soft-thresholding (L2 over orientation and
+    L1 over position (space + time)) if n_orient == 3. See also [1]_.
 
-    L2 over orientation and L1 over position (space + time)
+    Parameters
+    ----------
+    Y : array, shape (n_sources, n_coefs)
+        The input data.
+    alpha : float
+        The regularization parameter.
+    n_orient : int
+        Number of dipoles per locations (typically 1 or 3).
+
+    Returns
+    -------
+    Y : array, shape (n_sources, n_coefs)
+        The output data.
+    active_set : array of bool, shape (n_sources, )
+        Mask of active sources.
+
+    References
+    ----------
+    .. [1] A. Gramfort, D. Strohmeier, J. Haueisen, M. Hamalainen, M. Kowalski
+       "Time-Frequency Mixed-Norm Estimates: Sparse M/EEG imaging with
+       non-stationary source activations",
+       Neuroimage, Volume 70, pp. 410-422, 15 April 2013.
+       DOI: 10.1016/j.neuroimage.2012.12.051
 
     Example
     -------
@@ -129,13 +174,7 @@ def prox_l1(Y, alpha, n_orient):
 
 
 def dgap_l21(M, G, X, active_set, alpha, n_orient):
-    """Duality gaps for the mixed norm inverse problem
-
-    For details see:
-    Gramfort A., Kowalski M. and Hamalainen, M,
-    Mixed-norm estimates for the M/EEG inverse problem using accelerated
-    gradient methods, Physics in Medicine and Biology, 2012
-    http://dx.doi.org/10.1088/0031-9155/57/7/1937
+    """Duality gap for the mixed norm inverse problem.
 
     Parameters
     ----------
@@ -144,42 +183,51 @@ def dgap_l21(M, G, X, active_set, alpha, n_orient):
     G : array, shape (n_sensors, n_active)
         The gain matrix a.k.a. lead field.
     X : array, shape (n_active, n_times)
-        Sources
-    active_set : array of bool
-        Mask of active sources
+        Sources.
+    active_set : array of bool, shape (n_sources, )
+        Mask of active sources.
     alpha : float
-        Regularization parameter
+        The regularization parameter.
     n_orient : int
-        Number of dipoles per locations (typically 1 or 3)
+        Number of dipoles per locations (typically 1 or 3).
 
     Returns
     -------
     gap : float
-        Dual gap
-    pobj : float
-        Primal cost
-    dobj : float
-        Dual cost. gap = pobj - dobj
+        Dual gap.
+    p_obj : float
+        Primal objective.
+    d_obj : float
+        Dual objective. gap = p_obj - d_obj.
     R : array, shape (n_sensors, n_times)
-        Current residual of M - G * X
+        Current residual (M - G * X).
+
+    References
+    ----------
+    .. [1] A. Gramfort, M. Kowalski, M. Hamalainen,
+       "Mixed-norm estimates for the M/EEG inverse problem using accelerated
+       gradient methods", Physics in Medicine and Biology, 2012.
+       http://dx.doi.org/10.1088/0031-9155/57/7/1937
     """
     GX = np.dot(G[:, active_set], X)
     R = M - GX
     penalty = norm_l21(X, n_orient, copy=True)
     nR2 = sum_squared(R)
-    pobj = 0.5 * nR2 + alpha * penalty
+    p_obj = 0.5 * nR2 + alpha * penalty
+
     dual_norm = norm_l2inf(np.dot(G.T, R), n_orient, copy=False)
     scaling = alpha / dual_norm
     scaling = min(scaling, 1.0)
-    dobj = 0.5 * (scaling ** 2) * nR2 + scaling * np.sum(R * GX)
-    gap = pobj - dobj
-    return gap, pobj, dobj, R
+    d_obj = (scaling - 0.5 * (scaling ** 2)) * nR2 + scaling * np.sum(R * GX)
+
+    gap = p_obj - d_obj
+    return gap, p_obj, d_obj, R
 
 
 @verbose
 def _mixed_norm_solver_prox(M, G, alpha, lipschitz_constant, maxit=200,
                             tol=1e-8, verbose=None, init=None, n_orient=1):
-    """Solves L21 inverse problem with proximal iterations and FISTA"""
+    """Solve L21 inverse problem with proximal iterations and FISTA."""
     n_sensors, n_times = M.shape
     n_sensors, n_sources = G.shape
 
@@ -203,8 +251,8 @@ def _mixed_norm_solver_prox(M, G, alpha, lipschitz_constant, maxit=200,
 
     t = 1.0
     Y = np.zeros((n_sources, n_times))  # FISTA aux variable
-    E = []  # track cost function
-
+    E = []  # track primal objective function
+    highest_d_obj = - np.inf
     active_set = np.ones(n_sources, dtype=np.bool)  # start with full AS
 
     for i in range(maxit):
@@ -228,9 +276,11 @@ def _mixed_norm_solver_prox(M, G, alpha, lipschitz_constant, maxit=200,
         else:
             R = GTM - np.dot(gram[:, Y_as], Y[Y_as])
 
-        gap, pobj, dobj, _ = dgap_l21(M, G, X, active_set, alpha, n_orient)
-        E.append(pobj)
-        logger.debug("pobj : %s -- gap : %s" % (pobj, gap))
+        _, p_obj, d_obj, _ = dgap_l21(M, G, X, active_set, alpha, n_orient)
+        highest_d_obj = max(d_obj, highest_d_obj)
+        gap = p_obj - highest_d_obj
+        E.append(p_obj)
+        logger.debug("p_obj : %s -- gap : %s" % (p_obj, gap))
         if gap < tol:
             logger.debug('Convergence reached ! (gap: %s < %s)' % (gap, tol))
             break
@@ -240,7 +290,7 @@ def _mixed_norm_solver_prox(M, G, alpha, lipschitz_constant, maxit=200,
 @verbose
 def _mixed_norm_solver_cd(M, G, alpha, lipschitz_constant, maxit=10000,
                           tol=1e-8, verbose=None, init=None, n_orient=1):
-    """Solves L21 inverse problem with coordinate descent"""
+    """Solve L21 inverse problem with coordinate descent."""
     from sklearn.linear_model.coordinate_descent import MultiTaskLasso
 
     n_sensors, n_times = M.shape
@@ -249,8 +299,8 @@ def _mixed_norm_solver_cd(M, G, alpha, lipschitz_constant, maxit=10000,
     if init is not None:
         init = init.T
 
-    clf = MultiTaskLasso(alpha=alpha / len(M), tol=tol, normalize=False,
-                         fit_intercept=False, max_iter=maxit,
+    clf = MultiTaskLasso(alpha=alpha / len(M), tol=tol / sum_squared(M),
+                         normalize=False, fit_intercept=False, max_iter=maxit,
                          warm_start=True)
     clf.coef_ = init
     clf.fit(G, M)
@@ -258,14 +308,14 @@ def _mixed_norm_solver_cd(M, G, alpha, lipschitz_constant, maxit=10000,
     X = clf.coef_.T
     active_set = np.any(X, axis=1)
     X = X[active_set]
-    gap, pobj, dobj, _ = dgap_l21(M, G, X, active_set, alpha, n_orient)
-    return X, active_set, pobj
+    gap, p_obj, d_obj, _ = dgap_l21(M, G, X, active_set, alpha, n_orient)
+    return X, active_set, p_obj
 
 
 @verbose
 def _mixed_norm_solver_bcd(M, G, alpha, lipschitz_constant, maxit=200,
                            tol=1e-8, verbose=None, init=None, n_orient=1):
-    """Solves L21 inverse problem with block coordinate descent"""
+    """Solve L21 inverse problem with block coordinate descent."""
     # First make G fortran for faster access to blocks of columns
     G = np.asfortranarray(G)
 
@@ -280,8 +330,8 @@ def _mixed_norm_solver_bcd(M, G, alpha, lipschitz_constant, maxit=200,
         X = init
         R = M - np.dot(G, X)
 
-    E = []  # track cost function
-
+    E = []  # track primal objective function
+    highest_d_obj = - np.inf
     active_set = np.zeros(n_sources, dtype=np.bool)  # start with full AS
 
     alpha_lc = alpha / lipschitz_constant
@@ -311,11 +361,13 @@ def _mixed_norm_solver_bcd(M, G, alpha, lipschitz_constant, maxit=200,
                 X_j[:] = X_j_new
                 active_set[idx] = True
 
-        gap, pobj, dobj, _ = dgap_l21(M, G, X[active_set], active_set, alpha,
+        _, p_obj, d_obj, _ = dgap_l21(M, G, X[active_set], active_set, alpha,
                                       n_orient)
-        E.append(pobj)
-        logger.debug("Iteration %d :: pobj %f :: dgap %f :: n_active %d" % (
-                     i + 1, pobj, gap, np.sum(active_set) / n_orient))
+        highest_d_obj = max(d_obj, highest_d_obj)
+        gap = p_obj - highest_d_obj
+        E.append(p_obj)
+        logger.debug("Iteration %d :: p_obj %f :: dgap %f :: n_active %d" % (
+                     i + 1, p_obj, gap, np.sum(active_set) / n_orient))
 
         if gap < tol:
             logger.debug('Convergence reached ! (gap: %s < %s)' % (gap, tol))
@@ -329,14 +381,8 @@ def _mixed_norm_solver_bcd(M, G, alpha, lipschitz_constant, maxit=200,
 @verbose
 def mixed_norm_solver(M, G, alpha, maxit=3000, tol=1e-8, verbose=None,
                       active_set_size=50, debias=True, n_orient=1,
-                      solver='auto'):
-    """Solves L1/L2 mixed-norm inverse problem with active set strategy
-
-    Algorithm is detailed in:
-    Gramfort A., Kowalski M. and Hamalainen, M,
-    Mixed-norm estimates for the M/EEG inverse problem using accelerated
-    gradient methods, Physics in Medicine and Biology, 2012
-    http://dx.doi.org/10.1088/0031-9155/57/7/1937
+                      solver='auto', return_gap=False):
+    """Solve L1/L2 mixed-norm inverse problem with active set strategy.
 
     Parameters
     ----------
@@ -352,7 +398,8 @@ def mixed_norm_solver(M, G, alpha, maxit=3000, tol=1e-8, verbose=None,
     tol : float
         Tolerance on dual gap for convergence checking.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
     active_set_size : int
         Size of active set increase at each iteration.
     debias : bool
@@ -361,6 +408,8 @@ def mixed_norm_solver(M, G, alpha, maxit=3000, tol=1e-8, verbose=None,
         The number of orientation (1 : fixed or 3 : free or loose).
     solver : 'prox' | 'cd' | 'bcd' | 'auto'
         The algorithm to use for the optimization.
+    return_gap : bool
+        Return final duality gap.
 
     Returns
     -------
@@ -370,6 +419,20 @@ def mixed_norm_solver(M, G, alpha, maxit=3000, tol=1e-8, verbose=None,
         The mask of active sources.
     E : list
         The value of the objective function over the iterations.
+    gap : float
+        Final duality gap. Returned only if return_gap is True.
+
+    References
+    ----------
+    .. [1] A. Gramfort, M. Kowalski, M. Hamalainen,
+       "Mixed-norm estimates for the M/EEG inverse problem using accelerated
+       gradient methods", Physics in Medicine and Biology, 2012.
+       http://dx.doi.org/10.1088/0031-9155/57/7/1937
+
+    .. [2] D. Strohmeier, Y. Bekhti, J. Haueisen, A. Gramfort,
+       "The Iterative Reweighted Mixed-Norm Estimate for Spatio-Temporal
+       MEG/EEG Source Reconstruction", IEEE Transactions of Medical Imaging,
+       Volume 35 (10), pp. 2218-2228, 15 April 2013.
     """
     n_dipoles = G.shape[1]
     n_positions = n_dipoles // n_orient
@@ -380,7 +443,7 @@ def mixed_norm_solver(M, G, alpha, maxit=3000, tol=1e-8, verbose=None,
 
     has_sklearn = True
     try:
-        from sklearn.linear_model.coordinate_descent import MultiTaskLasso  # noqa
+        from sklearn.linear_model.coordinate_descent import MultiTaskLasso  # noqa: F401,E501
     except ImportError:
         has_sklearn = False
 
@@ -423,6 +486,7 @@ def mixed_norm_solver(M, G, alpha, maxit=3000, tol=1e-8, verbose=None,
 
     if active_set_size is not None:
         E = list()
+        highest_d_obj = - np.inf
         X_init = None
         active_set = np.zeros(n_dipoles, dtype=np.bool)
         idx_large_corr = np.argsort(groups_norm2(np.dot(G.T, M), n_orient))
@@ -445,12 +509,14 @@ def mixed_norm_solver(M, G, alpha, maxit=3000, tol=1e-8, verbose=None,
             active_set[active_set] = as_.copy()
             idx_old_active_set = np.where(active_set)[0]
 
-            gap, pobj, dobj, R = dgap_l21(M, G, X, active_set, alpha,
+            _, p_obj, d_obj, R = dgap_l21(M, G, X, active_set, alpha,
                                           n_orient)
-            E.append(pobj)
-            logger.info("Iteration %d :: pobj %f :: dgap %f ::"
+            highest_d_obj = max(d_obj, highest_d_obj)
+            gap = p_obj - highest_d_obj
+            E.append(p_obj)
+            logger.info("Iteration %d :: p_obj %f :: dgap %f ::"
                         "n_active_start %d :: n_active_end %d" % (
-                            k + 1, pobj, gap, as_size // n_orient,
+                            k + 1, p_obj, gap, as_size // n_orient,
                             np.sum(active_set) // n_orient))
             if gap < tol:
                 logger.info('Convergence reached ! (gap: %s < %s)'
@@ -477,6 +543,8 @@ def mixed_norm_solver(M, G, alpha, maxit=3000, tol=1e-8, verbose=None,
     else:
         X, active_set, E = l21_solver(M, G, alpha, lc, maxit=maxit,
                                       tol=tol, n_orient=n_orient, init=None)
+        if return_gap:
+            gap = dgap_l21(M, G, X, active_set, alpha, n_orient)[0]
 
     if np.any(active_set) and debias:
         bias = compute_bias(M, G[:, active_set], X, n_orient=n_orient)
@@ -484,21 +552,17 @@ def mixed_norm_solver(M, G, alpha, maxit=3000, tol=1e-8, verbose=None,
 
     logger.info('Final active set size: %s' % (np.sum(active_set) // n_orient))
 
-    return X, active_set, E
+    if return_gap:
+        return X, active_set, E, gap
+    else:
+        return X, active_set, E
 
 
 @verbose
 def iterative_mixed_norm_solver(M, G, alpha, n_mxne_iter, maxit=3000,
                                 tol=1e-8, verbose=None, active_set_size=50,
                                 debias=True, n_orient=1, solver='auto'):
-    """Solves L0.5/L2 mixed-norm inverse problem with active set strategy
-
-    Algorithm is detailed in:
-
-    Strohmeier D., Haueisen J., and Gramfort A.:
-    Improved MEG/EEG source localization with reweighted mixed-norms,
-    4th International Workshop on Pattern Recognition in Neuroimaging,
-    Tuebingen, 2014
+    """Solve L0.5/L2 mixed-norm inverse problem with active set strategy.
 
     Parameters
     ----------
@@ -517,7 +581,8 @@ def iterative_mixed_norm_solver(M, G, alpha, n_mxne_iter, maxit=3000,
     tol : float
         Tolerance on dual gap for convergence checking.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
     active_set_size : int
         Size of active set increase at each iteration.
     debias : bool
@@ -535,6 +600,13 @@ def iterative_mixed_norm_solver(M, G, alpha, n_mxne_iter, maxit=3000,
         The mask of active sources.
     E : list
         The value of the objective function over the iterations.
+
+    References
+    ----------
+    .. [1] D. Strohmeier, Y. Bekhti, J. Haueisen, A. Gramfort,
+       "The Iterative Reweighted Mixed-Norm Estimate for Spatio-Temporal
+       MEG/EEG Source Reconstruction", IEEE Transactions of Medical Imaging,
+       Volume 35 (10), pp. 2218-2228, 2016.
     """
     def g(w):
         return np.sqrt(np.sqrt(groups_norm2(w.copy(), n_orient)))
@@ -578,7 +650,7 @@ def iterative_mixed_norm_solver(M, G, alpha, n_mxne_iter, maxit=3000,
             # Reapply weights to have correct unit
             X *= weights[_active_set][:, np.newaxis]
             weights = gprime(X)
-            p_obj = 0.5 * linalg.norm(M - np.dot(G[:, active_set],  X),
+            p_obj = 0.5 * linalg.norm(M - np.dot(G[:, active_set], X),
                                       'fro') ** 2. + alpha * np.sum(g(X))
             E.append(p_obj)
 
@@ -605,7 +677,7 @@ def iterative_mixed_norm_solver(M, G, alpha, n_mxne_iter, maxit=3000,
 
 @verbose
 def tf_lipschitz_constant(M, G, phi, phiT, tol=1e-3, verbose=None):
-    """Compute lipschitz constant for FISTA
+    """Compute lipschitz constant for FISTA.
 
     It uses a power iteration method.
     """
@@ -629,7 +701,7 @@ def tf_lipschitz_constant(M, G, phi, phiT, tol=1e-3, verbose=None):
 
 
 def safe_max_abs(A, ia):
-    """Compute np.max(np.abs(A[ia])) possible with empty A"""
+    """Compute np.max(np.abs(A[ia])) possible with empty A."""
     if np.sum(ia):  # ia is not empty
         return np.max(np.abs(A[ia]))
     else:
@@ -637,40 +709,41 @@ def safe_max_abs(A, ia):
 
 
 def safe_max_abs_diff(A, ia, B, ib):
-    """Compute np.max(np.abs(A)) possible with empty A"""
+    """Compute np.max(np.abs(A)) possible with empty A."""
     A = A[ia] if np.sum(ia) else 0.0
     B = B[ib] if np.sum(ia) else 0.0
     return np.max(np.abs(A - B))
 
 
 class _Phi(object):
-    """Util class to have phi stft as callable without using
-    a lambda that does not pickle"""
-    def __init__(self, wsize, tstep, n_coefs):
+    """Have phi stft as callable w/o using a lambda that does not pickle."""
+
+    def __init__(self, wsize, tstep, n_coefs):  # noqa: D102
         self.wsize = wsize
         self.tstep = tstep
         self.n_coefs = n_coefs
 
-    def __call__(self, x):
+    def __call__(self, x):  # noqa: D105
         return stft(x, self.wsize, self.tstep,
                     verbose=False).reshape(-1, self.n_coefs)
 
 
 class _PhiT(object):
-    """Util class to have phi.T istft as callable without using
-    a lambda that does not pickle"""
-    def __init__(self, tstep, n_freq, n_step, n_times):
+    """Have phi.T istft as callable w/o using a lambda that does not pickle."""
+
+    def __init__(self, tstep, n_freq, n_step, n_times):  # noqa: D102
         self.tstep = tstep
         self.n_freq = n_freq
         self.n_step = n_step
         self.n_times = n_times
 
-    def __call__(self, z):
+    def __call__(self, z):  # noqa: D105
         return istft(z.reshape(-1, self.n_freq, self.n_step), self.tstep,
                      self.n_times)
 
 
 def norm_l21_tf(Z, shape, n_orient):
+    """L21 norm for TF."""
     if Z.shape[0]:
         Z2 = Z.reshape(*shape)
         l21_norm = np.sqrt(stft_norm2(Z2).reshape(-1, n_orient).sum(axis=1))
@@ -681,6 +754,7 @@ def norm_l21_tf(Z, shape, n_orient):
 
 
 def norm_l1_tf(Z, shape, n_orient):
+    """L1 norm for TF."""
     if Z.shape[0]:
         n_positions = Z.shape[0] // n_orient
         Z_ = np.sqrt(np.sum((np.abs(Z) ** 2.).reshape((n_orient, -1),
@@ -694,12 +768,86 @@ def norm_l1_tf(Z, shape, n_orient):
     return l1_norm
 
 
- at verbose
-def _tf_mixed_norm_solver_bcd_(M, G, Z, active_set, alpha_space, alpha_time,
-                               lipschitz_constant, phi, phiT,
-                               wsize=64, tstep=4, n_orient=1,
-                               maxit=200, tol=1e-8, log_objective=True,
-                               perc=None, verbose=None):
+def dgap_l21l1(M, G, Z, active_set, alpha_space, alpha_time, phi, phiT, shape,
+               n_orient, highest_d_obj):
+    """Duality gap for the time-frequency mixed norm inverse problem.
+
+    Parameters
+    ----------
+    M : array, shape (n_sensors, n_times)
+        The data.
+    G : array, shape (n_sensors, n_sources)
+        Gain matrix a.k.a. lead field.
+    Z : array, shape (n_active, n_coefs)
+        Sources in TF domain.
+    active_set : array of bool, shape (n_sources, )
+        Mask of active sources.
+    alpha_space : float
+        The spatial regularization parameter.
+    alpha_time : float
+        The temporal regularization parameter. The higher it is the smoother
+        will be the estimated time series.
+    phi : instance of _Phi
+        The TF operator.
+    phiT : instance of _PhiT
+        The transpose of the TF operator.
+    shape : tuple
+        Shape of TF coefficients matrix.
+    n_orient : int
+        Number of dipoles per locations (typically 1 or 3).
+    highest_d_obj : float
+        The highest value of the dual objective so far.
+
+    Returns
+    -------
+    gap : float
+        Dual gap
+    p_obj : float
+        Primal objective
+    d_obj : float
+        Dual objective. gap = p_obj - d_obj
+    R : array, shape (n_sensors, n_times)
+        Current residual (M - G * X)
+
+    References
+    ----------
+    .. [1] A. Gramfort, M. Kowalski, M. Hamalainen,
+       "Mixed-norm estimates for the M/EEG inverse problem using accelerated
+       gradient methods", Physics in Medicine and Biology, 2012.
+       http://dx.doi.org/10.1088/0031-9155/57/7/1937
+
+    .. [2] J. Wang, J. Ye,
+       "Two-layer feature reduction for sparse-group lasso via decomposition of
+       convex sets", Advances in Neural Information Processing Systems (NIPS),
+       vol. 27, pp. 2132-2140, 2014.
+    """
+    X = phiT(Z)
+    GX = np.dot(G[:, active_set], X)
+    R = M - GX
+    penaltyl1 = norm_l1_tf(Z, shape, n_orient)
+    penaltyl21 = norm_l21_tf(Z, shape, n_orient)
+    nR2 = sum_squared(R)
+    p_obj = 0.5 * nR2 + alpha_space * penaltyl21 + alpha_time * penaltyl1
+
+    GRPhi_norm, _ = prox_l1(phi(np.dot(G.T, R)), alpha_time, n_orient)
+    GRPhi_norm = stft_norm2(GRPhi_norm.reshape(*shape)).reshape(-1, n_orient)
+    GRPhi_norm = np.sqrt(GRPhi_norm.sum(axis=1))
+    dual_norm = np.amax(GRPhi_norm)
+    scaling = alpha_space / dual_norm
+    scaling = min(scaling, 1.0)
+    d_obj = (scaling - 0.5 * (scaling ** 2)) * nR2 + scaling * np.sum(R * GX)
+    d_obj = max(d_obj, highest_d_obj)
+
+    gap = p_obj - d_obj
+    return gap, p_obj, d_obj, R
+
+
+def _tf_mixed_norm_solver_bcd_(M, G, Z, active_set, candidates, alpha_space,
+                               alpha_time, lipschitz_constant, phi, phiT,
+                               shape, n_orient=1, maxit=200, tol=1e-8,
+                               log_objective=True, perc=None, timeit=True,
+                               verbose=None):
+
     # First make G fortran for faster access to blocks of columns
     G = np.asfortranarray(G)
 
@@ -707,42 +855,37 @@ def _tf_mixed_norm_solver_bcd_(M, G, Z, active_set, alpha_space, alpha_time,
     n_sources = G.shape[1]
     n_positions = n_sources // n_orient
 
-    n_step = int(ceil(n_times / float(tstep)))
-    n_freq = wsize // 2 + 1
-    shape = (-1, n_freq, n_step)
-
+    Gd = G.copy()
     G = dict(zip(np.arange(n_positions), np.hsplit(G, n_positions)))
+
     R = M.copy()  # residual
-    active = np.where(active_set)[0][::n_orient] // n_orient
+    active = np.where(active_set[::n_orient])[0]
     for idx in active:
         R -= np.dot(G[idx], phiT(Z[idx]))
 
-    E = []  # track cost function
+    E = []  # track primal objective function
 
     alpha_time_lc = alpha_time / lipschitz_constant
     alpha_space_lc = alpha_space / lipschitz_constant
 
     converged = False
+    d_obj = -np.Inf
 
-    for i in range(maxit):
-        val_norm_l21_tf = 0.0
-        val_norm_l1_tf = 0.0
-        max_diff = 0.0
-        active_set_0 = active_set.copy()
-        for j in range(n_positions):
-            ids = j * n_orient
+    ii = -1
+    while True:
+        ii += 1
+        for jj in candidates:
+            ids = jj * n_orient
             ide = ids + n_orient
 
-            G_j = G[j]
-            Z_j = Z[j]
+            G_j = G[jj]
+            Z_j = Z[jj]
             active_set_j = active_set[ids:ide]
 
-            Z0 = deepcopy(Z_j)
-
             was_active = np.any(active_set_j)
 
             # gradient step
-            GTR = np.dot(G_j.T, R) / lipschitz_constant[j]
+            GTR = np.dot(G_j.T, R) / lipschitz_constant[jj]
             X_j_new = GTR.copy()
 
             if was_active:
@@ -751,227 +894,159 @@ def _tf_mixed_norm_solver_bcd_(M, G, Z, active_set, alpha_space, alpha_time,
                 X_j_new += X_j
 
             rows_norm = linalg.norm(X_j_new, 'fro')
-            if rows_norm <= alpha_space_lc[j]:
+            if rows_norm <= alpha_space_lc[jj]:
                 if was_active:
-                    Z[j] = 0.0
+                    Z[jj] = 0.0
                     active_set_j[:] = False
             else:
                 if was_active:
                     Z_j_new = Z_j + phi(GTR)
                 else:
                     Z_j_new = phi(GTR)
-
                 col_norm = np.sqrt(np.sum(np.abs(Z_j_new) ** 2, axis=0))
 
-                if np.all(col_norm <= alpha_time_lc[j]):
-                    Z[j] = 0.0
+                if np.all(col_norm <= alpha_time_lc[jj]):
+                    Z[jj] = 0.0
                     active_set_j[:] = False
                 else:
                     # l1
-                    shrink = np.maximum(1.0 - alpha_time_lc[j] / np.maximum(
-                                        col_norm, alpha_time_lc[j]), 0.0)
+                    shrink = np.maximum(1.0 - alpha_time_lc[jj] / np.maximum(
+                                        col_norm, alpha_time_lc[jj]), 0.0)
                     Z_j_new *= shrink[np.newaxis, :]
 
                     # l21
                     shape_init = Z_j_new.shape
                     Z_j_new = Z_j_new.reshape(*shape)
                     row_norm = np.sqrt(stft_norm2(Z_j_new).sum())
-                    if row_norm <= alpha_space_lc[j]:
-                        Z[j] = 0.0
+                    if row_norm <= alpha_space_lc[jj]:
+                        Z[jj] = 0.0
                         active_set_j[:] = False
                     else:
-                        shrink = np.maximum(1.0 - alpha_space_lc[j] /
+                        shrink = np.maximum(1.0 - alpha_space_lc[jj] /
                                             np.maximum(row_norm,
-                                            alpha_space_lc[j]), 0.0)
+                                            alpha_space_lc[jj]), 0.0)
                         Z_j_new *= shrink
-                        Z[j] = Z_j_new.reshape(-1, *shape_init[1:]).copy()
+                        Z[jj] = Z_j_new.reshape(-1, *shape_init[1:]).copy()
                         active_set_j[:] = True
-                        R -= np.dot(G_j, phiT(Z[j]))
-
-                        if log_objective:
-                            val_norm_l21_tf += norm_l21_tf(
-                                Z[j], shape, n_orient)
-                            val_norm_l1_tf += norm_l1_tf(
-                                Z[j], shape, n_orient)
-
-            max_diff = np.maximum(max_diff, np.max(np.abs(Z[j] - Z0)))
-
-        if log_objective:  # log cost function value
-            pobj = (0.5 * (R ** 2.).sum() + alpha_space * val_norm_l21_tf +
-                    alpha_time * val_norm_l1_tf)
-            E.append(pobj)
-            logger.info("Iteration %d :: pobj %f :: n_active %d" % (i + 1,
-                        pobj, np.sum(active_set) / n_orient))
+                        R -= np.dot(G_j, phiT(Z[jj]))
+
+        if log_objective:
+            if (ii + 1) % 10 == 0:
+                Zd = np.vstack([Z[pos] for pos in range(n_positions)
+                               if np.any(Z[pos])])
+                gap, p_obj, d_obj, _ = dgap_l21l1(
+                    M, Gd, Zd, active_set, alpha_space, alpha_time, phi, phiT,
+                    shape, n_orient, d_obj)
+                converged = (gap < tol)
+                E.append(p_obj)
+                logger.info("\n    Iteration %d :: n_active %d" % (
+                            ii + 1, np.sum(active_set) / n_orient))
+                logger.info("    dgap %.2e :: p_obj %f :: d_obj %f" % (
+                            gap, p_obj, d_obj))
         else:
-            logger.info("Iteration %d" % (i + 1))
+            if (ii + 1) % 10 == 0:
+                logger.info("\n    Iteration %d :: n_active %d" % (
+                            ii + 1, np.sum(active_set) / n_orient))
+
+        if converged:
+            break
+
+        if (ii == maxit - 1):
+            converged = False
+            break
 
         if perc is not None:
             if np.sum(active_set) / float(n_orient) <= perc * n_positions:
                 break
 
-        if np.array_equal(active_set, active_set_0):
-            if max_diff < tol:
-                logger.info("Convergence reached !")
-                converged = True
-                break
-
     return Z, active_set, E, converged
 
 
 @verbose
-def _tf_mixed_norm_solver_bcd_active_set(
-        M, G, alpha_space, alpha_time, lipschitz_constant, phi, phiT,
-        Z_init=None, wsize=64, tstep=4, n_orient=1, maxit=200, tol=1e-8,
-        log_objective=True, perc=None, verbose=None):
-    """Solves TF L21+L1 inverse solver with BCD and active set approach
-
-    Algorithm is detailed in:
-
-    Strohmeier D., Gramfort A., and Haueisen J.:
-    MEG/EEG source imaging with a non-convex penalty in the time-
-    frequency domain,
-    5th International Workshop on Pattern Recognition in Neuroimaging,
-    Stanford University, 2015
+def _tf_mixed_norm_solver_bcd_active_set(M, G, alpha_space, alpha_time,
+                                         lipschitz_constant, phi, phiT, shape,
+                                         Z_init=None, n_orient=1, maxit=200,
+                                         tol=1e-8, log_objective=True,
+                                         verbose=None):
 
-    Parameters
-    ----------
-    M : array
-        The data.
-    G : array
-        The forward operator.
-    alpha_space : float in [0, 100]
-        Regularization parameter for spatial sparsity. If larger than 100,
-        then no source will be active.
-    alpha_time : float in [0, 100]
-        Regularization parameter for temporal sparsity. It set to 0,
-        no temporal regularization is applied. It this case, TF-MxNE is
-        equivalent to MxNE with L21 norm.
-    lipschitz_constant : float
-        The lipschitz constant of the spatio temporal linear operator.
-    phi : instance of _Phi
-        The TF operator.
-    phiT : instance of _PhiT
-        The transpose of the TF operator.
-    Z_init : None | array
-        The initialization of the TF coefficient matrix. If None, zeros
-        will be used for all coefficients.
-    wsize: int
-        length of the STFT window in samples (must be a multiple of 4).
-    tstep: int
-        step between successive windows in samples (must be a multiple of 2,
-        a divider of wsize and smaller than wsize/2) (default: wsize/2).
-    n_orient : int
-        The number of orientation (1 : fixed or 3 : free or loose).
-    maxit : int
-        The number of iterations.
-    tol : float
-        If absolute difference between estimates at 2 successive iterations
-        is lower than tol, the convergence is reached.
-    log_objective : bool
-        If True, the value of the minimized objective function is computed
-        and stored at every iteration.
-    perc : None | float in [0, 1]
-        The early stopping parameter used for BCD with active set approach.
-        If the active set size is smaller than perc * n_sources, the
-        subproblem limited to the active set is stopped. If None, full
-        convergence will be achieved.
-    verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
-
-    Returns
-    -------
-    X : array
-        The source estimates.
-    active_set : array
-        The mask of active sources.
-    E : list
-        The value of the objective function at each iteration. If log_objective
-        is False, it will be empty.
-    """
+    n_sensors, n_times = M.shape
     n_sources = G.shape[1]
     n_positions = n_sources // n_orient
 
-    if Z_init is None:
-        Z = dict.fromkeys(range(n_positions), 0.0)
-        active_set = np.zeros(n_sources, dtype=np.bool)
-    else:
-        active_set = np.zeros(n_sources, dtype=np.bool)
-        active = list()
-        for i in range(n_positions):
-            if np.any(Z_init[i * n_orient:(i + 1) * n_orient]):
-                active_set[i * n_orient:(i + 1) * n_orient] = True
-                active.append(i)
-        Z = dict.fromkeys(range(n_positions), 0.0)
+    Z = dict.fromkeys(np.arange(n_positions), 0.0)
+    active_set = np.zeros(n_sources, dtype=np.bool)
+    active = []
+    if Z_init is not None:
+        if Z_init.shape != (n_sources, shape[1] * shape[2]):
+            raise Exception('Z_init must be None or an array with shape '
+                            '(n_sources, n_coefs).')
+        for ii in range(n_positions):
+            if np.any(Z_init[ii * n_orient:(ii + 1) * n_orient]):
+                active_set[ii * n_orient:(ii + 1) * n_orient] = True
+                active.append(ii)
         if len(active):
             Z.update(dict(zip(active, np.vsplit(Z_init[active_set],
                      len(active)))))
 
-    Z, active_set, E, _ = _tf_mixed_norm_solver_bcd_(
-        M, G, Z, active_set, alpha_space, alpha_time, lipschitz_constant,
-        phi, phiT, wsize=wsize, tstep=tstep, n_orient=n_orient, maxit=1,
-        tol=tol, log_objective=log_objective, perc=None, verbose=verbose)
+    E = []
+
+    candidates = range(n_positions)
+    d_obj = -np.inf
+
+    while True:
+        Z_init = dict.fromkeys(np.arange(n_positions), 0.0)
+        Z_init.update(dict(zip(active, Z.values())))
+        Z, active_set, E_tmp, _ = _tf_mixed_norm_solver_bcd_(
+            M, G, Z_init, active_set, candidates, alpha_space, alpha_time,
+            lipschitz_constant, phi, phiT, shape, n_orient=n_orient,
+            maxit=1, tol=tol, log_objective=False, perc=None,
+            verbose=verbose)
+        E += E_tmp
 
-    while active_set.sum():
-        active = np.where(active_set)[0][::n_orient] // n_orient
+        active = np.where(active_set[::n_orient])[0]
         Z_init = dict(zip(range(len(active)), [Z[idx] for idx in active]))
+        candidates_ = range(len(active))
         Z, as_, E_tmp, converged = _tf_mixed_norm_solver_bcd_(
             M, G[:, active_set], Z_init,
             np.ones(len(active) * n_orient, dtype=np.bool),
-            alpha_space, alpha_time,
-            lipschitz_constant[active_set[::n_orient]],
-            phi, phiT, wsize=wsize, tstep=tstep, n_orient=n_orient,
-            maxit=maxit, tol=tol, log_objective=log_objective,
-            perc=0.5, verbose=verbose)
-        E += E_tmp
-        active = np.where(active_set)[0][::n_orient] // n_orient
-        Z_init = dict.fromkeys(range(n_positions), 0.0)
-        Z_init.update(dict(zip(active, Z.values())))
-        active_set[active_set] = as_
-        active_set_0 = active_set.copy()
-        Z, active_set, E_tmp, _ = _tf_mixed_norm_solver_bcd_(
-            M, G, Z_init, active_set, alpha_space, alpha_time,
-            lipschitz_constant, phi, phiT, wsize=wsize, tstep=tstep,
-            n_orient=n_orient, maxit=1, tol=tol, log_objective=log_objective,
-            perc=None, verbose=verbose)
+            candidates_, alpha_space, alpha_time,
+            lipschitz_constant[active_set[::n_orient]], phi, phiT, shape,
+            n_orient=n_orient, maxit=maxit, tol=tol,
+            log_objective=log_objective, perc=0.5, verbose=verbose)
+        active = np.where(active_set[::n_orient])[0]
+        active_set[active_set] = as_.copy()
         E += E_tmp
+
+        converged = True
         if converged:
-            if np.array_equal(active_set_0, active_set):
+            Zd = np.vstack([Z[pos] for pos in range(len(Z)) if np.any(Z[pos])])
+            gap, p_obj, d_obj, _ = dgap_l21l1(
+                M, G, Zd, active_set, alpha_space, alpha_time,
+                phi, phiT, shape, n_orient, d_obj)
+            logger.info("\ndgap %.2e :: p_obj %f :: d_obj %f :: n_active %d"
+                        % (gap, p_obj, d_obj, np.sum(active_set) / n_orient))
+            if gap < tol:
+                logger.info("\nConvergence reached!\n")
                 break
 
     if active_set.sum():
-        Z = np.vstack([Z_ for Z_ in list(Z.values()) if np.any(Z_)])
+        Z = np.vstack([Z[pos] for pos in range(len(Z)) if np.any(Z[pos])])
         X = phiT(Z)
     else:
-        n_sensors, n_times = M.shape
-        n_step = int(ceil(n_times / float(tstep)))
-        n_freq = wsize // 2 + 1
+        n_step = shape[2]
+        n_freq = shape[1]
         Z = np.zeros((0, n_step * n_freq), dtype=np.complex)
         X = np.zeros((0, n_times))
 
-    return X, Z, active_set, E
+    return X, Z, active_set, E, gap
 
 
 @verbose
 def tf_mixed_norm_solver(M, G, alpha_space, alpha_time, wsize=64, tstep=4,
                          n_orient=1, maxit=200, tol=1e-8, log_objective=True,
-                         debias=True, verbose=None):
-    """Solves TF L21+L1 inverse solver with BCD and active set approach
-
-    Algorithm is detailed in:
-
-    A. Gramfort, D. Strohmeier, J. Haueisen, M. Hamalainen, M. Kowalski
-    Time-Frequency Mixed-Norm Estimates: Sparse M/EEG imaging with
-    non-stationary source activations
-    Neuroimage, Volume 70, 15 April 2013, Pages 410-422, ISSN 1053-8119,
-    DOI: 10.1016/j.neuroimage.2012.12.051.
-
-    Functional Brain Imaging with M/EEG Using Structured Sparsity in
-    Time-Frequency Dictionaries
-    Gramfort A., Strohmeier D., Haueisen J., Hamalainen M. and Kowalski M.
-    INFORMATION PROCESSING IN MEDICAL IMAGING
-    Lecture Notes in Computer Science, 2011, Volume 6801/2011,
-    600-611, DOI: 10.1007/978-3-642-22092-0_49
-    http://dx.doi.org/10.1007/978-3-642-22092-0_49
+                         active_set_size=None, debias=True, return_gap=False,
+                         verbose=None):
+    """Solve TF L21+L1 inverse solver with BCD and active set approach.
 
     Parameters
     ----------
@@ -980,7 +1055,7 @@ def tf_mixed_norm_solver(M, G, alpha_space, alpha_time, wsize=64, tstep=4,
     G : array, shape (n_sensors, n_dipoles)
         The gain matrix a.k.a. lead field.
     alpha_space : float
-        The spatial regularization parameter. It should be between 0 and 100.
+        The spatial regularization parameter.
     alpha_time : float
         The temporal regularization parameter. The higher it is the smoother
         will be the estimated time series.
@@ -998,11 +1073,14 @@ def tf_mixed_norm_solver(M, G, alpha_space, alpha_time, wsize=64, tstep=4,
         is lower than tol, the convergence is reached.
     log_objective : bool
         If True, the value of the minimized objective function is computed
-        and stored at every iteration.
+        and stored at every 10th iteration.
     debias : bool
         Debias source estimates.
+    return_gap : bool
+        Return final duality gap.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -1013,6 +1091,23 @@ def tf_mixed_norm_solver(M, G, alpha_space, alpha_time, wsize=64, tstep=4,
     E : list
         The value of the objective function at each iteration. If log_objective
         is False, it will be empty.
+    gap : float
+        Final duality gap. Returned only if return_gap is True.
+
+    References
+    ----------
+    .. [1] A. Gramfort, D. Strohmeier, J. Haueisen, M. Hamalainen, M. Kowalski
+       "Time-Frequency Mixed-Norm Estimates: Sparse M/EEG imaging with
+       non-stationary source activations",
+       Neuroimage, Volume 70, pp. 410-422, 15 April 2013.
+       DOI: 10.1016/j.neuroimage.2012.12.051
+
+    .. [2] A. Gramfort, D. Strohmeier, J. Haueisen, M. Hamalainen, M. Kowalski
+       "Functional Brain Imaging with M/EEG Using Structured Sparsity in
+       Time-Frequency Dictionaries",
+       Proceedings Information Processing in Medical Imaging
+       Lecture Notes in Computer Science, Volume 6801/2011, pp. 600-611, 2011.
+       DOI: 10.1007/978-3-642-22092-0_49
     """
     n_sensors, n_times = M.shape
     n_sensors, n_sources = G.shape
@@ -1021,6 +1116,7 @@ def tf_mixed_norm_solver(M, G, alpha_space, alpha_time, wsize=64, tstep=4,
     n_step = int(ceil(n_times / float(tstep)))
     n_freq = wsize // 2 + 1
     n_coefs = n_step * n_freq
+    shape = (-1, n_freq, n_step)
     phi = _Phi(wsize, tstep, n_coefs)
     phiT = _PhiT(tstep, n_freq, n_step, n_times)
 
@@ -1032,14 +1128,17 @@ def tf_mixed_norm_solver(M, G, alpha_space, alpha_time, wsize=64, tstep=4,
             G_tmp = G[:, (j * n_orient):((j + 1) * n_orient)]
             lc[j] = linalg.norm(np.dot(G_tmp.T, G_tmp), ord=2)
 
-    logger.info("Using block coordinate descent and active set approach")
-    X, Z, active_set, E = _tf_mixed_norm_solver_bcd_active_set(
-        M, G, alpha_space, alpha_time, lc, phi, phiT, Z_init=None,
-        wsize=wsize, tstep=tstep, n_orient=n_orient, maxit=maxit, tol=tol,
+    logger.info("Using block coordinate descent with active set approach")
+    X, Z, active_set, E, gap = _tf_mixed_norm_solver_bcd_active_set(
+        M, G, alpha_space, alpha_time, lc, phi, phiT, shape, Z_init=None,
+        n_orient=n_orient, maxit=maxit, tol=tol,
         log_objective=log_objective, verbose=None)
 
     if np.any(active_set) and debias:
         bias = compute_bias(M, G[:, active_set], X, n_orient=n_orient)
         X *= bias[:, np.newaxis]
 
-    return X, active_set, E
+    if return_gap:
+        return X, active_set, E, gap
+    else:
+        return X, active_set, E
diff --git a/mne/inverse_sparse/tests/test_gamma_map.py b/mne/inverse_sparse/tests/test_gamma_map.py
index 98f20d9..ff9dee6 100644
--- a/mne/inverse_sparse/tests/test_gamma_map.py
+++ b/mne/inverse_sparse/tests/test_gamma_map.py
@@ -4,16 +4,22 @@
 
 import os.path as op
 
-from nose.tools import assert_true
+from nose.tools import assert_true, assert_raises
+import pytest
 import numpy as np
-from numpy.testing import assert_array_almost_equal, assert_equal
+from numpy.testing import (assert_array_almost_equal, assert_equal,
+                           assert_allclose)
 
+import mne
 from mne.datasets import testing
-from mne import read_cov, read_forward_solution, read_evokeds
+from mne import (read_cov, read_forward_solution, read_evokeds,
+                 convert_forward_solution)
 from mne.cov import regularize
 from mne.inverse_sparse import gamma_map
+from mne.inverse_sparse.mxne_inverse import make_stc_from_dipoles
 from mne import pick_types_forward
-from mne.utils import run_tests_if_main, slow_test
+from mne.utils import run_tests_if_main
+from mne.dipole import Dipole
 
 data_path = testing.data_path(download=False)
 fname_evoked = op.join(data_path, 'MEG', 'sample',
@@ -35,17 +41,28 @@ def _check_stc(stc, evoked, idx, ratio=50.):
     assert_true(amps[0] > ratio * amps[1], msg=str(amps[0] / amps[1]))
 
 
- at slow_test
+def _check_stcs(stc1, stc2):
+    """Helper to check correctness"""
+    assert_allclose(stc1.times, stc2.times)
+    assert_allclose(stc1.data, stc2.data)
+    assert_allclose(stc1.vertices[0], stc2.vertices[0])
+    assert_allclose(stc1.vertices[1], stc2.vertices[1])
+    assert_allclose(stc1.tmin, stc2.tmin)
+    assert_allclose(stc1.tstep, stc2.tstep)
+
+
+ at pytest.mark.slowtest
 @testing.requires_testing_data
 def test_gamma_map():
     """Test Gamma MAP inverse"""
-    forward = read_forward_solution(fname_fwd, force_fixed=False,
-                                    surf_ori=True)
+    forward = read_forward_solution(fname_fwd)
+    forward = convert_forward_solution(forward, surf_ori=True)
+
     forward = pick_types_forward(forward, meg=False, eeg=True)
     evoked = read_evokeds(fname_evoked, condition=0, baseline=(None, 0),
                           proj=False)
     evoked.resample(50, npad=100)
-    evoked.crop(tmin=0.1, tmax=0.16)  # crop to nice window near samp border
+    evoked.crop(tmin=0.1, tmax=0.16)  # crop to window around peak
 
     cov = read_cov(fname_cov)
     cov = regularize(cov, evoked.info)
@@ -59,11 +76,70 @@ def test_gamma_map():
                     xyz_same_gamma=False, update_mode=1)
     _check_stc(stc, evoked, 82010)
 
+    dips = gamma_map(evoked, forward, cov, alpha, tol=1e-4,
+                     xyz_same_gamma=False, update_mode=1,
+                     return_as_dipoles=True)
+    assert_true(isinstance(dips[0], Dipole))
+    stc_dip = make_stc_from_dipoles(dips, forward['src'])
+    _check_stcs(stc, stc_dip)
+
     # force fixed orientation
     stc = gamma_map(evoked, forward, cov, alpha, tol=1e-4,
                     xyz_same_gamma=False, update_mode=2,
-                    loose=None, return_residual=False)
+                    loose=0, return_residual=False)
     _check_stc(stc, evoked, 85739, 20)
 
 
+ at pytest.mark.slowtest
+ at testing.requires_testing_data
+def test_gamma_map_vol_sphere():
+    """Gamma MAP with a sphere forward and volumic source space"""
+    evoked = read_evokeds(fname_evoked, condition=0, baseline=(None, 0),
+                          proj=False)
+    evoked.resample(50, npad=100)
+    evoked.crop(tmin=0.1, tmax=0.16)  # crop to window around peak
+
+    cov = read_cov(fname_cov)
+    cov = regularize(cov, evoked.info)
+
+    info = evoked.info
+    sphere = mne.make_sphere_model(r0=(0., 0., 0.), head_radius=0.080)
+    src = mne.setup_volume_source_space(subject=None, pos=15., mri=None,
+                                        sphere=(0.0, 0.0, 0.0, 80.0),
+                                        bem=None, mindist=5.0,
+                                        exclude=2.0)
+    fwd = mne.make_forward_solution(info, trans=None, src=src, bem=sphere,
+                                    eeg=False, meg=True)
+
+    alpha = 0.5
+    assert_raises(ValueError, gamma_map, evoked, fwd, cov, alpha,
+                  loose=0, return_residual=False)
+
+    assert_raises(ValueError, gamma_map, evoked, fwd, cov, alpha,
+                  loose=0.2, return_residual=False)
+
+    stc = gamma_map(evoked, fwd, cov, alpha, tol=1e-4,
+                    xyz_same_gamma=False, update_mode=2,
+                    return_residual=False)
+
+    assert_array_almost_equal(stc.times, evoked.times, 5)
+
+    # Compare orientation obtained using fit_dipole and gamma_map
+    # for a simulated evoked containing a single dipole
+    stc = mne.VolSourceEstimate(50e-9 * np.random.RandomState(42).randn(1, 4),
+                                vertices=stc.vertices[:1],
+                                tmin=stc.tmin,
+                                tstep=stc.tstep)
+    evoked_dip = mne.simulation.simulate_evoked(fwd, stc, info, cov, nave=1e9,
+                                                use_cps=True)
+
+    dip_gmap = gamma_map(evoked_dip, fwd, cov, 0.1, return_as_dipoles=True)
+
+    amp_max = [np.max(d.amplitude) for d in dip_gmap]
+    dip_gmap = dip_gmap[np.argmax(amp_max)]
+    assert_true(dip_gmap[0].pos[0] in src[0]['rr'][stc.vertices])
+
+    dip_fit = mne.fit_dipole(evoked_dip, cov, sphere)[0]
+    assert_true(np.abs(np.dot(dip_fit.ori[0], dip_gmap.ori[0])) > 0.99)
+
 run_tests_if_main()
diff --git a/mne/inverse_sparse/tests/test_mxne_inverse.py b/mne/inverse_sparse/tests/test_mxne_inverse.py
index 29951d9..540c889 100644
--- a/mne/inverse_sparse/tests/test_mxne_inverse.py
+++ b/mne/inverse_sparse/tests/test_mxne_inverse.py
@@ -6,14 +6,20 @@
 import os.path as op
 import numpy as np
 from numpy.testing import assert_array_almost_equal, assert_allclose
-from nose.tools import assert_true, assert_equal
+from nose.tools import assert_true, assert_equal, assert_raises
+import pytest
 
+import mne
 from mne.datasets import testing
 from mne.label import read_label
-from mne import read_cov, read_forward_solution, read_evokeds
+from mne import (read_cov, read_forward_solution, read_evokeds,
+                 convert_forward_solution)
 from mne.inverse_sparse import mixed_norm, tf_mixed_norm
+from mne.inverse_sparse.mxne_inverse import make_stc_from_dipoles
 from mne.minimum_norm import apply_inverse, make_inverse_operator
-from mne.utils import run_tests_if_main, slow_test
+from mne.utils import run_tests_if_main
+from mne.dipole import Dipole
+from mne.source_estimate import VolSourceEstimate
 
 
 data_path = testing.data_path(download=False)
@@ -26,7 +32,17 @@ label = 'Aud-rh'
 fname_label = op.join(data_path, 'MEG', 'sample', 'labels', '%s.label' % label)
 
 
- at slow_test
+def _check_stcs(stc1, stc2):
+    """Helper to check correctness"""
+    assert_allclose(stc1.times, stc2.times)
+    assert_allclose(stc1.data, stc2.data)
+    assert_allclose(stc1.vertices[0], stc2.vertices[0])
+    assert_allclose(stc1.vertices[1], stc2.vertices[1])
+    assert_allclose(stc1.tmin, stc2.tmin)
+    assert_allclose(stc1.tstep, stc2.tstep)
+
+
+ at pytest.mark.slowtest
 @testing.requires_testing_data
 def test_mxne_inverse():
     """Test (TF-)MxNE inverse computation"""
@@ -34,7 +50,7 @@ def test_mxne_inverse():
     cov = read_cov(fname_cov)
 
     # Handling average file
-    loose = None
+    loose = 0.0
     depth = 0.9
 
     evoked = read_evokeds(fname_data, condition=0, baseline=(None, 0))
@@ -44,13 +60,13 @@ def test_mxne_inverse():
     evoked_l21.crop(tmin=0.081, tmax=0.1)
     label = read_label(fname_label)
 
-    forward = read_forward_solution(fname_fwd, force_fixed=False,
-                                    surf_ori=True)
+    forward = read_forward_solution(fname_fwd)
+    forward = convert_forward_solution(forward, surf_ori=True)
 
     # Reduce source space to make test computation faster
     inverse_operator = make_inverse_operator(evoked_l21.info, forward, cov,
                                              loose=loose, depth=depth,
-                                             fixed=True)
+                                             fixed=True, use_cps=True)
     stc_dspm = apply_inverse(evoked_l21, inverse_operator, lambda2=1. / 9.,
                              method='dSPM')
     stc_dspm.data[np.abs(stc_dspm.data) < 12] = 0.0
@@ -61,20 +77,19 @@ def test_mxne_inverse():
     alpha = 70  # spatial regularization parameter
 
     stc_prox = mixed_norm(evoked_l21, forward, cov, alpha, loose=loose,
-                          depth=depth, maxit=500, tol=1e-8,
+                          depth=depth, maxit=300, tol=1e-8,
                           active_set_size=10, weights=stc_dspm,
                           weights_min=weights_min, solver='prox')
     stc_cd = mixed_norm(evoked_l21, forward, cov, alpha, loose=loose,
-                        depth=depth, maxit=500, tol=1e-8, active_set_size=10,
+                        depth=depth, maxit=300, tol=1e-8, active_set_size=10,
                         weights=stc_dspm, weights_min=weights_min,
                         solver='cd')
     stc_bcd = mixed_norm(evoked_l21, forward, cov, alpha, loose=loose,
-                         depth=depth, maxit=500, tol=1e-8, active_set_size=10,
+                         depth=depth, maxit=300, tol=1e-8, active_set_size=10,
                          weights=stc_dspm, weights_min=weights_min,
                          solver='bcd')
     assert_array_almost_equal(stc_prox.times, evoked_l21.times, 5)
     assert_array_almost_equal(stc_cd.times, evoked_l21.times, 5)
-
     assert_array_almost_equal(stc_bcd.times, evoked_l21.times, 5)
     assert_allclose(stc_prox.data, stc_cd.data, rtol=1e-3, atol=0.0)
     assert_allclose(stc_prox.data, stc_bcd.data, rtol=1e-3, atol=0.0)
@@ -83,8 +98,16 @@ def test_mxne_inverse():
     assert_true(stc_cd.vertices[1][0] in label.vertices)
     assert_true(stc_bcd.vertices[1][0] in label.vertices)
 
+    dips = mixed_norm(evoked_l21, forward, cov, alpha, loose=loose,
+                      depth=depth, maxit=300, tol=1e-8, active_set_size=10,
+                      weights=stc_dspm, weights_min=weights_min,
+                      solver='cd', return_as_dipoles=True)
+    stc_dip = make_stc_from_dipoles(dips, forward['src'])
+    assert_true(isinstance(dips[0], Dipole))
+    _check_stcs(stc_cd, stc_dip)
+
     stc, _ = mixed_norm(evoked_l21, forward, cov, alpha, loose=loose,
-                        depth=depth, maxit=500, tol=1e-8,
+                        depth=depth, maxit=300, tol=1e-8,
                         active_set_size=10, return_residual=True,
                         solver='cd')
     assert_array_almost_equal(stc.times, evoked_l21.times, 5)
@@ -93,7 +116,7 @@ def test_mxne_inverse():
     # irMxNE tests
     stc = mixed_norm(evoked_l21, forward, cov, alpha,
                      n_mxne_iter=5, loose=loose, depth=depth,
-                     maxit=500, tol=1e-8, active_set_size=10,
+                     maxit=300, tol=1e-8, active_set_size=10,
                      solver='cd')
     assert_array_almost_equal(stc.times, evoked_l21.times, 5)
     assert_true(stc.vertices[1][0] in label.vertices)
@@ -111,4 +134,71 @@ def test_mxne_inverse():
     assert_true(stc.vertices[1][0] in label.vertices)
 
 
+ at pytest.mark.slowtest
+ at testing.requires_testing_data
+def test_mxne_vol_sphere():
+    """(TF-)MxNE with a sphere forward and volumic source space"""
+    evoked = read_evokeds(fname_data, condition=0, baseline=(None, 0))
+    evoked.crop(tmin=-0.05, tmax=0.2)
+    cov = read_cov(fname_cov)
+
+    evoked_l21 = evoked.copy()
+    evoked_l21.crop(tmin=0.081, tmax=0.1)
+
+    info = evoked.info
+    sphere = mne.make_sphere_model(r0=(0., 0., 0.), head_radius=0.080)
+    src = mne.setup_volume_source_space(subject=None, pos=15., mri=None,
+                                        sphere=(0.0, 0.0, 0.0, 80.0),
+                                        bem=None, mindist=5.0,
+                                        exclude=2.0)
+    fwd = mne.make_forward_solution(info, trans=None, src=src,
+                                    bem=sphere, eeg=False, meg=True)
+
+    alpha = 80.
+    assert_raises(ValueError, mixed_norm, evoked, fwd, cov, alpha,
+                  loose=0.0, return_residual=False,
+                  maxit=3, tol=1e-8, active_set_size=10)
+
+    assert_raises(ValueError, mixed_norm, evoked, fwd, cov, alpha,
+                  loose=0.2, return_residual=False,
+                  maxit=3, tol=1e-8, active_set_size=10)
+
+    # irMxNE tests
+    stc = mixed_norm(evoked_l21, fwd, cov, alpha,
+                     n_mxne_iter=1, maxit=30, tol=1e-8,
+                     active_set_size=10)
+    assert_true(isinstance(stc, VolSourceEstimate))
+    assert_array_almost_equal(stc.times, evoked_l21.times, 5)
+
+    # Compare orientation obtained using fit_dipole and gamma_map
+    # for a simulated evoked containing a single dipole
+    stc = mne.VolSourceEstimate(50e-9 * np.random.RandomState(42).randn(1, 4),
+                                vertices=stc.vertices[:1],
+                                tmin=stc.tmin,
+                                tstep=stc.tstep)
+    evoked_dip = mne.simulation.simulate_evoked(fwd, stc, info, cov, nave=1e9,
+                                                use_cps=True)
+
+    dip_mxne = mixed_norm(evoked_dip, fwd, cov, alpha=80,
+                          n_mxne_iter=1, maxit=30, tol=1e-8,
+                          active_set_size=10, return_as_dipoles=True)
+
+    amp_max = [np.max(d.amplitude) for d in dip_mxne]
+    dip_mxne = dip_mxne[np.argmax(amp_max)]
+    assert_true(dip_mxne.pos[0] in src[0]['rr'][stc.vertices])
+
+    dip_fit = mne.fit_dipole(evoked_dip, cov, sphere)[0]
+    assert_true(np.abs(np.dot(dip_fit.ori[0], dip_mxne.ori[0])) > 0.99)
+
+    # Do with TF-MxNE for test memory savings
+    alpha_space = 60.  # spatial regularization parameter
+    alpha_time = 1.  # temporal regularization parameter
+
+    stc, _ = tf_mixed_norm(evoked, fwd, cov, alpha_space, alpha_time,
+                           maxit=3, tol=1e-4,
+                           tstep=16, wsize=32, window=0.1,
+                           return_residual=True)
+    assert_true(isinstance(stc, VolSourceEstimate))
+    assert_array_almost_equal(stc.times, evoked.times, 5)
+
 run_tests_if_main()
diff --git a/mne/inverse_sparse/tests/test_mxne_optim.py b/mne/inverse_sparse/tests/test_mxne_optim.py
index ba49be7..acb0dc8 100644
--- a/mne/inverse_sparse/tests/test_mxne_optim.py
+++ b/mne/inverse_sparse/tests/test_mxne_optim.py
@@ -5,8 +5,8 @@
 
 import numpy as np
 import warnings
-from numpy.testing import assert_array_equal, assert_array_almost_equal
-from numpy.testing import assert_allclose
+from numpy.testing import (assert_array_equal, assert_array_almost_equal,
+                           assert_allclose, assert_array_less)
 
 from mne.inverse_sparse.mxne_optim import (mixed_norm_solver,
                                            tf_mixed_norm_solver,
@@ -48,13 +48,15 @@ def test_l21_mxne():
         *args, active_set_size=None,
         debias=True, solver='prox')
     assert_array_equal(np.where(active_set)[0], [0, 4])
-    X_hat_cd, active_set, _ = mixed_norm_solver(
+    X_hat_cd, active_set, _, gap_cd = mixed_norm_solver(
         *args, active_set_size=None,
-        debias=True, solver='cd')
+        debias=True, solver='cd', return_gap=True)
+    assert_array_less(gap_cd, 1e-8)
     assert_array_equal(np.where(active_set)[0], [0, 4])
-    X_hat_bcd, active_set, _ = mixed_norm_solver(
+    X_hat_bcd, active_set, E, gap_bcd = mixed_norm_solver(
         M, G, alpha, maxit=1000, tol=1e-8, active_set_size=None,
-        debias=True, solver='bcd')
+        debias=True, solver='bcd', return_gap=True)
+    assert_array_less(gap_bcd, 9.6e-9)
     assert_array_equal(np.where(active_set)[0], [0, 4])
     assert_allclose(X_hat_prox, X_hat_cd, rtol=1e-2)
     assert_allclose(X_hat_prox, X_hat_bcd, rtol=1e-2)
@@ -109,9 +111,10 @@ def test_tf_mxne():
 
     M, G, active_set = _generate_tf_data()
 
-    X_hat_tf, active_set_hat_tf, E = tf_mixed_norm_solver(
+    X_hat_tf, active_set_hat_tf, E, gap_tfmxne = tf_mixed_norm_solver(
         M, G, alpha_space, alpha_time, maxit=200, tol=1e-8, verbose=True,
-        n_orient=1, tstep=4, wsize=32)
+        n_orient=1, tstep=4, wsize=32, return_gap=True)
+    assert_array_less(gap_tfmxne, 1e-8)
     assert_array_equal(np.where(active_set_hat_tf)[0], active_set)
 
 
diff --git a/mne/io/__init__.py b/mne/io/__init__.py
index 6083377..2b4dab3 100644
--- a/mne/io/__init__.py
+++ b/mne/io/__init__.py
@@ -1,4 +1,4 @@
-"""FIF module for IO with .fif files"""
+"""IO module for reading raw data."""
 
 # Authors: Alexandre Gramfort <alexandre.gramfort at telecom-paristech.fr>
 #          Matti Hamalainen <msh at nmr.mgh.harvard.edu>
@@ -8,11 +8,11 @@
 from .open import fiff_open, show_fiff, _fiff_get_fid
 from .meas_info import (read_fiducials, write_fiducials, read_info, write_info,
                         _empty_info, _merge_info, _force_update_info, Info,
-                        anonymize_info)
+                        anonymize_info, _stamp_to_dt)
 
 from .proj import make_eeg_average_ref_proj, Projection
 from .tag import _loc_to_coil_trans, _coil_trans_to_loc, _loc_to_eeg_loc
-from .base import _BaseRaw
+from .base import BaseRaw
 
 from . import array
 from . import base
@@ -34,12 +34,13 @@ from .brainvision import read_raw_brainvision
 from .bti import read_raw_bti
 from .cnt import read_raw_cnt
 from .ctf import read_raw_ctf
-from .edf import read_raw_edf
+from .edf import read_raw_edf, find_edf_events
 from .egi import read_raw_egi
 from .kit import read_raw_kit, read_epochs_kit
 from .fiff import read_raw_fif
 from .nicolet import read_raw_nicolet
-from .eeglab import read_raw_eeglab, read_epochs_eeglab
+from .artemis123 import read_raw_artemis123
+from .eeglab import read_raw_eeglab, read_epochs_eeglab, read_events_eeglab
 
 # for backward compatibility
 from .fiff import Raw
diff --git a/mne/io/array/__init__.py b/mne/io/array/__init__.py
index 112d5d8..35778e4 100644
--- a/mne/io/array/__init__.py
+++ b/mne/io/array/__init__.py
@@ -1,4 +1,4 @@
-"""Module to convert user data to FIF"""
+"""Module to convert user data to FIF."""
 
 # Author: Eric Larson <larson.eric.d at gmail.com>
 
diff --git a/mne/io/array/array.py b/mne/io/array/array.py
index c9214f0..aa55141 100644
--- a/mne/io/array/array.py
+++ b/mne/io/array/array.py
@@ -1,4 +1,4 @@
-"""Tools for creating Raw objects from numpy arrays"""
+"""Tools for creating Raw objects from numpy arrays."""
 
 # Authors: Eric Larson <larson.eric.d at gmail.com>
 #
@@ -6,20 +6,20 @@
 
 import numpy as np
 
-from ..base import _BaseRaw
+from ..base import BaseRaw
 from ..meas_info import Info
 from ...utils import verbose, logger
 
 
-class RawArray(_BaseRaw):
-    """Raw object from numpy array
+class RawArray(BaseRaw):
+    """Raw object from numpy array.
 
     Parameters
     ----------
     data : array, shape (n_channels, n_times)
-        The channels' time series.
+        The channels' time series. See notes for proper units of measure.
     info : instance of Info
-        Info dictionary. Consider using `create_info` to populate
+        Info dictionary. Consider using :func:`mne.create_info` to populate
         this structure. This may be modified in place by the class.
     first_samp : int
         First sample offset used during recording (default 0).
@@ -27,14 +27,28 @@ class RawArray(_BaseRaw):
         .. versionadded:: 0.12
 
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
+
+    Notes
+    -----
+    Proper units of measure:
+    * V: eeg, eog, seeg, emg, ecg, bio, ecog
+    * T: mag
+    * T/m: grad
+    * M: hbo, hbr
+    * Am: dipole
+    * AU: misc
 
     See Also
     --------
-    EpochsArray, EvokedArray, create_info
+    mne.EpochsArray
+    mne.EvokedArray
+    mne.create_info
     """
+
     @verbose
-    def __init__(self, data, info, first_samp=0, verbose=None):
+    def __init__(self, data, info, first_samp=0, verbose=None):  # noqa: D102
         if not isinstance(info, Info):
             raise TypeError('info must be an instance of Info, got %s'
                             % type(info))
@@ -53,6 +67,7 @@ class RawArray(_BaseRaw):
         assert len(info['ch_names']) == info['nchan']
         if info.get('buffer_size_sec', None) is None:
             info['buffer_size_sec'] = 1.  # reasonable default
+        info = info.copy()  # do not modify original info
         super(RawArray, self).__init__(info, data,
                                        first_samps=(int(first_samp),),
                                        dtype=dtype, verbose=verbose)
diff --git a/mne/io/array/tests/test_array.py b/mne/io/array/tests/test_array.py
index 0a50388..ed62f8e 100644
--- a/mne/io/array/tests/test_array.py
+++ b/mne/io/array/tests/test_array.py
@@ -9,13 +9,14 @@ import matplotlib
 import numpy as np
 from numpy.testing import assert_array_almost_equal, assert_allclose
 from nose.tools import assert_equal, assert_raises, assert_true
+import pytest
 
-from mne import find_events, Epochs, pick_types
+from mne import find_events, Epochs, pick_types, channels
 from mne.io import read_raw_fif
 from mne.io.array import RawArray
 from mne.io.tests.test_raw import _test_raw_reader
 from mne.io.meas_info import create_info, _kind_dict
-from mne.utils import slow_test, requires_version, run_tests_if_main
+from mne.utils import requires_version, run_tests_if_main
 
 matplotlib.use('Agg')  # for testing don't use X server
 
@@ -25,14 +26,25 @@ base_dir = op.join(op.dirname(__file__), '..', '..', 'tests', 'data')
 fif_fname = op.join(base_dir, 'test_raw.fif')
 
 
- at slow_test
+def test_long_names():
+    """Test long name support."""
+    info = create_info(['a' * 15 + 'b', 'a' * 16], 1000., verbose='error')
+    data = np.empty((2, 1000))
+    raw = RawArray(data, info)
+    assert raw.ch_names == ['a' * 13 + '-0', 'a' * 13 + '-1']
+    info = create_info(['a' * 16] * 11, 1000., verbose='error')
+    data = np.empty((11, 1000))
+    raw = RawArray(data, info)
+    assert raw.ch_names == ['a' * 12 + '-%s' % ii for ii in range(11)]
+
+
+ at pytest.mark.slowtest
 @requires_version('scipy', '0.12')
 def test_array_raw():
-    """Test creating raw from array
-    """
+    """Test creating raw from array."""
     import matplotlib.pyplot as plt
     # creating
-    raw = read_raw_fif(fif_fname, add_eeg_ref=False).crop(2, 5)
+    raw = read_raw_fif(fif_fname).crop(2, 5)
     data, times = raw[:, :]
     sfreq = raw.info['sfreq']
     ch_names = [(ch[4:] if 'STI' not in ch else ch)
@@ -67,21 +79,16 @@ def test_array_raw():
     picks = pick_types(raw2.info, misc=True, exclude='bads')[:4]
     assert_equal(len(picks), 4)
     raw_lp = raw2.copy()
-    raw_lp.filter(None, 4.0, h_trans_bandwidth=4.,
-                  filter_length='auto', picks=picks, n_jobs=2, phase='zero',
-                  fir_window='hamming')
+    kwargs = dict(fir_design='firwin', picks=picks)
+    raw_lp.filter(None, 4.0, h_trans_bandwidth=4., n_jobs=2, **kwargs)
     raw_hp = raw2.copy()
-    raw_hp.filter(16.0, None, l_trans_bandwidth=4.,
-                  filter_length='auto', picks=picks, n_jobs=2, phase='zero',
-                  fir_window='hamming')
+    raw_hp.filter(16.0, None, l_trans_bandwidth=4., n_jobs=2, **kwargs)
     raw_bp = raw2.copy()
-    raw_bp.filter(8.0, 12.0, l_trans_bandwidth=4.,
-                  h_trans_bandwidth=4., filter_length='auto', picks=picks,
-                  phase='zero', fir_window='hamming')
+    raw_bp.filter(8.0, 12.0, l_trans_bandwidth=4., h_trans_bandwidth=4.,
+                  **kwargs)
     raw_bs = raw2.copy()
     raw_bs.filter(16.0, 4.0, l_trans_bandwidth=4., h_trans_bandwidth=4.,
-                  filter_length='auto', picks=picks, n_jobs=2, phase='zero',
-                  fir_window='hamming')
+                  n_jobs=2, **kwargs)
     data, _ = raw2[picks, :]
     lp_data, _ = raw_lp[picks, :]
     hp_data, _ = raw_hp[picks, :]
@@ -93,15 +100,14 @@ def test_array_raw():
 
     # plotting
     raw2.plot()
-    raw2.plot_psd()
+    raw2.plot_psd(tmax=np.inf, average=True, n_fft=1024, spatial_colors=False)
     plt.close('all')
 
     # epoching
     events = find_events(raw2, stim_channel='STI 014')
     events[:, 2] = 1
     assert_true(len(events) > 2)
-    epochs = Epochs(raw2, events, 1, -0.2, 0.4, preload=True,
-                    add_eeg_ref=False)
+    epochs = Epochs(raw2, events, 1, -0.2, 0.4, preload=True)
     epochs.plot_drop_log()
     epochs.plot()
     evoked = epochs.average()
@@ -115,4 +121,21 @@ def test_array_raw():
     raw = RawArray(data, create_info(1, 1000., 'eeg'))
     assert_allclose(raw._data, data)
 
+    # Using digital montage to give MNI electrode coordinates
+    n_elec = 10
+    ts_size = 10000
+    Fs = 512.
+    elec_labels = [str(i) for i in range(n_elec)]
+    elec_coords = np.random.randint(60, size=(n_elec, 3)).tolist()
+
+    electrode = np.random.rand(n_elec, ts_size)
+    dig_ch_pos = dict(zip(elec_labels, elec_coords))
+    mon = channels.DigMontage(dig_ch_pos=dig_ch_pos)
+    info = create_info(elec_labels, Fs, 'ecog', montage=mon)
+
+    raw = RawArray(electrode, info)
+    raw.plot_psd(average=False)  # looking for inexistent layout
+    raw.plot_psd_topo()
+
+
 run_tests_if_main()
diff --git a/mne/io/artemis123/__init__.py b/mne/io/artemis123/__init__.py
new file mode 100644
index 0000000..5429cb8
--- /dev/null
+++ b/mne/io/artemis123/__init__.py
@@ -0,0 +1,7 @@
+"""artemis123 module for conversion to FIF."""
+
+# Author: Luke Bloy <bloyl at chop.edu>
+#
+# License: BSD (3-clause)
+
+from .artemis123 import read_raw_artemis123
diff --git a/mne/io/artemis123/artemis123.py b/mne/io/artemis123/artemis123.py
new file mode 100644
index 0000000..4ea1db7
--- /dev/null
+++ b/mne/io/artemis123/artemis123.py
@@ -0,0 +1,440 @@
+# Author: Luke Bloy <bloyl at chop.edu>
+#
+# License: BSD (3-clause)
+
+import numpy as np
+import os.path as op
+import datetime
+import calendar
+
+from .utils import _load_mne_locs, _read_pos
+from ...utils import logger, warn, verbose
+from ..utils import _read_segments_file
+from ..base import BaseRaw
+from ..meas_info import _empty_info, _make_dig_points
+from ..constants import FIFF
+from ...chpi import _fit_device_hpi_positions, _fit_coil_order_dev_head_trans
+from ...transforms import get_ras_to_neuromag_trans, apply_trans, Transform
+
+
+ at verbose
+def read_raw_artemis123(input_fname, preload=False, verbose=None,
+                        pos_fname=None, add_head_trans=True):
+    """Read Artemis123 data as raw object.
+
+    Parameters
+    ----------
+    input_fname : str
+        Path to the data file (extension ``.bin``). The header file with the
+        same file name stem and an extension ``.txt`` is expected to be found
+        in the same directory.
+    preload : bool or str (default False)
+        Preload data into memory for data manipulation and faster indexing.
+        If True, the data will be preloaded into memory (fast, requires
+        large amount of memory). If preload is a string, preload is the
+        file name of a memory-mapped file which is used to store the data
+        on the hard drive (slower, requires less memory).
+    verbose : bool, str, int, or None
+        If not None, override default verbose level (see mne.verbose).
+    pos_fname : str or None (default None)
+        If not None, load digitized head points from this file
+    add_head_trans : bool (default True)
+        If True attempt to perform initial head localization. Compute initial
+        device to head coordinate transform using HPI coils. If no
+        HPI coils are in info['dig'] hpi coils are assumed to be in canonical
+        order of fiducial points (nas, rpa, lpa).
+
+    Returns
+    -------
+    raw : Instance of Raw
+        A Raw object containing the data.
+
+    See Also
+    --------
+    mne.io.Raw : Documentation of attribute and methods.
+    """
+    return RawArtemis123(input_fname, preload=preload, verbose=verbose,
+                         pos_fname=pos_fname, add_head_trans=add_head_trans)
+
+
+def _get_artemis123_info(fname, pos_fname=None):
+    """Generate info struct from artemis123 header file."""
+    fname = op.splitext(fname)[0]
+    header = fname + '.txt'
+
+    logger.info('Reading header...')
+
+    # key names for artemis channel info...
+    chan_keys = ['name', 'scaling', 'FLL_Gain', 'FLL_Mode', 'FLL_HighPass',
+                 'FLL_AutoReset', 'FLL_ResetLock']
+
+    header_info = dict()
+    header_info['filter_hist'] = []
+    header_info['comments'] = ''
+    header_info['channels'] = []
+
+    with open(header, 'r') as fid:
+        # section flag
+        # 0 - None
+        # 1 - main header
+        # 2 - channel header
+        # 3 - comments
+        # 4 - length
+        # 5 - filtering History
+        sectionFlag = 0
+        for line in fid:
+            # skip emptylines or header line for channel info
+            if ((not line.strip()) or
+               (sectionFlag == 2 and line.startswith('DAQ Map'))):
+                continue
+
+            # set sectionFlag
+            if line.startswith('<end'):
+                sectionFlag = 0
+            elif line.startswith("<start main header>"):
+                sectionFlag = 1
+            elif line.startswith("<start per channel header>"):
+                sectionFlag = 2
+            elif line.startswith("<start comments>"):
+                sectionFlag = 3
+            elif line.startswith("<start length>"):
+                sectionFlag = 4
+            elif line.startswith("<start filtering history>"):
+                sectionFlag = 5
+            else:
+                # parse header info lines
+                # part of main header - lines are name value pairs
+                if sectionFlag == 1:
+                    values = line.strip().split('\t')
+                    if len(values) == 1:
+                        values.append('')
+                    header_info[values[0]] = values[1]
+                # part of channel header - lines are Channel Info
+                elif sectionFlag == 2:
+                    values = line.strip().split('\t')
+                    if len(values) != 7:
+                        raise IOError('Error parsing line \n\t:%s\n' % line +
+                                      'from file %s' % header)
+                    tmp = dict()
+                    for k, v in zip(chan_keys, values):
+                        tmp[k] = v
+                    header_info['channels'].append(tmp)
+                elif sectionFlag == 3:
+                    header_info['comments'] = '%s%s' \
+                        % (header_info['comments'], line.strip())
+                elif sectionFlag == 4:
+                    header_info['num_samples'] = int(line.strip())
+                elif sectionFlag == 5:
+                    header_info['filter_hist'].append(line.strip())
+
+    for k in ['Temporal Filter Active?', 'Decimation Active?',
+              'Spatial Filter Active?']:
+        if(header_info[k] != 'FALSE'):
+            warn('%s - set to but is not supported' % k)
+    if(header_info['filter_hist']):
+        warn('Non-Empty Filter histroy found, BUT is not supported' % k)
+
+    # build mne info struct
+    info = _empty_info(float(header_info['DAQ Sample Rate']))
+
+    # Attempt to get time/date from fname
+    # Artemis123 files saved from the scanner observe the following
+    # naming convention 'Artemis_Data_YYYY-MM-DD-HHh-MMm_[chosen by user].bin'
+    try:
+        date = datetime.datetime.strptime(
+            op.basename(fname).split('_')[2], '%Y-%m-%d-%Hh-%Mm')
+        meas_date = calendar.timegm(date.utctimetuple())
+    except Exception:
+        meas_date = None
+
+    # build subject info must be an integer (as per FIFF)
+    try:
+        subject_info = {'id': int(header_info['Subject ID'])}
+    except:
+        subject_info = {'id': 0}
+
+    # build description
+    desc = ''
+    for k in ['Purpose', 'Notes']:
+        desc += '{} : {}\n'.format(k, header_info[k])
+    desc += 'Comments : {}'.format(header_info['comments'])
+
+    info.update({'meas_date': meas_date,
+                 'description': desc, 'buffer_size_sec': 1.,
+                 'subject_info': subject_info,
+                 'proj_name': header_info['Project Name']})
+
+    # Channel Names by type
+    ref_mag_names = ['REF_001', 'REF_002', 'REF_003',
+                     'REF_004', 'REF_005', 'REF_006']
+
+    ref_grad_names = ['REF_007', 'REF_008', 'REF_009',
+                      'REF_010', 'REF_011', 'REF_012']
+
+    # load mne loc dictionary
+    loc_dict = _load_mne_locs()
+    info['chs'] = []
+    info['bads'] = []
+
+    for i, chan in enumerate(header_info['channels']):
+        # build chs struct
+        t = {'cal': float(chan['scaling']), 'ch_name': chan['name'],
+             'logno': i + 1, 'scanno': i + 1, 'range': 1.0,
+             'unit_mul': FIFF.FIFF_UNITM_NONE,
+             'coord_frame': FIFF.FIFFV_COORD_DEVICE}
+        # REF_018 has a zero cal which can cause problems. Let's set it to
+        # a value of another ref channel to make writers/readers happy.
+        if t['cal'] == 0:
+            t['cal'] = 4.716e-10
+            info['bads'].append(t['ch_name'])
+        t['loc'] = loc_dict.get(chan['name'], np.zeros(12))
+
+        if (chan['name'].startswith('MEG')):
+            t['coil_type'] = FIFF.FIFFV_COIL_ARTEMIS123_GRAD
+            t['kind'] = FIFF.FIFFV_MEG_CH
+            # While gradiometer units are T/m, the meg sensors referred to as
+            # gradiometers report the field difference between 2 pick-up coils.
+            # Therefore the units of the measurements should be T
+            # *AND* the baseline (difference between pickup coils)
+            # should not be used in leadfield / forwardfield computations.
+            t['unit'] = FIFF.FIFF_UNIT_T
+            t['unit_mul'] = FIFF.FIFF_UNITM_F
+
+        # 3 axis referance magnetometers
+        elif (chan['name'] in ref_mag_names):
+            t['coil_type'] = FIFF.FIFFV_COIL_ARTEMIS123_REF_MAG
+            t['kind'] = FIFF.FIFFV_REF_MEG_CH
+            t['unit'] = FIFF.FIFF_UNIT_T
+            t['unit_mul'] = FIFF.FIFF_UNITM_F
+
+        # reference gradiometers
+        elif (chan['name'] in ref_grad_names):
+            t['coil_type'] = FIFF.FIFFV_COIL_ARTEMIS123_REF_GRAD
+            t['kind'] = FIFF.FIFFV_REF_MEG_CH
+            # While gradiometer units are T/m, the meg sensors referred to as
+            # gradiometers report the field difference between 2 pick-up coils.
+            # Therefore the units of the measurements should be T
+            # *AND* the baseline (difference between pickup coils)
+            # should not be used in leadfield / forwardfield computations.
+            t['unit'] = FIFF.FIFF_UNIT_T
+            t['unit_mul'] = FIFF.FIFF_UNITM_F
+
+        # other reference channels are unplugged and should be ignored.
+        elif (chan['name'].startswith('REF')):
+            t['coil_type'] = FIFF.FIFFV_COIL_NONE
+            t['kind'] = FIFF.FIFFV_MISC_CH
+            t['unit'] = FIFF.FIFF_UNIT_V
+            info['bads'].append(t['ch_name'])
+
+        elif (chan['name'].startswith(('AUX', 'TRG', 'MIO'))):
+            t['coil_type'] = FIFF.FIFFV_COIL_NONE
+            t['unit'] = FIFF.FIFF_UNIT_V
+            if (chan['name'].startswith('TRG')):
+                t['kind'] = FIFF.FIFFV_STIM_CH
+            else:
+                t['kind'] = FIFF.FIFFV_MISC_CH
+        else:
+            raise ValueError('Channel does not match expected' +
+                             ' channel Types:"%s"' % chan['name'])
+
+        # incorporate mulitplier (unit_mul) into calibration
+        t['cal'] *= 10 ** t['unit_mul']
+        t['unit_mul'] = FIFF.FIFF_UNITM_NONE
+
+        # append this channel to the info
+        info['chs'].append(t)
+        if chan['FLL_ResetLock'] == 'TRUE':
+            info['bads'].append(t['ch_name'])
+
+    # reduce info['bads'] to unique set
+    info['bads'] = list(set(info['bads']))
+
+    # HPI information
+    # print header_info.keys()
+    hpi_sub = dict()
+    # Don't know what event_channel is don't think we have it HPIs are either
+    # always on or always off.
+    # hpi_sub['event_channel'] = ???
+    hpi_sub['hpi_coils'] = [dict(), dict(), dict(), dict()]
+    hpi_coils = [dict(), dict(), dict(), dict()]
+    drive_channels = ['MIO_001', 'MIO_003', 'MIO_009', 'MIO_011']
+    key_base = 'Head Tracking %s %d'
+
+    # set default HPI frequencies
+    if info['sfreq'] == 1000:
+        default_freqs = [140, 150, 160, 40]
+    else:
+        default_freqs = [700, 750, 800, 40]
+
+    for i in range(4):
+        # build coil structure
+        hpi_coils[i]['number'] = i + 1
+        hpi_coils[i]['drive_chan'] = drive_channels[i]
+        this_freq = header_info.pop(key_base % ('Frequency', i + 1),
+                                    default_freqs[i])
+        hpi_coils[i]['coil_freq'] = this_freq
+
+        # check if coil is on
+        if header_info[key_base % ('Channel', i + 1)] == 'OFF':
+            hpi_sub['hpi_coils'][i]['event_bits'] = [0]
+        else:
+            hpi_sub['hpi_coils'][i]['event_bits'] = [256]
+
+    info['hpi_subsystem'] = hpi_sub
+    info['hpi_meas'] = [{'hpi_coils': hpi_coils}]
+
+    # read in digitized points if supplied
+    if pos_fname is not None:
+        info['dig'] = _read_pos(pos_fname)
+    else:
+        info['dig'] = []
+
+    info._update_redundant()
+    return info, header_info
+
+
+class RawArtemis123(BaseRaw):
+    """Raw object from Artemis123 file.
+
+    Parameters
+    ----------
+    input_fname : str
+        Path to the Artemis123 data file (ending in ``'.bin'``).
+    preload : bool or str (default False)
+        Preload data into memory for data manipulation and faster indexing.
+        If True, the data will be preloaded into memory (fast, requires
+        large amount of memory). If preload is a string, preload is the
+        file name of a memory-mapped file which is used to store the data
+        on the hard drive (slower, requires less memory).
+    verbose : bool, str, int, or None
+        If not None, override default verbose level (see mne.verbose).
+
+    See Also
+    --------
+    mne.io.Raw : Documentation of attribute and methods.
+    """
+
+    def __init__(self, input_fname, preload=False, verbose=None,
+                 pos_fname=None, add_head_trans=True):  # noqa: D102
+        from scipy.spatial.distance import cdist
+
+        fname, ext = op.splitext(input_fname)
+        if ext == '.txt':
+            input_fname = fname + '.bin'
+        elif ext != '.bin':
+            raise RuntimeError('Valid artemis123 files must end in "txt"' +
+                               ' or ".bin".')
+
+        if not op.exists(input_fname):
+            raise RuntimeError('%s - Not Found' % input_fname)
+
+        info, header_info = _get_artemis123_info(input_fname,
+                                                 pos_fname=pos_fname)
+
+        last_samps = [header_info.get('num_samples', 1) - 1]
+
+        super(RawArtemis123, self).__init__(
+            info, preload, filenames=[input_fname], raw_extras=[header_info],
+            last_samps=last_samps, orig_format=np.float32,
+            verbose=verbose)
+        self.info['hpi_results'] = []
+
+        if add_head_trans:
+            n_hpis = 0
+            for d in info['hpi_subsystem']['hpi_coils']:
+                if d['event_bits'] == [256]:
+                    n_hpis += 1
+            if n_hpis < 3:
+                warn('%d HPIs active. At least 3 needed to perform' % n_hpis +
+                     'head localization\n *NO* head localization performed')
+            else:
+                # Localized HPIs using the 1st seconds of data.
+                hpi_dev, hpi_g = _fit_device_hpi_positions(self,
+                                                           t_win=[0, 0.25])
+                if pos_fname is not None:
+                    logger.info('No Digitized cHPI locations found.\n' +
+                                'Assuming cHPIs are placed at cardinal ' +
+                                'fiducial locations. (Nasion, LPA, RPA')
+
+                    # Digitized HPI points are needed.
+                    hpi_head = np.array([d['r']
+                                         for d in self.info.get('dig', [])
+                                         if d['kind'] == FIFF.FIFFV_POINT_HPI])
+
+                    if (len(hpi_head) != len(hpi_dev)):
+                        mesg = ("number of digitized (%d) and " +
+                                "active (%d) HPI coils are " +
+                                "not the same.")
+                        raise RuntimeError(mesg % (len(hpi_head),
+                                                   len(hpi_dev)))
+
+                    # compute initial head to dev transform and hpi ordering
+                    head_to_dev_t, order = \
+                        _fit_coil_order_dev_head_trans(hpi_dev, hpi_head)
+
+                    # set the device to head transform
+                    self.info['dev_head_t'] = \
+                        Transform(FIFF.FIFFV_COORD_DEVICE,
+                                  FIFF.FIFFV_COORD_HEAD, head_to_dev_t)
+
+                    dig_dists = cdist(hpi_head, hpi_head)
+                    dev_dists = cdist(hpi_dev, hpi_dev)
+                    tmp_dists = np.abs(dig_dists - dev_dists)
+                    dist_limit = tmp_dists.max() * 1.1
+
+                else:
+                    logger.info('Assuming Cardinal HPIs')
+                    nas = hpi_dev[0]
+                    lpa = hpi_dev[2]
+                    rpa = hpi_dev[1]
+                    t = get_ras_to_neuromag_trans(nas, lpa, rpa)
+                    self.info['dev_head_t'] = \
+                        Transform(FIFF.FIFFV_COORD_DEVICE,
+                                  FIFF.FIFFV_COORD_HEAD, t)
+
+                    # transform fiducial points
+                    nas = apply_trans(t, nas)
+                    lpa = apply_trans(t, lpa)
+                    rpa = apply_trans(t, rpa)
+
+                    hpi = [nas, rpa, lpa]
+                    self.info['dig'] = _make_dig_points(nasion=nas, lpa=lpa,
+                                                        rpa=rpa, hpi=hpi)
+                    order = np.array([0, 1, 2])
+                    dist_limit = 0.005
+
+                # fill in hpi_results
+                hpi_result = dict()
+
+                # add HPI points in device coords...
+                dig = []
+                for idx, point in enumerate(hpi_dev):
+                    dig.append({'r': point, 'ident': idx + 1,
+                                'kind': FIFF.FIFFV_POINT_HPI,
+                                'coord_frame': FIFF.FIFFV_COORD_DEVICE})
+                hpi_result['dig_points'] = dig
+
+                # attach Transform
+                hpi_result['coord_trans'] = self.info['dev_head_t']
+
+                # 1 based indexing
+                hpi_result['order'] = order + 1
+                hpi_result['used'] = np.arange(3) + 1
+                hpi_result['dist_limit'] = dist_limit
+                hpi_result['good_limit'] = 0.98
+
+                # Warn for large discrepencies between digitized and fit
+                # cHPI locations
+                if hpi_result['dist_limit'] > 0.005:
+                    warn('Large difference between digitized geometry' +
+                         ' and HPI geometry. Max coil to coil difference' +
+                         ' is %0.2f cm\n' % (100. * tmp_dists.max()) +
+                         'beware of *POOR* head localization')
+
+                # store it
+                self.info['hpi_results'] = [hpi_result]
+
+    def _read_segment_file(self, data, idx, fi, start, stop, cals, mult):
+        """Read a chunk of raw data."""
+        _read_segments_file(self, data, idx, fi, start,
+                            stop, cals, mult, dtype='>f4')
diff --git a/mne/io/artemis123/resources/Artemis123_ChannelMap.csv b/mne/io/artemis123/resources/Artemis123_ChannelMap.csv
new file mode 100644
index 0000000..1ee9325
--- /dev/null
+++ b/mne/io/artemis123/resources/Artemis123_ChannelMap.csv
@@ -0,0 +1,146 @@
+name,Channel Type,CAD X+ (INCH),CAD Y+ (INCH),CAD Z+ (INCH),CAD X- (INCH),CAD Y- (INCH),CAD Z- (INCH)
+Derived from '90-0395 Channel Map for 6th cooldown  2-01-13.xls',,,,,,,
+MEG_059,MEG_GRAD,-1.97677,1.56552,2.91489,-4.18768,2.50074,5.40664
+MEG_045,MEG_GRAD,-1.61144,0.93037,3.41137,-3.33479,1.92534,6.24186
+MEG_029,MEG_GRAD,-0.91075,1.72387,3.473,-1.93587,2.72988,6.62081
+MEG_073,MEG_GRAD,-2.38955,0.86972,2.76491,-4.94504,1.79985,4.90406
+MEG_043,MEG_GRAD,-1.59926,2.33243,2.93122,-3.46787,3.39595,5.64209
+MEG_085,MEG_GRAD,-2.78631,1.40783,1.84839,-5.89386,2.21359,3.13893
+REF_013,UNUSED,,,,,,
+MEG_071,MEG_GRAD,-2.43321,2.17533,2.12153,-5.27622,3.05529,3.88634
+MEG_032,MEG_GRAD,0.93037,-1.61144,3.41137,1.92534,-3.33479,6.24186
+MEG_048,MEG_GRAD,1.27145,-2.20222,2.76491,2.6312,-4.55737,4.90406
+MEG_018,MEG_GRAD,0.44157,-2.50427,2.76491,0.91381,-5.18245,4.90406
+MEG_006,MEG_GRAD,0,-3.0105,1.94967,0,-6.23006,3.21696
+MEG_005,MEG_GRAD,0,-1.86073,3.41137,0,-3.85068,6.24186
+MEG_049,MEG_GRAD,-1.27145,-2.20222,2.76491,-2.6312,-4.55737,4.90406
+MEG_019,MEG_GRAD,-0.44157,-2.50427,2.76491,-0.91381,-5.18245,4.90406
+MEG_033,MEG_GRAD,-0.93037,-1.61144,3.41137,-1.92534,-3.33479,6.24186
+MEG_021,MEG_GRAD,-0.56074,-3.168,1.10519,-1.13708,-6.39559,2.21066
+MEG_020,MEG_GRAD,0.56022,-3.16809,1.10519,1.13604,-6.39578,2.21066
+MEG_034,MEG_GRAD,1.02965,-2.82894,1.94967,2.13081,-5.85434,3.21696
+MEG_077,MEG_GRAD,-2.47272,-2.0647,1.06346,-5.01426,-4.15829,2.12604
+MEG_035,MEG_GRAD,-1.02965,-2.82894,1.94967,-2.13081,-5.85434,3.21696
+MEG_007,MEG_GRAD,0,-3.27147,0.25764,0,-6.63351,1.0751
+MEG_023,MEG_GRAD,-0.576,-3.27431,-0.5962,-1.16503,-6.58484,0.21931
+MEG_022,MEG_GRAD,0.56022,-3.27709,-0.59609,1.14872,-6.58771,0.21942
+MEG_047,MEG_GRAD,-1.61144,-0.93037,3.41137,-3.33479,-1.92534,6.24186
+MEG_061,MEG_GRAD,-1.86073,0,3.41137,-3.85068,0,6.24186
+MEG_087,MEG_GRAD,-2.5429,0,2.76491,-5.2624,0,4.90406
+MEG_113,MEG_GRAD,-3.22769,0.0086,0.98505,-6.5452,0.046,1.96703
+MEG_101,MEG_GRAD,-2.96476,-0.52277,1.94967,-6.13541,-1.08184,3.21696
+MEG_099,MEG_GRAD,-2.96476,0.52277,1.94967,-6.13541,1.08184,3.21696
+MEG_063,MEG_GRAD,-1.94798,-1.63455,2.76491,-4.03123,-3.38261,4.90406
+MEG_075,MEG_GRAD,-2.38955,-0.86972,2.76491,-4.94504,-1.79985,4.90406
+MEG_089,MEG_GRAD,-2.60717,-1.50525,1.94967,-5.39539,-3.11503,3.21696
+MEG_123,MEG_GRAD,-3.24454,-0.65992,-1.54654,-6.63007,-1.24165,-1.13258
+MEG_103,MEG_GRAD,-3.03312,-1.09456,1.02677,-6.15066,-2.19102,2.05164
+MEG_119,MEG_GRAD,-3.27163,-0.04807,-0.71822,-6.66217,-0.02172,-0.02891
+MEG_121,MEG_GRAD,-3.24454,0.48346,-1.58979,-6.63007,1.0948,-1.22095
+MEG_105,MEG_GRAD,-3.07707,-1.16672,-0.67591,-6.26323,-2.29919,0.05723
+MEG_091,MEG_GRAD,-2.81455,-1.64764,0.19622,-5.75085,-3.31563,0.94961
+MEG_115,MEG_GRAD,-3.20059,-0.58777,0.15614,-6.53962,-1.15004,0.86771
+MEG_037,MEG_GRAD,-1.11155,-3.07561,0.25023,-2.27119,-6.23333,1.05996
+MEG_067,MEG_GRAD,-2.08904,-2.51166,0.2289,-4.26844,-5.08104,1.01638
+MEG_079,MEG_GRAD,-2.51137,-2.1514,-0.63867,-5.10885,-4.30296,0.13301
+MEG_093,MEG_GRAD,-2.8532,-1.73435,-1.50591,-5.7542,-3.37531,-0.57691
+MEG_051,MEG_GRAD,-1.61407,-2.7848,1.0907,-3.27306,-5.61852,2.18127
+MEG_065,MEG_GRAD,-1.93511,-2.30617,1.94967,-4.00461,-4.7725,3.21696
+REF_014,UNUSED,,,,,,
+MEG_053,MEG_GRAD,-1.64275,-2.88336,-0.61098,-3.33826,-5.79135,0.1893
+MEG_039,MEG_GRAD,-1.37821,4.03301,0.38766,-2.98972,7.09471,0.3625
+MEG_041,MEG_GRAD,-1.59926,3.67934,1.66789,-3.46787,6.31662,2.90266
+MEG_055,MEG_GRAD,-2.06278,3.53364,0.8475,-4.47296,6.00069,1.12372
+MEG_069,MEG_GRAD,-2.43321,2.88136,1.45931,-5.27622,4.58626,2.45038
+MEG_027,MEG_GRAD,-1.02514,3.32279,2.63742,-2.22293,5.54346,5.00502
+MEG_025,MEG_GRAD,-0.92333,4.17235,1.20548,-2.00217,7.38566,1.89996
+MEG_057,MEG_GRAD,-1.84667,3.00588,2.29955,-4.00435,4.85628,4.27238
+REF_015,UNUSED,,,,,,
+MEG_083,MEG_GRAD,-2.81067,2.32514,1.52142,-6.13327,3.15736,2.01067
+MEG_095,MEG_GRAD,-2.85632,2.16654,0.82155,-6.24599,2.85761,0.88605
+MEG_117,MEG_GRAD,-3.14455,0.87829,-0.52294,-6.53422,1.56936,-0.45844
+MEG_109,MEG_GRAD,-3.0226,1.3925,0.37679,-6.41227,2.08357,0.44129
+MEG_107,MEG_GRAD,-2.7791,2.44789,0.19401,-6.01824,3.66345,0.23867
+MEG_111,MEG_GRAD,-3.20059,0.54013,0.11348,-6.53962,1.15454,0.78055
+MEG_097,MEG_GRAD,-3.04326,1.22292,1.10768,-6.3884,1.94226,1.62169
+MEG_081,MEG_GRAD,-2.54021,2.92425,0.68688,-5.5195,4.68347,0.71098
+REF_001,REF_MAG,-2.26079604,3.98626183,5.04439808,-2.20703425,3.92437924,4.93090704
+REF_002,REF_MAG,1.93013445,4.03046866,5.17689263,1.8763992,3.96852956,5.06341985
+REF_004,REF_MAG,1.70031266,4.21202221,5.57217923,1.57144014,4.22797498,5.62449924
+REF_012,REF_GRAD,4.64675,-0.89642,-0.43802,6.03162,-1.01804,-0.22614
+REF_006,REF_MAG,2.07781,3.83073028,5.60154279,2.08802749,3.70619491,5.66468189
+REF_008,REF_GRAD,4.50056,0.78066,1.76423,5.88573,0.92199,1.96135
+REF_010,REF_GRAD,4.31926,2.18698,-0.37055,5.69806,2.46181,-0.34022
+MEG_094,REF_GRAD,2.85632,2.16654,0.82155,6.24599,2.85761,0.88605
+REF_016,UNUSED,,,,,,
+REF_003,REF_MAG,-2.73073962,4.07852721,5.1569653,-2.8596759,4.06162797,5.1051015
+REF_017,UNUSED,,,,,,
+REF_011,REF_GRAD,-4.64675,-0.89642,-0.43802,-6.03162,-1.01804,-0.22614
+REF_009,REF_GRAD,-4.31926,2.18698,-0.37055,-5.69806,2.46181,-0.34022
+REF_007,REF_GRAD,-4.50056,0.78066,1.76423,-5.88573,0.92199,1.96135
+REF_018,UNUSED,,,,,,
+REF_005,REF_MAG,-2.4058382,3.78665997,5.47001894,-2.41506358,3.66222139,5.53350068
+MEG_090,MEG_GRAD,2.81455,-1.64764,0.19622,5.75085,-3.31563,0.94961
+MEG_088,MEG_GRAD,2.60717,-1.50525,1.94967,5.39539,-3.11503,3.21696
+MEG_102,MEG_GRAD,3.03294,-1.09506,1.02679,6.1503,-2.19202,2.05167
+MEG_122,MEG_GRAD,3.24454,-0.65992,-1.54654,6.63007,-1.24165,-1.13258
+MEG_114,MEG_GRAD,3.20059,-0.58777,0.15614,6.53962,-1.15004,0.86771
+MEG_104,MEG_GRAD,3.07159,-1.18176,-0.67534,6.25756,-2.31475,0.05782
+MEG_120,MEG_GRAD,3.24454,0.48346,-1.58979,6.63007,1.0948,-1.22094
+MEG_118,MEG_GRAD,3.27163,-0.06408,-0.71761,6.66217,-0.03828,-0.02828
+MEG_106,MEG_GRAD,2.7791,2.44789,0.19401,6.01824,3.66345,0.23867
+MEG_082,MEG_GRAD,2.81067,2.32514,1.52142,6.13327,3.15736,2.01067
+MEG_110,MEG_GRAD,3.20059,0.54013,0.11348,6.53962,1.15454,0.78055
+MEG_116,MEG_GRAD,3.14455,0.87829,-0.52294,6.53422,1.56936,-0.45844
+MEG_096,MEG_GRAD,3.04326,1.22292,1.10768,6.3884,1.94226,1.62169
+MEG_080,MEG_GRAD,2.54021,2.92425,0.68688,5.5195,4.68347,0.71098
+MEG_108,MEG_GRAD,3.0226,1.3925,0.37679,6.41227,2.08357,0.44129
+REF_019,UNUSED,,,,,,
+MEG_009,MEG_GRAD,-0.48824,4.32904,0.13976,-1.05817,7.74156,0.10133
+MEG_003,MEG_GRAD,0,3.44805,2.77097,0,5.81508,5.29461
+MEG_010,MEG_GRAD,0.51257,3.97032,2.03007,1.11147,6.94759,3.68802
+MEG_012,MEG_GRAD,0.51257,2.67525,3.24478,1.11147,4.13933,6.32201
+MEG_004,MEG_GRAD,0,4.3528,1.03622,0,7.77696,1.53295
+MEG_011,MEG_GRAD,-0.51257,3.97032,2.03007,-1.11147,6.94759,3.68802
+MEG_008,MEG_GRAD,0.48824,4.32904,0.13976,1.05817,7.74156,0.10133
+MEG_013,MEG_GRAD,-0.51257,2.67525,3.24478,-1.11147,4.13933,6.32201
+MEG_024,MEG_GRAD,0.92333,4.17235,1.20548,2.00217,7.38566,1.89996
+REF_020,UNUSED,,,,,,
+MEG_068,MEG_GRAD,2.43321,2.88136,1.45931,5.27622,4.58626,2.45038
+MEG_026,MEG_GRAD,1.02514,3.32279,2.63742,2.22293,5.54346,5.00502
+MEG_038,MEG_GRAD,1.37821,4.03301,0.38766,2.98972,7.09471,0.3625
+MEG_040,MEG_GRAD,1.59926,3.67934,1.66789,3.46787,6.31662,2.90266
+MEG_054,MEG_GRAD,2.06278,3.53364,0.8475,4.47296,6.00069,1.12372
+MEG_056,MEG_GRAD,1.84667,3.00588,2.29955,4.00435,4.85628,4.27238
+MEG_058,MEG_GRAD,2.00892,1.56358,2.88668,4.25593,2.49543,5.34722
+MEG_042,MEG_GRAD,1.59926,2.33243,2.93122,3.46787,3.39595,5.64209
+MEG_028,MEG_GRAD,0.90968,1.7238,3.47337,1.93358,2.72985,6.62156
+MEG_070,MEG_GRAD,2.43321,2.17533,2.12153,5.27622,3.05529,3.88634
+REF_021,UNUSED,,,,,,
+MEG_072,MEG_GRAD,2.38955,0.86972,2.76491,4.94504,1.79985,4.90406
+MEG_044,MEG_GRAD,1.61144,0.93037,3.41137,3.33479,1.92534,6.24186
+MEG_084,MEG_GRAD,2.78632,1.40783,1.84839,5.89386,2.21359,3.13893
+MEG_046,MEG_GRAD,1.61144,-0.93037,3.41137,3.33479,-1.92534,6.24186
+MEG_098,MEG_GRAD,2.96476,0.52277,1.94967,6.13541,1.08184,3.21696
+MEG_060,MEG_GRAD,1.8607,0,3.41137,3.85068,0,6.24186
+MEG_100,MEG_GRAD,2.96476,-0.52277,1.94967,6.13541,-1.08184,3.21696
+MEG_074,MEG_GRAD,2.38955,-0.86972,2.76491,4.94504,-1.79985,4.90406
+MEG_086,MEG_GRAD,2.5429,0,2.76491,5.2624,0,4.90406
+MEG_062,MEG_GRAD,1.94798,-1.63455,2.76491,4.03123,-3.38261,4.90406
+MEG_112,MEG_GRAD,3.22769,0.00807,0.98507,6.5452,0.04494,1.96707
+MEG_016,MEG_GRAD,0.50538,-0.87535,3.83752,0.89368,-1.5479,7.20924
+MEG_031,MEG_GRAD,-1.01076,0,3.83752,-1.78736,0,7.20924
+MEG_015,MEG_GRAD,-0.50538,0.87535,3.83752,-0.89368,1.5479,7.20924
+MEG_001,MEG_GRAD,0,0,4,0,0,7.46
+MEG_002,MEG_GRAD,0,1.80611,3.59215,0,2.82922,6.89743
+MEG_017,MEG_GRAD,-0.50538,-0.87535,3.83752,-0.89368,-1.5479,7.20924
+MEG_014,MEG_GRAD,0.50538,0.87535,3.83752,0.89368,1.5479,7.20924
+MEG_030,MEG_GRAD,1.01076,0,3.83752,1.78736,0,7.20924
+MEG_050,MEG_GRAD,1.61362,-2.78506,1.09071,3.27214,-5.61905,2.18129
+MEG_064,MEG_GRAD,1.93511,-2.30617,1.94967,4.00461,-4.7725,3.21696
+MEG_076,MEG_GRAD,2.47238,-2.0651,1.06348,5.01358,-4.1591,2.12607
+MEG_078,MEG_GRAD,2.50107,-2.16367,-0.6382,5.0982,-4.31565,0.13349
+MEG_066,MEG_GRAD,2.08904,-2.51166,0.2289,4.26844,-5.08104,1.01638
+MEG_036,MEG_GRAD,1.11155,-3.07561,0.25023,2.27119,-6.23333,1.05996
+MEG_052,MEG_GRAD,1.62888,-2.89137,-0.61068,3.32391,-5.79963,0.18962
+MEG_092,MEG_GRAD,2.8532,-1.73435,-1.50591,5.7542,-3.37531,-0.57691
diff --git a/mne/io/artemis123/resources/Artemis123_mneLoc.csv b/mne/io/artemis123/resources/Artemis123_mneLoc.csv
new file mode 100644
index 0000000..cdad771
--- /dev/null
+++ b/mne/io/artemis123/resources/Artemis123_mneLoc.csv
@@ -0,0 +1,144 @@
+MEG_001,0.0,0.0,0.10160000191,1.0,-0.0,-0.0,-0.0,1.0,-0.0,0.0,0.0,1.0
+MEG_002,0.0,0.0458751948625,0.0912406117153,1.0,-0.0,-0.0,-0.0,0.955282042035,-0.295696161906,0.0,0.295696161906,0.955282042035
+MEG_003,0.0,0.0875804716465,0.0703826393232,1.0,-0.0,-0.0,-0.0,0.729376031116,-0.684113006186,0.0,0.684113006186,0.729376031116
+MEG_004,0.0,0.110561122079,0.0263199884948,1.0,-0.0,-0.0,-0.0,0.143563509474,-0.989641106032,0.0,0.989641106032,0.143563509474
+MEG_005,0.0,-0.0472625428885,0.086648799629,1.0,0.0,-0.0,0.0,0.818061560022,0.575130666904,0.0,-0.575130666904,0.818061560022
+MEG_006,0.0,-0.0764667014376,0.049521618931,1.0,0.0,-0.0,0.0,0.366268930876,0.930509038255,0.0,-0.930509038255,0.366268930876
+MEG_007,0.0,-0.0830953395622,0.00654405612303,1.0,0.0,-0.0,0.0,0.236260571358,0.971689735678,0.0,-0.971689735678,0.236260571358
+MEG_008,0.0124012962331,0.109957618067,0.00354990406674,0.972562667953,-0.164284112711,-0.164719723212,-0.164284112711,0.0163303909087,-0.986277875978,0.164719723212,0.986277875978,-0.0111069411385
+MEG_009,-0.0124012962331,0.109957618067,0.00354990406674,0.972562667953,0.164284112711,0.164719723212,0.164284112711,0.0163303909087,-0.986277875978,-0.164719723212,0.986277875978,-0.0111069411385
+MEG_010,0.0130192782448,0.100846129896,0.0515637789694,0.979744824976,-0.100693145673,-0.173092369408,-0.100693145673,0.499431154085,-0.860482081594,0.173092369408,0.860482081594,0.479175979061
+MEG_011,-0.0130192782448,0.100846129896,0.0515637789694,0.979744824976,0.100693145673,0.173092369408,0.100693145673,0.499431154085,-0.860482081594,-0.173092369408,0.860482081594,0.479175979061
+MEG_012,0.0130192782448,0.0679513512775,0.0824174135494,0.984142307767,-0.038765954324,-0.17309280415,-0.038765954324,0.905232161619,-0.423145287527,0.17309280415,0.423145287527,0.889374469386
+MEG_013,-0.0130192782448,0.0679513512775,0.0824174135494,0.984142307767,0.038765954324,0.17309280415,0.038765954324,0.905232161619,-0.423145287527,-0.17309280415,0.423145287527,0.889374469386
+MEG_014,0.0128366522413,0.022233890418,0.0974730098325,0.993621350642,-0.0110480572384,-0.112225451567,-0.0110480572384,0.980864355149,-0.194378643965,0.112225451567,0.194378643965,0.974485705791
+MEG_015,-0.0128366522413,0.022233890418,0.0974730098325,0.993621350642,0.0110480572384,0.112225451567,0.0110480572384,0.980864355149,-0.194378643965,-0.112225451567,0.194378643965,0.974485705791
+MEG_016,0.0128366522413,-0.022233890418,0.0974730098325,0.993621350642,0.0110480572384,-0.112225451567,0.0110480572384,0.980864355149,0.194378643965,0.112225451567,-0.194378643965,0.974485705791
+MEG_017,-0.0128366522413,-0.022233890418,0.0974730098325,0.993621350642,-0.0110480572384,0.112225451567,-0.0110480572384,0.980864355149,0.194378643965,-0.112225451567,-0.194378643965,0.974485705791
+MEG_018,0.0112158782109,-0.0636084591958,0.0702287153203,0.988488638046,0.0652835409099,-0.136485426846,0.0652835409099,0.629762253104,0.774039768908,0.136485426846,-0.774039768908,0.61825089115
+MEG_019,-0.0112158782109,-0.0636084591958,0.0702287153203,0.988488638046,-0.0652835409099,0.136485426846,-0.0652835409099,0.629762253104,0.774039768908,-0.136485426846,-0.774039768908,0.61825089115
+MEG_020,0.0142295882675,-0.0804694875128,0.0280718265278,0.979010049739,0.117656650617,-0.166421858768,0.117656650617,0.340489745704,0.93285778425,0.166421858768,-0.93285778425,0.319499795443
+MEG_021,-0.0142427962678,-0.0804672015128,0.0280718265278,0.978972050612,-0.117759654311,0.166572470526,-0.117759654311,0.340528364062,0.932830690472,-0.166572470526,-0.932830690472,0.319500414673
+MEG_022,0.0142295882675,-0.0832380875649,-0.0151406862846,0.976588506526,0.131701883642,-0.170086750705,0.131701883642,0.259108088321,0.956826845574,0.170086750705,-0.956826845574,0.235696594848
+MEG_023,-0.0146304002751,-0.0831674755635,-0.0151434802847,0.976546368958,-0.131816629327,0.170239729521,-0.131816629327,0.259149948413,0.956799707604,-0.170239729521,-0.956799707604,0.235696317371
+MEG_024,0.0234525824409,0.105977691992,0.0306191925756,0.919030275794,-0.241167202261,-0.311803997292,-0.241167202261,0.281686827798,-0.928703888007,0.311803997292,0.928703888007,0.200717103592
+MEG_025,-0.0234525824409,0.105977691992,0.0306191925756,0.919030275794,0.241167202261,0.311803997292,0.241167202261,0.281686827798,-0.928703888007,-0.311803997292,0.928703888007,0.200717103592
+MEG_026,0.0260385564895,0.0843988675867,0.0669904692594,0.928846648352,-0.131916373824,-0.346181995721,-0.131916373824,0.755430639878,-0.641811980763,0.346181995721,0.641811980763,0.68427728823
+MEG_027,-0.0260385564895,0.0843988675867,0.0669904692594,0.928846648352,0.131916373824,0.346181995721,0.131916373824,0.755430639878,-0.641811980763,-0.346181995721,0.641811980763,0.68427728823
+MEG_028,0.0231058724344,0.0437845208231,0.0882235996586,0.954148215535,-0.0450524345745,-0.295924755521,-0.0450524345745,0.955732979975,-0.290765797726,0.295924755521,0.290765797726,0.90988119551
+MEG_029,-0.0231330504349,0.0437862988232,0.0882142016584,0.954036318954,0.0451068389737,0.296277024411,0.0451068389737,0.955734030088,-0.290753911081,-0.296277024411,0.290753911081,0.909770349043
+MEG_030,0.0256733044827,0.0,0.0974730098325,0.974485414074,-0.0,-0.224450835944,-0.0,1.0,-0.0,0.224450835944,0.0,0.974485414074
+MEG_031,-0.0256733044827,0.0,0.0974730098325,0.974485414074,0.0,0.224450835944,0.0,1.0,-0.0,-0.224450835944,0.0,0.974485414074
+MEG_032,0.0236313984443,-0.0409305767695,0.086648799629,0.954515845737,0.078781387629,-0.287563894118,0.078781387629,0.863545730655,0.498078572146,0.287563894118,-0.498078572146,0.818061576392
+MEG_033,-0.0236313984443,-0.0409305767695,0.086648799629,0.954515845737,-0.078781387629,0.287563894118,-0.078781387629,0.863545730655,0.498078572146,-0.287563894118,-0.498078572146,0.818061576392
+MEG_034,0.0261531104917,-0.0718550773509,0.049521618931,0.925866960833,0.203678027441,-0.318254036858,0.203678027441,0.440401481873,0.874392243734,0.318254036858,-0.874392243734,0.366268442706
+MEG_035,-0.0261531104917,-0.0718550773509,0.049521618931,0.925866960833,-0.203678027441,0.318254036858,-0.203678027441,0.440401481873,0.874392243734,-0.318254036858,-0.874392243734,0.366268442706
+MEG_036,0.0282333705308,-0.0781204954687,0.00635584211949,0.908973236603,0.247867468623,-0.335155744599,0.247867468623,0.325052548187,0.91263495381,0.335155744599,-0.91263495381,0.23402578479
+MEG_037,-0.0282333705308,-0.0781204954687,0.00635584211949,0.908973236603,-0.247867468623,0.335155744599,-0.247867468623,0.325052548187,0.91263495381,-0.335155744599,-0.91263495381,0.23402578479
+MEG_038,0.0350065346581,0.102438455926,0.00984656418512,0.781484001521,-0.415157481208,-0.465754249753,-0.415157481208,0.211244323513,-0.884884230609,0.465754249753,0.884884230609,-0.00727167496558
+MEG_039,-0.0350065346581,0.102438455926,0.00984656418512,0.781484001521,0.415157481208,0.465754249753,0.415157481208,0.211244323513,-0.884884230609,-0.465754249753,0.884884230609,-0.00727167496558
+MEG_040,0.0406212047637,0.093455237757,0.0423644067965,0.785045408535,-0.303378150058,-0.540060556425,-0.303378150058,0.57182444299,-0.762219459517,0.540060556425,0.762219459517,0.356869851524
+MEG_041,-0.0406212047637,0.093455237757,0.0423644067965,0.785045408535,0.303378150058,0.540060556425,0.303378150058,0.57182444299,-0.762219459517,-0.540060556425,0.762219459517,0.356869851524
+MEG_042,0.0406212047637,0.0592437231138,0.0744529893997,0.836463385382,-0.0930769183398,-0.540060822675,-0.0930769183398,0.947025241119,-0.307375795983,0.540060822675,0.307375795983,0.7834886265
+MEG_043,-0.0406212047637,0.0592437231138,0.0744529893997,0.836463385382,0.0930769183398,0.540060822675,0.0930769183398,0.947025241119,-0.307375795983,-0.540060822675,0.307375795983,0.7834886265
+MEG_044,0.0409305767695,0.0236313984443,0.086648799629,0.863545730655,-0.078781387629,-0.498078572146,-0.078781387629,0.954515845737,-0.287563894118,0.498078572146,0.287563894118,0.818061576392
+MEG_045,-0.0409305767695,0.0236313984443,0.086648799629,0.863545730655,0.078781387629,0.498078572146,0.078781387629,0.954515845737,-0.287563894118,-0.498078572146,0.287563894118,0.818061576392
+MEG_046,0.0409305767695,-0.0236313984443,0.086648799629,0.863545730655,0.078781387629,-0.498078572146,0.078781387629,0.954515845737,0.287563894118,0.498078572146,-0.287563894118,0.818061576392
+MEG_047,-0.0409305767695,-0.0236313984443,0.086648799629,0.863545730655,-0.078781387629,0.498078572146,-0.078781387629,0.954515845737,0.287563894118,-0.498078572146,-0.287563894118,0.818061576392
+MEG_048,0.0322948306071,-0.0559363890516,0.0702287153203,0.904562399003,0.165302346745,-0.392991094644,0.165302346745,0.713688676641,0.680678784005,0.392991094644,-0.680678784005,0.618251075645
+MEG_049,-0.0322948306071,-0.0559363890516,0.0702287153203,0.904562399003,-0.165302346745,0.392991094644,-0.165302346745,0.713688676641,0.680678784005,-0.392991094644,-0.680678784005,0.618251075645
+MEG_050,0.0409859487705,-0.0707405253299,0.0277040345208,0.825297111533,0.298522923381,-0.479341988471,0.298522923381,0.489900043633,0.819073874241,0.479341988471,-0.819073874241,0.315197155166
+MEG_051,-0.0409973787708,-0.0707339213298,0.0277037805208,0.825197788666,-0.298579570884,0.479477683976,-0.298579570884,0.489996382374,0.818995595294,-0.479477683976,-0.818995595294,0.31519417104
+MEG_052,0.0413735527778,-0.0734407993807,-0.0155112722916,0.805087785644,0.334422043575,-0.489893411036,0.334422043575,0.426212956438,0.840538168399,0.489893411036,-0.840538168399,0.231300742083
+MEG_053,-0.0417258507844,-0.0732373453769,-0.0155188922918,0.804976833568,-0.334486625117,0.490031626568,-0.334486625117,0.426317886079,0.840459253996,-0.490031626568,-0.840459253996,0.231294719647
+MEG_054,0.052394612985,0.0897544576874,0.0215265004047,0.550644162251,-0.459958724875,-0.696583791076,-0.459958724875,0.529188204946,-0.713020206696,0.696583791076,0.713020206696,0.0798323671971
+MEG_055,-0.052394612985,0.0897544576874,0.0215265004047,0.550644162251,0.459958724875,0.696583791076,0.459958724875,0.529188204946,-0.713020206696,-0.696583791076,0.713020206696,0.0798323671971
+MEG_056,0.0469054188818,0.0763493534354,0.0584085710981,0.752331243475,-0.212397698952,-0.623606380317,-0.212397698952,0.817850328992,-0.534797210957,0.623606380317,0.534797210957,0.570181572467
+MEG_057,-0.0469054188818,0.0763493534354,0.0584085710981,0.752331243475,0.212397698952,0.623606380317,0.212397698952,0.817850328992,-0.534797210957,-0.623606380317,0.534797210957,0.570181572467
+MEG_058,0.0510265689593,0.0397149327466,0.0733216733784,0.75352606525,-0.102214380931,-0.649423351381,-0.102214380931,0.95761101603,-0.269320185484,0.649423351381,0.269320185484,0.71113708128
+MEG_059,-0.0502099589439,0.0397642087476,0.0740382073919,0.762632097113,0.100407167247,0.638991928915,0.100407167247,0.957527538003,-0.27029505125,-0.638991928915,0.27029505125,0.720159635116
+MEG_060,0.0472617808885,0.0,0.086648799629,0.818057480605,-0.0,-0.575136469394,-0.0,1.0,-0.0,0.575136469394,0.0,0.818057480605
+MEG_061,-0.0472625428885,0.0,0.086648799629,0.818061560022,0.0,0.575130666904,0.0,1.0,-0.0,-0.575130666904,0.0,0.818061560022
+MEG_062,0.0494786929302,-0.0415175707805,0.0702287153203,0.775981248219,0.187974664221,-0.602095198473,0.187974664221,0.842270014862,0.505219504448,0.602095198473,-0.505219504448,0.618251263081
+MEG_063,-0.0494786929302,-0.0415175707805,0.0702287153203,0.775981248219,-0.187974664221,0.602095198473,-0.187974664221,0.842270014862,0.505219504448,-0.602095198473,-0.505219504448,0.618251263081
+MEG_064,0.0491517949241,-0.0585767191012,0.049521618931,0.738156783089,0.312052080775,-0.598120441436,0.312052080775,0.628111423833,0.712811011513,0.598120441436,-0.712811011513,0.366268206923
+MEG_065,-0.0491517949241,-0.0585767191012,0.049521618931,0.738156783089,-0.312052080775,0.598120441436,-0.312052080775,0.628111423833,0.712811011513,-0.598120441436,-0.712811011513,0.366268206923
+MEG_066,0.0530616169976,-0.0637961651994,0.0058140601093,0.676804202704,0.381028180993,-0.629883796022,0.381028180993,0.550790957291,0.742594671847,0.629883796022,-0.742594671847,0.227595159994
+MEG_067,-0.0530616169976,-0.0637961651994,0.0058140601093,0.676804202704,-0.381028180993,0.629883796022,-0.381028180993,0.550790957291,0.742594671847,-0.629883796022,-0.742594671847,0.227595159994
+MEG_068,0.0618035351619,0.0731865453759,0.0370664746968,0.475173275464,-0.314728784866,-0.821678860785,-0.314728784866,0.811263025695,-0.492745466865,0.821678860785,0.492745466865,0.286436301159
+MEG_069,-0.0618035351619,0.0731865453759,0.0370664746968,0.475173275464,0.314728784866,0.821678860785,0.314728784866,0.811263025695,-0.492745466865,-0.821678860785,0.492745466865,0.286436301159
+MEG_070,0.0618035351619,0.0552533830388,0.0538868630131,0.552894017073,-0.138386914129,-0.821679540869,-0.138386914129,0.957166893906,-0.254323807789,0.821679540869,0.254323807789,0.510060910979
+MEG_071,-0.0618035351619,0.0552533830388,0.0538868630131,0.552894017073,0.138386914129,0.821679540869,0.138386914129,0.957166893906,-0.254323807789,-0.821679540869,0.254323807789,0.510060910979
+MEG_072,0.0606945711411,0.0220908884153,0.0702287153203,0.662907428432,-0.12269267874,-0.738579885939,-0.12269267874,0.955343146999,-0.268823321284,0.738579885939,0.268823321284,0.61825057543
+MEG_073,-0.0606945711411,0.0220908884153,0.0702287153203,0.662907428432,0.12269267874,0.738579885939,0.12269267874,0.955343146999,-0.268823321284,-0.738579885939,0.268823321284,0.61825057543
+MEG_074,0.0606945711411,-0.0220908884153,0.0702287153203,0.662907428432,0.12269267874,-0.738579885939,0.12269267874,0.955343146999,0.268823321284,0.738579885939,-0.268823321284,0.61825057543
+MEG_075,-0.0606945711411,-0.0220908884153,0.0702287153203,0.662907428432,-0.12269267874,0.738579885939,-0.12269267874,0.955343146999,0.268823321284,-0.738579885939,-0.268823321284,0.61825057543
+MEG_076,0.0627984531806,-0.0524535409861,0.0270123925078,0.587320034467,0.340056606259,-0.73444991773,0.340056606259,0.719786504995,0.605201529878,0.73444991773,-0.605201529878,0.307106539462
+MEG_077,-0.0628070891808,-0.0524433809859,0.0270118845078,0.587208379993,-0.340036516337,0.734548491268,-0.340036516337,0.719895397972,0.605083286446,-0.734548491268,-0.605083286446,0.307103777966
+MEG_078,0.0635271791943,-0.0549572190332,-0.0162102803048,0.539322307512,0.381717195782,-0.750615368258,0.381717195782,0.683709413476,0.621959339803,0.750615368258,-0.621959339803,0.223031720988
+MEG_079,-0.0637887991992,-0.0546455610273,-0.016222218305,0.539196876386,-0.381695169412,0.750716675014,-0.381695169412,0.683831999207,0.621838077403,-0.750716675014,-0.621838077403,0.223028875593
+MEG_080,0.064521335213,0.0742759513964,0.017446752328,0.263693428198,-0.434776489447,-0.861066304154,-0.434776489447,0.743271888347,-0.508444986421,0.861066304154,0.508444986421,0.00696531654525
+MEG_081,-0.064521335213,0.0742759513964,0.017446752328,0.263693428198,0.434776489447,0.861066304154,0.434776489447,0.743271888347,-0.508444986421,-0.861066304154,0.508444986421,0.00696531654525
+MEG_082,0.0713910193422,0.0590585571103,0.0386440687265,0.192087187177,-0.202359959395,-0.960287956478,-0.202359959395,0.949314390716,-0.240525745844,0.960287956478,0.240525745844,0.141401577893
+MEG_083,-0.0713910193422,0.0590585571103,0.0386440687265,0.192087187177,0.202359959395,0.960287956478,0.202359959395,0.949314390716,-0.240525745844,-0.960287956478,0.240525745844,0.141401577893
+MEG_084,0.0707725293305,0.0357588826723,0.0469491068826,0.412488973038,-0.15233685973,-0.89813491653,-0.15233685973,0.960500283795,-0.232879123147,0.89813491653,0.232879123147,0.372989256833
+MEG_085,-0.0707722753305,0.0357588826723,0.0469491068826,0.412487827635,0.152336666507,0.898135475354,0.152336666507,0.960500461005,-0.232878518647,-0.898135475354,0.232878518647,0.37298828864
+MEG_086,0.0645896612143,0.0,0.0702287153203,0.618250335472,-0.0,-0.785981248306,-0.0,1.0,-0.0,0.785981248306,0.0,0.618250335472
+MEG_087,-0.0645896612143,0.0,0.0702287153203,0.618250335472,0.0,0.785981248306,0.0,1.0,-0.0,-0.785981248306,0.0,0.618250335472
+MEG_088,0.066222119245,-0.0382333507188,0.049521618931,0.524701809918,0.274413611706,-0.80584438968,0.274413611706,0.841567184852,0.46525460029,0.80584438968,-0.46525460029,0.36626899477
+MEG_089,-0.066222119245,-0.0382333507188,0.049521618931,0.524701809918,-0.274413611706,0.80584438968,-0.274413611706,0.841567184852,0.46525460029,-0.80584438968,-0.46525460029,0.36626899477
+MEG_090,0.071489571344,-0.0418500567868,0.0049839880937,0.408585986848,0.335957722235,-0.848640029826,0.335957722235,0.809156380101,0.482077132224,0.848640029826,-0.482077132224,0.217742366948
+MEG_091,-0.071489571344,-0.0418500567868,0.0049839880937,0.408585986848,-0.335957722235,0.848640029826,-0.335957722235,0.809156380101,0.482077132224,-0.848640029826,-0.482077132224,0.217742366948
+MEG_092,0.0724712813625,-0.0440524908282,-0.0382501147191,0.445815918958,0.313476011591,-0.83843959625,0.313476011591,0.822681283702,0.474266059932,0.83843959625,-0.474266059932,0.26849720266
+MEG_093,-0.0724712813625,-0.0440524908282,-0.0382501147191,0.445815918958,-0.313476011591,0.83843959625,-0.313476011591,0.822681283702,0.474266059932,-0.83843959625,-0.474266059932,0.26849720266
+MEG_094,0.0725505293639,0.0550301170346,0.0208673703923,0.0578041209796,-0.192090470788,-0.979673381608,-0.192090470788,0.96083749697,-0.199731208002,0.979673381608,0.199731208002,0.0186416179491
+MEG_095,-0.0725505293639,0.0550301170346,0.0208673703923,0.0578041209796,0.192090470788,0.979673381608,0.192090470788,0.96083749697,-0.199731208002,-0.979673381608,0.199731208002,0.0186416179491
+MEG_096,0.0772988054532,0.031062168584,0.0281350725289,0.186190165142,-0.175001933135,-0.966802743999,-0.175001933135,0.962367527045,-0.20790157837,0.966802743999,0.20790157837,0.148557692187
+MEG_097,-0.0772988054532,0.031062168584,0.0281350725289,0.186190165142,0.175001933135,0.966802743999,0.175001933135,0.962367527045,-0.20790157837,-0.966802743999,0.20790157837,0.148557692187
+MEG_098,0.0753049054157,0.0132783582496,0.049521618931,0.385377976058,-0.108374224505,-0.91637265511,-0.108374224505,0.980890739219,-0.1615808936,0.91637265511,0.1615808936,0.366268715277
+MEG_099,-0.0753049054157,0.0132783582496,0.049521618931,0.385377976058,0.108374224505,0.91637265511,0.108374224505,0.980890739219,-0.1615808936,-0.91637265511,0.1615808936,0.366268715277
+MEG_100,0.0753049054157,-0.0132783582496,0.049521618931,0.385377976058,0.108374224505,-0.91637265511,0.108374224505,0.980890739219,0.1615808936,0.91637265511,-0.1615808936,0.366268715277
+MEG_101,-0.0753049054157,-0.0132783582496,0.049521618931,0.385377976058,-0.108374224505,0.91637265511,-0.108374224505,0.980890739219,0.1615808936,-0.91637265511,-0.1615808936,0.366268715277
+MEG_102,0.0770366774483,-0.0278145245229,0.0260804664903,0.373752636547,0.220368615692,-0.900969832953,0.220368615692,0.922455039947,0.31704001718,0.900969832953,-0.31704001718,0.296207676495
+MEG_103,-0.0770412494484,-0.0278018245227,0.0260799584903,0.373679152588,-0.220281297547,0.901021665041,-0.220281297547,0.92252557096,0.31689544155,-0.901021665041,-0.31689544155,0.296204723548
+MEG_104,0.0780183874667,-0.0300167045643,-0.0171536363225,0.300373897506,0.24880001314,-0.920800779299,0.24880001314,0.911522102566,0.327453828799,0.920800779299,-0.327453828799,0.211896000073
+MEG_105,-0.0781575794694,-0.0296346885571,-0.0171681143228,0.300287289279,-0.248701776907,0.920855564169,-0.248701776907,0.911602900892,0.327303494098,-0.920855564169,-0.327303494098,0.211890190171
+MEG_106,0.0705891413271,0.0621764071689,0.00492785409264,0.134758903688,-0.324701145067,-0.936167295022,-0.324701145067,0.878148606143,-0.351317793345,0.936167295022,0.351317793345,0.0129075098315
+MEG_107,-0.0705891413271,0.0621764071689,0.00492785409264,0.134758903688,0.324701145067,0.936167295022,0.324701145067,0.878148606143,-0.351317793345,-0.936167295022,0.351317793345,0.0129075098315
+MEG_108,0.0767740414434,0.0353695006649,0.00957046617992,0.0578041209796,-0.192090470788,-0.979673381608,-0.192090470788,0.96083749697,-0.199731208002,0.979673381608,0.199731208002,0.0186416179491
+MEG_109,-0.0767740414434,0.0353695006649,0.00957046617992,0.0578041209796,0.192090470788,0.979673381608,0.192090470788,0.96083749697,-0.199731208002,-0.979673381608,0.199731208002,0.0186416179491
+MEG_110,0.0812949875283,0.0137193022579,0.00288239205419,0.219230938619,-0.143668166804,-0.965037436268,-0.143668166804,0.973563831901,-0.177575119486,0.965037436268,0.177575119486,0.192794770521
+MEG_111,-0.0812949875283,0.0137193022579,0.00288239205419,0.219230938619,0.143668166804,0.965037436268,0.143668166804,0.973563831901,-0.177575119486,-0.965037436268,0.177575119486,0.192794770521
+MEG_112,0.0819833275413,0.000204978003854,0.0250207784704,0.283903999524,-0.00795851694118,-0.958819681203,-0.00795851694118,0.999911550977,-0.010656088948,0.958819681203,0.010656088948,0.283815550501
+MEG_113,-0.0819833275413,0.000218440004107,0.0250202704704,0.283900779742,0.00807295557139,0.958819677859,0.00807295557139,0.999908989411,-0.0108092683826,-0.958819677859,0.0108092683826,0.283809769153
+MEG_114,0.0812949875283,-0.0149293582807,0.00396595607456,0.227559595527,0.130073723873,-0.965037541675,0.130073723873,0.978096467321,0.162505775197,0.965037541675,-0.162505775197,0.205656062847
+MEG_115,-0.0812949875283,-0.0149293582807,0.00396595607456,0.227559595527,-0.130073723873,0.965037541675,-0.130073723873,0.978096467321,0.162505775197,-0.965037541675,-0.162505775197,0.205656062847
+MEG_116,0.0798715715016,0.0223085664194,-0.0132826762497,0.0578041209796,-0.192090470788,-0.979673381608,-0.192090470788,0.96083749697,-0.199731208002,0.979673381608,0.199731208002,0.0186416179491
+MEG_117,-0.0798715715016,0.0223085664194,-0.0132826762497,0.0578041209796,0.192090470788,0.979673381608,0.192090470788,0.96083749697,-0.199731208002,-0.979673381608,0.199731208002,0.0186416179491
+MEG_118,0.0830994035623,-0.0016276320306,-0.0182272943427,0.199274663362,-0.00609304526277,-0.979924733508,-0.00609304526277,0.999953635537,-0.00745664647062,0.979924733508,0.00745664647062,0.199228298899
+MEG_119,-0.0830994035623,-0.00122097802295,-0.018242788343,0.199270871862,0.00622296522868,0.979924688091,0.00622296522868,0.999951637458,-0.00761560563545,-0.979924688091,0.00761560563545,0.19922250932
+MEG_120,0.0824113175493,0.0122798842309,-0.0403806667592,0.134815219165,-0.156230210311,-0.978476866394,-0.156230210311,0.971788825746,-0.176687859065,0.978476866394,0.176687859065,0.106604044912
+MEG_121,-0.0824113175493,0.0122798842309,-0.0403806667592,0.134812452063,0.156230709979,0.978477167862,0.156230709979,0.971788735519,-0.176687913503,-0.978477167862,0.176687913503,0.106601187582
+MEG_122,0.0824113175493,-0.0167619683151,-0.0392821167385,0.144888827116,0.146932333372,-0.978477448481,0.146932333372,0.974752861061,0.168130155723,0.978477448481,-0.168130155723,0.119641688177
+MEG_123,-0.0824113175493,-0.0167619683151,-0.0392821167385,0.144888827116,-0.146932333372,0.978477448481,-0.146932333372,0.974752861061,0.168130155723,-0.978477448481,-0.168130155723,0.119641688177
+REF_001,-0.0574242204956,0.101251052386,0.128127713641,0.221198761686,0.896440347728,-0.384012774259,0.896440347728,-0.0318490232173,0.442018486814,0.384012774259,-0.442018486814,-0.810650261531
+REF_002,0.0490254159517,0.102373905889,0.131493075274,0.222503034056,-0.896198721013,0.383823204472,-0.896198721013,-0.0330228704748,0.442422131545,-0.383823204472,-0.442422131545,-0.810519836419
+REF_003,-0.069360787652,0.103594593082,0.130986921083,-0.347310972431,-0.17658747001,0.920973373049,-0.17658747001,0.976855280479,0.120708849866,-0.920973373049,-0.120708849866,-0.370455691952
+REF_004,0.0431879423759,0.106985366145,0.141533355103,0.383166262537,0.0763561288473,0.920517982899,0.0763561288473,0.990548087664,-0.113948355026,-0.920517982899,0.113948355026,0.3737143502
+REF_005,-0.0611082914288,0.0961811650462,0.138938483688,0.997012450802,-0.040298218601,0.0658955728709,-0.040298218601,0.456428559123,0.888847019455,-0.0658955728709,-0.888847019455,0.453441009925
+REF_006,0.0527763749922,0.0973005509413,0.142279189541,0.99632914816,0.0447419955488,-0.072982068763,0.0447419955488,0.454664406796,0.889538324653,0.072982068763,-0.889538324653,0.450993554956
+REF_007,-0.114314226149,0.0198287643728,0.0448114428425,0.149033474209,0.0868247934117,0.985012933323,0.0868247934117,0.991141197071,-0.100501655296,-0.985012933323,0.100501655296,0.14017467128
+REF_008,0.114314226149,0.0198287643728,0.0448114428425,0.149033474209,-0.0868247934117,-0.985012933323,-0.0868247934117,0.991141197071,-0.100501655296,0.985012933323,0.100501655296,0.14017467128
+REF_009,-0.109709206063,0.0555492930443,-0.00941197017695,0.0589562738411,0.187574011648,0.98047954998,0.187574011648,0.96261171626,-0.195434576966,-0.98047954998,0.195434576966,0.0215679901007
+REF_010,0.109709206063,0.0555492930443,-0.00941197017695,0.0589562738411,-0.187574011648,-0.98047954998,-0.187574011648,0.96261171626,-0.195434576966,0.98047954998,0.195434576966,0.0215679901007
+REF_011,-0.118027452219,-0.0227690684281,-0.0111257082092,0.157170103306,-0.0740177576494,0.984793851615,-0.0740177576494,0.993499722223,0.0864851056297,-0.984793851615,-0.0864851056297,0.150669825529
+REF_012,0.118027452219,-0.0227690684281,-0.0111257082092,0.157170103306,0.0740177576494,-0.984793851615,0.0740177576494,0.993499722223,0.0864851056297,0.984793851615,-0.0864851056297,0.150669825529
+REF_013,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0
+REF_014,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0
+REF_015,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0
+REF_016,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0
+REF_017,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0
+REF_018,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0
+REF_019,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0
+REF_020,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0
+REF_021,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0
diff --git a/mne/data/__init__.py b/mne/io/artemis123/tests/__init__.py
similarity index 100%
copy from mne/data/__init__.py
copy to mne/io/artemis123/tests/__init__.py
diff --git a/mne/io/artemis123/tests/test_artemis123.py b/mne/io/artemis123/tests/test_artemis123.py
new file mode 100644
index 0000000..c89d569
--- /dev/null
+++ b/mne/io/artemis123/tests/test_artemis123.py
@@ -0,0 +1,100 @@
+
+# Author: Luke Bloy <bloyl at chop.edu>
+#
+# License: BSD (3-clause)
+
+import os.path as op
+import warnings
+
+import numpy as np
+from numpy.testing import assert_allclose, assert_equal
+from nose.tools import assert_true
+
+from mne.utils import run_tests_if_main, _TempDir
+from mne.io import read_raw_artemis123
+from mne.io.tests.test_raw import _test_raw_reader
+from mne.datasets import testing
+from mne.io.artemis123.utils import _generate_mne_locs_file, _load_mne_locs
+from mne import pick_types
+from mne.transforms import rot_to_quat, _angle_between_quats
+
+artemis123_dir = op.join(testing.data_path(download=False), 'ARTEMIS123')
+
+short_HPI_dip_fname = op.join(artemis123_dir,
+                              'Artemis_Data_2017-04-04-15h-44m-' +
+                              '22s_Motion_Translation-z.bin')
+
+dig_fname = op.join(artemis123_dir, 'Phantom_040417_dig.pos')
+
+short_hpi_1kz_fname = op.join(artemis123_dir, 'Artemis_Data_2017-04-14-10h' +
+                              '-38m-59s_Phantom_1k_HPI_1s.bin')
+
+
+def _assert_trans(actual, desired, dist_tol=0.003, angle_tol=5.):
+    trans_est = actual[0:3, 3]
+    quat_est = rot_to_quat(actual[0:3, 0:3])
+    trans = desired[0:3, 3]
+    quat = rot_to_quat(desired[0:3, 0:3])
+
+    angle = 180 * _angle_between_quats(quat_est, quat) / np.pi
+    dist = np.sqrt(np.sum((trans - trans_est) ** 2))
+    assert_true(dist <= dist_tol, '%0.3f > %0.3f mm' % (1000 * dist,
+                                                        1000 * dist_tol))
+    assert_true(angle <= angle_tol, '%0.3f > %0.3f deg' % (angle, angle_tol))
+
+
+ at testing.requires_testing_data
+def test_data():
+    """Test reading raw Artemis123 files."""
+    _test_raw_reader(read_raw_artemis123, input_fname=short_hpi_1kz_fname,
+                     pos_fname=dig_fname, verbose='error')
+
+    # test a random selected point
+    raw = read_raw_artemis123(short_hpi_1kz_fname, preload=True,
+                              add_head_trans=False)
+    meg_picks = pick_types(raw.info, meg=True, eeg=False)
+
+    # checked against matlab reader.
+    assert_allclose(raw[meg_picks[12]][0][0][123], 1.08239606023e-11)
+
+    dev_head_t_1 = np.array([[9.713e-01, 2.340e-01, -4.164e-02, 1.302e-04],
+                             [-2.371e-01, 9.664e-01, -9.890e-02, 1.977e-03],
+                             [1.710e-02,   1.059e-01, 9.942e-01, -8.159e-03],
+                             [0.0, 0.0, 0.0, 1.0]])
+
+    dev_head_t_2 = np.array([[9.890e-01, 1.475e-01, -8.090e-03, 4.997e-04],
+                             [-1.476e-01, 9.846e-01, -9.389e-02, 1.962e-03],
+                             [-5.888e-03, 9.406e-02, 9.955e-01, -1.610e-02],
+                             [0.0, 0.0, 0.0, 1.0]])
+
+    # test with head loc no digitization
+    raw = read_raw_artemis123(short_HPI_dip_fname, add_head_trans=True)
+    _assert_trans(raw.info['dev_head_t']['trans'], dev_head_t_1)
+    assert_equal(raw.info['sfreq'], 5000.0)
+
+    # test with head loc and digitization
+    with warnings.catch_warnings(record=True):  # bad dig
+        raw = read_raw_artemis123(short_HPI_dip_fname,  add_head_trans=True,
+                                  pos_fname=dig_fname)
+    _assert_trans(raw.info['dev_head_t']['trans'], dev_head_t_1)
+
+    # test 1kz hpi head loc (different freq)
+    raw = read_raw_artemis123(short_hpi_1kz_fname, add_head_trans=True)
+    _assert_trans(raw.info['dev_head_t']['trans'], dev_head_t_2)
+    assert_equal(raw.info['sfreq'], 1000.0)
+
+
+def test_utils():
+    """Test artemis123 utils."""
+    # make a tempfile
+    tmp_dir = _TempDir()
+    tmp_fname = op.join(tmp_dir, 'test_gen_mne_locs.csv')
+    _generate_mne_locs_file(tmp_fname)
+    installed_locs = _load_mne_locs()
+    generated_locs = _load_mne_locs(tmp_fname)
+    assert_equal(set(installed_locs.keys()), set(generated_locs.keys()))
+    for key in installed_locs.keys():
+        assert_allclose(installed_locs[key], generated_locs[key], atol=1e-7)
+
+
+run_tests_if_main()
diff --git a/mne/io/artemis123/utils.py b/mne/io/artemis123/utils.py
new file mode 100644
index 0000000..dfca824
--- /dev/null
+++ b/mne/io/artemis123/utils.py
@@ -0,0 +1,138 @@
+import numpy as np
+import os.path as op
+from ...utils import logger
+from ...transforms import (rotation3d_align_z_axis, get_ras_to_neuromag_trans,
+                           apply_trans)
+from ..meas_info import _make_dig_points
+
+
+def _load_mne_locs(fname=None):
+    """Load MNE locs structure from file (if exists) or recreate it."""
+    if (not fname):
+        # find input file
+        resource_dir = op.join(op.dirname(op.abspath(__file__)), 'resources')
+        fname = op.join(resource_dir, 'Artemis123_mneLoc.csv')
+
+    if not op.exists(fname):
+        raise IOError('MNE locs file "%s" does not exist' % (fname))
+
+    logger.info('Loading mne loc file {}'.format(fname))
+    locs = dict()
+    with open(fname, 'r') as fid:
+        for line in fid:
+            vals = line.strip().split(',')
+            locs[vals[0]] = np.array(vals[1::], np.float)
+
+    return locs
+
+
+def _generate_mne_locs_file(output_fname):
+    """Generate mne coil locs and save to supplied file."""
+    logger.info('Converting Tristan coil file to mne loc file...')
+    resource_dir = op.join(op.dirname(op.abspath(__file__)), 'resources')
+    chan_fname = op.join(resource_dir, 'Artemis123_ChannelMap.csv')
+    chans = _load_tristan_coil_locs(chan_fname)
+
+    # compute a dict of loc structs
+    locs = {n: _compute_mne_loc(cinfo) for n, cinfo in chans.items()}
+
+    # write it out to output_fname
+    with open(output_fname, 'w') as fid:
+        for n in sorted(locs.keys()):
+            fid.write('%s,' % n)
+            fid.write(','.join(locs[n].astype(str)))
+            fid.write('\n')
+
+
+def _load_tristan_coil_locs(coil_loc_path):
+    """Load the Coil locations from Tristan CAD drawings."""
+    channel_info = dict()
+    with open(coil_loc_path, 'r') as fid:
+        # skip 2 Header lines
+        fid.readline()
+        fid.readline()
+        for line in fid:
+            line = line.strip()
+            vals = line.split(',')
+            channel_info[vals[0]] = dict()
+            if vals[6]:
+                channel_info[vals[0]]['inner_coil'] = \
+                    np.array(vals[2:5], np.float)
+                channel_info[vals[0]]['outer_coil'] = \
+                    np.array(vals[5:8], np.float)
+            else:  # nothing supplied
+                channel_info[vals[0]]['inner_coil'] = np.zeros(3)
+                channel_info[vals[0]]['outer_coil'] = np.zeros(3)
+    return channel_info
+
+
+def _compute_mne_loc(coil_loc):
+    """Convert a set of coils to an mne Struct.
+
+    Note input coil locations are in inches.
+    """
+    loc = np.zeros((12))
+    if (np.linalg.norm(coil_loc['inner_coil']) == 0) and \
+       (np.linalg.norm(coil_loc['outer_coil']) == 0):
+        return loc
+
+    # channel location is inner coil location converted to meters From inches
+    loc[0:3] = coil_loc['inner_coil'] / 39.370078
+
+    # figure out rotation
+    z_axis = coil_loc['outer_coil'] - coil_loc['inner_coil']
+    R = rotation3d_align_z_axis(z_axis)
+    loc[3:13] = R.T.reshape(9)
+    return loc
+
+
+def _read_pos(fname):
+    """Read the .pos file and return positions as dig points."""
+    nas = None
+    lpa = None
+    rpa = None
+    hpi = None
+    extra = None
+    with open(fname, 'r') as fid:
+        for line in fid:
+            line = line.strip()
+            if len(line) > 0:
+                parts = line.split()
+                # The lines can have 4 or 5 parts. First part is for the id,
+                # which can be an int or a string. The last three are for xyz
+                # coordinates. The extra part is for additional info
+                # (e.g. 'Pz', 'Cz') which is ignored.
+                if len(parts) not in [4, 5]:
+                    continue
+
+                if parts[0].lower() == 'nasion':
+                    nas = np.array([float(p) for p in parts[-3:]]) / 100.
+                elif parts[0].lower() == 'left':
+                    lpa = np.array([float(p) for p in parts[-3:]]) / 100.
+                elif parts[0].lower() == 'right':
+                    rpa = np.array([float(p) for p in parts[-3:]]) / 100.
+                elif 'hpi' in parts[0].lower():
+                    if hpi is None:
+                        hpi = list()
+                    hpi.append(np.array([float(p) for p in parts[-3:]]) / 100.)
+                else:
+                    if extra is None:
+                        extra = list()
+                    extra.append(np.array([float(p)
+                                           for p in parts[-3:]]) / 100.)
+    # move into MNE head coords
+    if ((nas is not None) and (lpa is not None) and (rpa is not None)):
+        neuromag_trans = get_ras_to_neuromag_trans(nas, lpa, rpa)
+        nas = apply_trans(neuromag_trans, nas)
+        lpa = apply_trans(neuromag_trans, lpa)
+        rpa = apply_trans(neuromag_trans, rpa)
+
+        if hpi is not None:
+            hpi = apply_trans(neuromag_trans, hpi)
+
+        if extra is not None:
+            extra = apply_trans(neuromag_trans, extra)
+
+    digs = _make_dig_points(nasion=nas, lpa=lpa, rpa=rpa, hpi=hpi,
+                            extra_points=extra)
+    return digs
diff --git a/mne/io/base.py b/mne/io/base.py
index 87746db..faa9a45 100644
--- a/mne/io/base.py
+++ b/mne/io/base.py
@@ -28,24 +28,29 @@ from .write import (start_file, end_file, start_block, end_block,
                     write_complex64, write_complex128, write_int,
                     write_id, write_string, write_name_list, _get_split_size)
 
+from ..annotations import _annotations_starts_stops
 from ..filter import (filter_data, notch_filter, resample, next_fast_len,
-                      _resample_stim_channels)
+                      _resample_stim_channels, _filt_check_picks,
+                      _filt_update_info)
 from ..parallel import parallel_func
 from ..utils import (_check_fname, _check_pandas_installed, sizeof_fmt,
-                     _check_pandas_index_arguments, _check_copy_dep,
+                     _check_pandas_index_arguments,
                      check_fname, _get_stim_channel,
                      logger, verbose, _time_mask, warn, SizeMixin,
-                     copy_function_doc_to_method_doc)
+                     copy_function_doc_to_method_doc,
+                     _check_preload, _scale_dep)
 from ..viz import plot_raw, plot_raw_psd, plot_raw_psd_topo
 from ..defaults import _handle_default
 from ..externals.six import string_types
 from ..event import find_events, concatenate_events
-from ..annotations import Annotations, _combine_annotations, _onset_to_seconds
+from ..annotations import Annotations, _combine_annotations, _sync_onset
 
 
 class ToDataFrameMixin(object):
     """Class to add to_data_frame capabilities to certain classes."""
+
     def _get_check_picks(self, picks, picks_check):
+        """Get and check picks."""
         if picks is None:
             picks = list(range(self.info['nchan']))
         else:
@@ -54,8 +59,9 @@ class ToDataFrameMixin(object):
                                  'in this object instance.')
         return picks
 
-    def to_data_frame(self, picks=None, index=None, scale_time=1e3,
-                      scalings=None, copy=True, start=None, stop=None):
+    def to_data_frame(self, picks=None, index=None, scaling_time=1e3,
+                      scalings=None, copy=True, start=None, stop=None,
+                      scale_time=None):
         """Export data in tabular structure as a pandas DataFrame.
 
         Columns and indices will depend on the object being converted.
@@ -73,7 +79,7 @@ class ToDataFrameMixin(object):
             Column to be used as index for the data. Valid string options
             are 'epoch', 'time' and 'condition'. If None, all three info
             columns will be included in the table as categorial data.
-        scale_time : float
+        scaling_time : float
             Scaling to be applied to time units.
         scalings : dict | None
             Scaling to be applied to the channels picked. If None, defaults to
@@ -97,9 +103,12 @@ class ToDataFrameMixin(object):
             depend on the object type being converted, but should be
             human-readable.
         """
-        from ..epochs import _BaseEpochs
+        from ..epochs import BaseEpochs
         from ..evoked import Evoked
         from ..source_estimate import _BaseSourceEstimate
+        scaling_time = _scale_dep(scaling_time, scale_time,
+                                  'scaling_time', 'scale_time')
+        del scale_time
 
         pd = _check_pandas_installed()
         mindex = list()
@@ -124,9 +133,9 @@ class ToDataFrameMixin(object):
             else:
                 # volume source estimates
                 col_names = ['VOL {0}'.format(vert) for vert in self.vertices]
-        elif isinstance(self, (_BaseEpochs, _BaseRaw, Evoked)):
+        elif isinstance(self, (BaseEpochs, BaseRaw, Evoked)):
             picks = self._get_check_picks(picks, self.ch_names)
-            if isinstance(self, _BaseEpochs):
+            if isinstance(self, BaseEpochs):
                 default_index = ['condition', 'epoch', 'time']
                 data = self.get_data()[:, picks, :]
                 times = self.times
@@ -142,14 +151,13 @@ class ToDataFrameMixin(object):
                               np.repeat(np.arange(n_epochs), n_times)))
                 col_names = [self.ch_names[k] for k in picks]
 
-            elif isinstance(self, (_BaseRaw, Evoked)):
+            elif isinstance(self, (BaseRaw, Evoked)):
                 default_index = ['time']
-                if isinstance(self, _BaseRaw):
+                if isinstance(self, BaseRaw):
                     data, times = self[picks, start:stop]
                 elif isinstance(self, Evoked):
                     data = self.data[picks, :]
                     times = self.times
-                    n_picks, n_times = data.shape
                 data = data.T
                 col_names = [self.ch_names[k] for k in picks]
 
@@ -165,7 +173,7 @@ class ToDataFrameMixin(object):
 
             for t in ch_types_used:
                 scaling = scalings[t]
-                idx = [picks[i] for i in range(len(picks)) if types[i] == t]
+                idx = [i for i in range(len(picks)) if types[i] == t]
                 if len(idx) > 0:
                     data[:, idx] *= scaling
         else:
@@ -174,7 +182,7 @@ class ToDataFrameMixin(object):
                             'SourceEstimate. This is {0}'.format(type(self)))
 
         # Make sure that the time index is scaled correctly
-        times = np.round(times * scale_time)
+        times = np.round(times * scaling_time)
         mindex.append(('time', times))
 
         if index is not None:
@@ -204,7 +212,7 @@ class TimeMixin(object):
     """Class to add sfreq and time_as_index capabilities to certain classes."""
 
     def time_as_index(self, times, use_rounding=False):
-        """Convert time to indices
+        """Convert time to indices.
 
         Parameters
         ----------
@@ -231,6 +239,7 @@ class TimeMixin(object):
 
 
 def _check_fun(fun, d, *args, **kwargs):
+    """Check shapes."""
     want_shape = d.shape
     d = fun(d, *args, **kwargs)
     if not isinstance(d, np.ndarray):
@@ -241,25 +250,68 @@ def _check_fun(fun, d, *args, **kwargs):
     return d
 
 
-class _BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
-               SetChannelsMixin, InterpolationMixin, ToDataFrameMixin,
-               TimeMixin, SizeMixin):
-    """Base class for Raw data
+class BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
+              SetChannelsMixin, InterpolationMixin, ToDataFrameMixin,
+              TimeMixin, SizeMixin):
+    """Base class for Raw data.
+
+    Parameters
+    ----------
+    info : dict
+        A dict passed from the subclass.
+    preload : bool | str | ndarray
+        Preload data into memory for data manipulation and faster indexing.
+        If True, the data will be preloaded into memory (fast, requires
+        large amount of memory). If preload is a string, preload is the
+        file name of a memory-mapped file which is used to store the data
+        on the hard drive (slower, requires less memory). If preload is an
+        ndarray, the data are taken from that array. If False, data are not
+        read until save.
+    first_samps : iterable
+        Iterable of the first sample number from each raw file. For unsplit raw
+        files this should be a length-one list or tuple.
+    last_samps : iterable | None
+        Iterable of the last sample number from each raw file. For unsplit raw
+        files this should be a length-one list or tuple. If None, then preload
+        must be an ndarray.
+    filenames : tuple
+        Tuple of length one (for unsplit raw files) or length > 1 (for split
+        raw files).
+    raw_extras : list
+        Whatever data is necessary for on-demand reads. For `RawFIF` this means
+        a list of variables formerly known as ``_rawdirs``.
+    orig_format : str
+        The data format of the original raw file (e.g., ``'double'``).
+    dtype : dtype | None
+        The dtype of the raw data. If preload is an ndarray, its dtype must
+        match what is passed here.
+    verbose : bool, str, int, or None
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
+
+    Notes
+    -----
+    The `BaseRaw` class is public to allow for stable type-checking in user
+    code (i.e., ``isinstance(my_raw_object, BaseRaw)``) but should not be used
+    as a constructor for `Raw` objects (use instead one of the subclass
+    constructors, or one of the ``mne.io.read_raw_*`` functions).
 
     Subclasses must provide the following methods:
 
         * _read_segment_file(self, data, idx, fi, start, stop, cals, mult)
           (only needed for types that support on-demand disk reads)
 
-    The `_BaseRaw._raw_extras` list can contain whatever data is necessary for
-    such on-demand reads. For `RawFIF` this means a list of variables formerly
-    known as ``_rawdirs``.
+    See Also
+    --------
+    mne.io.Raw : Documentation of attribute and methods.
     """
+
     @verbose
     def __init__(self, info, preload=False,
                  first_samps=(0,), last_samps=None,
                  filenames=(None,), raw_extras=(None,),
-                 orig_format='double', dtype=np.float64, verbose=None):
+                 orig_format='double', dtype=np.float64,
+                 verbose=None):  # noqa: D102
         # wait until the end to preload data, but triage here
         if isinstance(preload, np.ndarray):
             # some functions (e.g., filtering) only work w/64-bit data
@@ -293,6 +345,10 @@ class _BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
         cals = np.empty(info['nchan'])
         for k in range(info['nchan']):
             cals[k] = info['chs'][k]['range'] * info['chs'][k]['cal']
+        bad = np.where(cals == 0)[0]
+        if len(bad) > 0:
+            raise ValueError('Bad cals for channels %s'
+                             % dict((ii, self.ch_names[ii]) for ii in bad))
         self.verbose = verbose
         self._cals = cals
         self._raw_extras = list(raw_extras)
@@ -316,7 +372,7 @@ class _BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
 
     @verbose
     def apply_gradient_compensation(self, grade, verbose=None):
-        """Apply CTF gradient compensation
+        """Apply CTF gradient compensation.
 
         .. warning:: The compensation matrices are stored with single
                      precision, so repeatedly switching between different
@@ -331,7 +387,9 @@ class _BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
         grade : int
             CTF gradient compensation level.
         verbose : bool, str, int, or None
-            If not None, override default verbose level (see mne.verbose).
+            If not None, override default verbose level (see
+            :func:`mne.verbose` and :ref:`Logging documentation <tut_logging>`
+            for more).
 
         Returns
         -------
@@ -364,13 +422,13 @@ class _BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
 
     @property
     def _dtype(self):
-        """dtype for loading data (property so subclasses can override)"""
+        """Datatype for loading data (property so subclasses can override)."""
         # most classes only store real data, they won't need anything special
         return self._dtype_
 
     def _read_segment(self, start=0, stop=None, sel=None, data_buffer=None,
                       projector=None, verbose=None):
-        """Read a chunk of raw data
+        """Read a chunk of raw data.
 
         Parameters
         ----------
@@ -389,7 +447,9 @@ class _BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
         projector : array
             SSP operator to apply to the data.
         verbose : bool, str, int, or None
-            If not None, override default verbose level (see mne.verbose).
+            If not None, override default verbose level (see
+            :func:`mne.verbose` and :ref:`Logging documentation <tut_logging>`
+            for more).
 
         Returns
         -------
@@ -465,7 +525,7 @@ class _BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
         return data
 
     def _read_segment_file(self, data, idx, fi, start, stop, cals, mult):
-        """Read a segment of data from a file
+        """Read a segment of data from a file.
 
         Only needs to be implemented for readers that support
         ``preload=False``.
@@ -491,7 +551,7 @@ class _BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
 
     def _check_bad_segment(self, start, stop, picks,
                            reject_by_annotation=False):
-        """Function for checking if data segment is bad.
+        """Check if data segment is bad.
 
         If the slice is good, returns the data in desired range.
         If rejected based on annotation, returns description of the
@@ -520,7 +580,7 @@ class _BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
         if reject_by_annotation and self.annotations is not None:
             annot = self.annotations
             sfreq = self.info['sfreq']
-            onset = _onset_to_seconds(self, annot.onset)
+            onset = _sync_onset(self, annot.onset)
             overlaps = np.where(onset < stop / sfreq)
             overlaps = np.where(onset[overlaps] + annot.duration[overlaps] >
                                 start / sfreq)
@@ -531,12 +591,14 @@ class _BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
 
     @verbose
     def load_data(self, verbose=None):
-        """Load raw data
+        """Load raw data.
 
         Parameters
         ----------
         verbose : bool, str, int, or None
-            If not None, override default verbose level (see mne.verbose).
+            If not None, override default verbose level (see
+            :func:`mne.verbose` and :ref:`Logging documentation <tut_logging>`
+            for more).
 
         Returns
         -------
@@ -556,7 +618,7 @@ class _BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
 
     @verbose
     def _preload_data(self, preload, verbose=None):
-        """This function actually preloads the data"""
+        """Actually preload the data."""
         data_buffer = preload if isinstance(preload, (string_types,
                                                       np.ndarray)) else None
         logger.info('Reading %d ... %d  =  %9.3f ... %9.3f secs...' %
@@ -568,17 +630,23 @@ class _BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
         self.close()
 
     def _update_times(self):
-        """Helper to update times"""
+        """Update times."""
         self._times = np.arange(self.n_times) / float(self.info['sfreq'])
         # make it immutable
         self._times.flags.writeable = False
 
     @property
+    def _first_time(self):
+        return self.first_samp / float(self.info['sfreq'])
+
+    @property
     def first_samp(self):
+        """The first data sample."""
         return self._first_samps[0]
 
     @property
     def last_samp(self):
+        """The last data sample."""
         return self.first_samp + sum(self._raw_lengths) - 1
 
     @property
@@ -586,18 +654,27 @@ class _BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
         return [l - f + 1 for f, l in zip(self._first_samps, self._last_samps)]
 
     @property
-    def annotations(self):
+    def annotations(self):  # noqa: D401
         """Annotations for marking segments of data."""
         return self._annotations
 
+    @property
+    def filenames(self):
+        """The filenames used."""
+        return tuple(self._filenames)
+
     @annotations.setter
-    def annotations(self, annotations):
-        """Setter for annotations. Checks if they are inside the data range.
+    def annotations(self, annotations, emit_warning=True):
+        """Setter for annotations.
+
+        This setter checks if they are inside the data range.
 
         Parameters
         ----------
         annotations : Instance of mne.Annotations
             Annotations to set.
+        emit_warning : bool
+            Whether to emit warnings when limiting or omitting annotations.
         """
         if annotations is not None:
             if not isinstance(annotations, Annotations):
@@ -615,33 +692,38 @@ class _BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
             else:
                 offset = 0
             omit_ind = list()
+            omitted = limited = 0
             for ind, onset in enumerate(annotations.onset):
                 onset += offset
                 if onset > self.times[-1]:
-                    warn('Omitting annotation outside data range.')
+                    omitted += 1
                     omit_ind.append(ind)
                 elif onset < self.times[0]:
                     if onset + annotations.duration[ind] < self.times[0]:
-                        warn('Omitting annotation outside data range.')
+                        omitted += 1
                         omit_ind.append(ind)
                     else:
-                        warn('Annotation starting outside the data range. '
-                             'Limiting to the start of data.')
+                        limited += 1
                         duration = annotations.duration[ind] + onset
                         annotations.duration[ind] = duration
                         annotations.onset[ind] = self.times[0] - offset
                 elif onset + annotations.duration[ind] > self.times[-1]:
-                    warn('Annotation expanding outside the data range. '
-                         'Limiting to the end of data.')
+                    limited += 1
                     annotations.duration[ind] = self.times[-1] - onset
             annotations.onset = np.delete(annotations.onset, omit_ind)
             annotations.duration = np.delete(annotations.duration, omit_ind)
             annotations.description = np.delete(annotations.description,
                                                 omit_ind)
-
+            if emit_warning:
+                if omitted > 0:
+                    warn('Omitted %s annotation(s) that were outside data '
+                         'range.' % omitted)
+                if limited > 0:
+                    warn('Limited %s annotation(s) that were expanding '
+                         'outside the data range.' % limited)
         self._annotations = annotations
 
-    def __del__(self):
+    def __del__(self):  # noqa: D105
         # remove file for memmap
         if hasattr(self, '_data') and hasattr(self._data, 'filename'):
             # First, close the file out; happens automatically on del
@@ -654,17 +736,18 @@ class _BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
                 pass  # ignore file that no longer exists
 
     def __enter__(self):
-        """ Entering with block """
+        """Entering with block."""
         return self
 
     def __exit__(self, exception_type, exception_val, trace):
-        """ Exiting with block """
+        """Exit with block."""
         try:
             self.close()
-        except:
+        except Exception:
             return exception_type, exception_val, trace
 
     def _parse_get_set_params(self, item):
+        """Parse the __getitem__ / __setitem__ tuples."""
         # make sure item is a tuple
         if not isinstance(item, tuple):  # only channel selection passed
             item = (item, slice(None, None, None))
@@ -714,7 +797,7 @@ class _BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
         return sel, start, stop
 
     def __getitem__(self, item):
-        """Get raw data and times
+        """Get raw data and times.
 
         Parameters
         ----------
@@ -748,7 +831,7 @@ class _BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
             >>> t_idx = raw.time_as_index([10., 20.])  # doctest: +SKIP
             >>> data, times = raw[picks, t_idx[0]:t_idx[1]]  # doctest: +SKIP
 
-        """  # noqa
+        """  # noqa: E501
         sel, start, stop = self._parse_get_set_params(item)
         if self.preload:
             data = self._data[sel, start:stop]
@@ -760,15 +843,96 @@ class _BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
         return data, times
 
     def __setitem__(self, item, value):
-        """setting raw data content with python slicing"""
+        """Set raw data content."""
         _check_preload(self, 'Modifying data of Raw')
         sel, start, stop = self._parse_get_set_params(item)
         # set the data
         self._data[sel, start:stop] = value
 
+    def get_data(self, picks=None, start=0, stop=None,
+                 reject_by_annotation=None, return_times=False):
+        """Get data in the given range.
+
+        Parameters
+        ----------
+        picks : array-like of int | None
+            Indices of channels to get data from. If None, data from all
+            channels is returned
+        start : int
+            The first sample to include. Defaults to 0.
+        stop : int | None
+            End sample (first not to include). If None (default), the end of
+            the data is  used.
+        reject_by_annotation : None | 'omit' | 'NaN'
+            Whether to reject by annotation. If None (default), no rejection is
+            done. If 'omit', segments annotated with description starting with
+            'bad' are omitted. If 'NaN', the bad samples are filled with NaNs.
+        return_times : bool
+            Whether to return times as well. Defaults to False.
+
+        Returns
+        -------
+        data : ndarray, shape (n_channels, n_times)
+            Copy of the data in the given range.
+        times : ndarray, shape (n_times,)
+            Times associated with the data samples. Only returned if
+            return_times=True.
+
+        Notes
+        -----
+        .. versionadded:: 0.14.0
+        """
+        if picks is None:
+            picks = np.arange(self.info['nchan'])
+        start = 0 if start is None else start
+        stop = min(self.n_times if stop is None else stop, self.n_times)
+        if self.annotations is None or reject_by_annotation is None:
+            data, times = self[picks, start:stop]
+            return (data, times) if return_times else data
+        if reject_by_annotation.lower() not in ['omit', 'nan']:
+            raise ValueError("reject_by_annotation must be None, 'omit' or "
+                             "'NaN'. Got %s." % reject_by_annotation)
+        onsets, ends = _annotations_starts_stops(self, ['BAD'])
+        keep = (onsets < stop) & (ends > start)
+        onsets = np.maximum(onsets[keep], start)
+        ends = np.minimum(ends[keep], stop)
+        if len(onsets) == 0:
+            data, times = self[picks, start:stop]
+            if return_times:
+                return data, times
+            return data
+
+        used = np.ones(stop - start, bool)
+        for onset, end in zip(onsets, ends):
+            if onset >= end:
+                continue
+            used[onset - start: end - start] = False
+        used = np.concatenate([[False], used, [False]])
+        starts = np.where(~used[:-1] & used[1:])[0] + start
+        stops = np.where(used[:-1] & ~used[1:])[0] + start
+        if reject_by_annotation == 'omit':
+
+            data = np.zeros((len(picks), (stops - starts).sum()))
+            times = np.zeros(data.shape[1])
+            idx = 0
+            for start, stop in zip(starts, stops):  # get the data
+                if start == stop:
+                    continue
+                end = idx + stop - start
+                data[:, idx:end], times[idx:end] = self[picks, start:stop]
+                idx = end
+        else:
+            data, times = self[picks, start:stop]
+            data[:, ~used[1:-1]] = np.nan
+
+        if return_times:
+            return data, times
+        return data
+
     @verbose
-    def apply_function(self, fun, picks, dtype, n_jobs, *args, **kwargs):
-        """ Apply a function to a subset of channels.
+    def apply_function(self, fun, picks=None, dtype=None,
+                       n_jobs=1, *args, **kwargs):
+        """Apply a function to a subset of channels.
 
         The function "fun" is applied to the channels defined in "picks". The
         data of the Raw object is modified inplace. If the function returns
@@ -793,13 +957,13 @@ class _BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
             A function to be applied to the channels. The first argument of
             fun has to be a timeseries (numpy.ndarray). The function must
             return an numpy.ndarray with the same size as the input.
-        picks : array-like of int | None
-            Indices of channels to apply the function to. If None, all
-            M-EEG channels are used.
-        dtype : numpy.dtype
+        picks : array-like of int (default: None)
+            Indices of channels to apply the function to. If None, all data
+            channels are used.
+        dtype : numpy.dtype (default: None)
             Data type to use for raw data after applying the function. If None
             the data type is not modified.
-        n_jobs: int
+        n_jobs: int (default: 1)
             Number of jobs to run in parallel.
         *args :
             Additional positional arguments to pass to fun (first pos. argument
@@ -807,7 +971,13 @@ class _BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
         **kwargs :
             Keyword arguments to pass to fun. Note that if "verbose" is passed
             as a member of ``kwargs``, it will be consumed and will override
-            the default mne-python verbose level (see mne.verbose).
+            the default mne-python verbose level (see :func:`mne.verbose` and
+            :ref:`Logging documentation <tut_logging>` for more).
+
+        Returns
+        -------
+        self : instance of Raw
+            The raw object with transformed data.
         """
         _check_preload(self, 'raw.apply_function')
         if picks is None:
@@ -833,11 +1003,12 @@ class _BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
                                       for p in picks)
             for pp, p in enumerate(picks):
                 self._data[p, :] = data_picks_new[pp]
+        return self
 
     @verbose
-    def apply_hilbert(self, picks, envelope=False, n_jobs=1, n_fft='',
+    def apply_hilbert(self, picks=None, envelope=False, n_jobs=1, n_fft='auto',
                       verbose=None):
-        """ Compute analytic signal or envelope for a subset of channels.
+        """Compute analytic signal or envelope for a subset of channels.
 
         If envelope=False, the analytic signal for the channels defined in
         "picks" is computed and the data of the Raw object is converted to
@@ -863,8 +1034,9 @@ class _BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
 
         Parameters
         ----------
-        picks : array-like of int
-            Indices of channels to apply the function to.
+        picks : array-like of int (default: None)
+            Indices of channels to apply the function to. If None, all data
+            channels are used.
         envelope : bool (default: False)
             Compute the envelope signal of each channel.
         n_jobs: int
@@ -875,8 +1047,14 @@ class _BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
             to original length. If None, n == self.n_times. If 'auto',
             the next highest fast FFT length will be use.
         verbose : bool, str, int, or None
-            If not None, override default verbose level (see mne.verbose).
-            Defaults to self.verbose.
+            If not None, override default verbose level (see
+            :func:`mne.verbose` and :ref:`Logging documentation <tut_logging>`
+            for more). Defaults to self.verbose.
+
+        Returns
+        -------
+        self : instance of Raw
+            The raw object with transformed data.
 
         Notes
         -----
@@ -900,27 +1078,26 @@ class _BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
         if n_fft is None:
             n_fft = len(self.times)
         elif isinstance(n_fft, string_types):
-            if n_fft == '':
-                n_fft = len(self.times)
-                warn('n_fft is None by default in 0.13 but will change to '
-                     '"auto" in 0.14', DeprecationWarning)
-            elif n_fft == 'auto':
-                n_fft = next_fast_len(len(self.times))
+            if n_fft != 'auto':
+                raise ValueError('n_fft must be an integer, string, or None, '
+                                 'got %s' % (type(n_fft),))
+            n_fft = next_fast_len(len(self.times))
         n_fft = int(n_fft)
         if n_fft < self.n_times:
             raise ValueError("n_fft must be greater than n_times")
         if envelope is True:
-            self.apply_function(_my_hilbert, picks, None, n_jobs, n_fft,
-                                envelope=envelope)
+            dtype = None
         else:
-            self.apply_function(_my_hilbert, picks, np.complex64, n_jobs,
-                                n_fft, envelope=envelope)
+            dtype = np.complex64
+        return self.apply_function(_my_hilbert, picks, dtype, n_jobs, n_fft,
+                                   envelope=envelope)
 
     @verbose
-    def filter(self, l_freq, h_freq, picks=None, filter_length='',
-               l_trans_bandwidth=None, h_trans_bandwidth=None, n_jobs=1,
-               method='fir', iir_params=None, phase='', fir_window='',
-               verbose=None):
+    def filter(self, l_freq, h_freq, picks=None, filter_length='auto',
+               l_trans_bandwidth='auto', h_trans_bandwidth='auto', n_jobs=1,
+               method='fir', iir_params=None, phase='zero',
+               fir_window='hamming', fir_design=None,
+               skip_by_annotation=None, pad='reflect_limited', verbose=None):
         """Filter a subset of channels.
 
         Applies a zero-phase low-pass, high-pass, band-pass, or band-stop
@@ -958,21 +1135,22 @@ class _BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
         filter_length : str | int
             Length of the FIR filter to use (if applicable):
 
-                * int: specified length in samples.
-                * 'auto' (default in 0.14): the filter length is chosen based
-                  on the size of the transition regions (6.6 times the
-                  reciprocal of the shortest transition band for
-                  fir_window='hamming').
-                * str: (default in 0.13 is "10s") a human-readable time in
-                  units of "s" or "ms" (e.g., "10s" or "5500ms") will be
-                  converted to that number of samples if ``phase="zero"``, or
-                  the shortest power-of-two length at least that duration for
-                  ``phase="zero-double"``.
+            * 'auto' (default): the filter length is chosen based
+              on the size of the transition regions (6.6 times the reciprocal
+              of the shortest transition band for fir_window='hamming'
+              and fir_design="firwin2", and half that for "firwin").
+            * str: a human-readable time in
+              units of "s" or "ms" (e.g., "10s" or "5500ms") will be
+              converted to that number of samples if ``phase="zero"``, or
+              the shortest power-of-two length at least that duration for
+              ``phase="zero-double"``.
+            * int: specified length in samples. For fir_design="firwin",
+              this should not be used.
 
         l_trans_bandwidth : float | str
             Width of the transition band at the low cut-off frequency in Hz
             (high pass or cutoff 1 in bandpass). Can be "auto"
-            (default in 0.14) to use a multiple of ``l_freq``::
+            (default) to use a multiple of ``l_freq``::
 
                 min(max(l_freq * 0.25, 2), l_freq)
 
@@ -980,7 +1158,7 @@ class _BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
         h_trans_bandwidth : float | str
             Width of the transition band at the high cut-off frequency in Hz
             (low pass or cutoff 2 in bandpass). Can be "auto"
-            (default in 0.14) to use a multiple of ``h_freq``::
+            (default) to use a multiple of ``h_freq``::
 
                 min(max(h_freq * 0.25, 2.), info['sfreq'] / 2. - h_freq)
 
@@ -998,22 +1176,48 @@ class _BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
         phase : str
             Phase of the filter, only used if ``method='fir'``.
             By default, a symmetric linear-phase FIR filter is constructed.
-            If ``phase='zero'`` (default in 0.14), the delay of this filter
-            is compensated for. If ``phase=='zero-double'`` (default in 0.13
-            and before), then this filter is applied twice, once forward, and
-            once backward.
+            If ``phase='zero'`` (default), the delay of this filter
+            is compensated for. If ``phase=='zero-double'``, then this filter
+            is applied twice, once forward, and once backward. If 'minimum',
+            then a minimum-phase, causal filter will be used.
 
             .. versionadded:: 0.13
-
         fir_window : str
-            The window to use in FIR design, can be "hamming" (default in
-            0.14), "hann" (default in 0.13), or "blackman".
+            The window to use in FIR design, can be "hamming" (default),
+            "hann" (default in 0.13), or "blackman".
 
             .. versionadded:: 0.13
+        fir_design : str
+            Can be "firwin" (default in 0.16) to use
+            :func:`scipy.signal.firwin`, or "firwin2" (default in 0.15 and
+            before) to use :func:`scipy.signal.firwin2`. "firwin" uses a
+            time-domain design technique that generally gives improved
+            attenuation using fewer samples than "firwin2".
+
+            .. versionadded:: 0.15
+
+        skip_by_annotation : str | list of str
+            If a string (or list of str), any annotation segment that begins
+            with the given string will not be included in filtering, and
+            segments on either side of the given excluded annotated segment
+            will be filtered separately (i.e., as independent signals).
+            The default in 0.16 (``'edge'``) will separately filter any
+            segments that were concatenated by :func:`mne.concatenate_raws`
+            or :meth:`mne.io.Raw.append`. To disable, provide an empty list.
+
+            .. versionadded:: 0.16.
+        pad : str
+            The type of padding to use. Supports all :func:`numpy.pad` ``mode``
+            options. Can also be "reflect_limited" (default), which pads with a
+            reflected version of each vector mirrored on the first and last
+            values of the vector, followed by zeros.
+            Only used for ``method='fir'``.
 
+            .. versionadded:: 0.15
         verbose : bool, str, int, or None
-            If not None, override default verbose level (see mne.verbose).
-            Defaults to self.verbose.
+            If not None, override default verbose level (see
+            :func:`mne.verbose` and :ref:`Logging documentation <tut_logging>`
+            for more). Defaults to self.verbose.
 
         Returns
         -------
@@ -1034,45 +1238,42 @@ class _BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
         and :ref:`tut_artifacts_filter`.
         """
         _check_preload(self, 'raw.filter')
-        data_picks = _pick_data_or_ica(self.info)
-        update_info = False
-        if picks is None:
-            picks = data_picks
-            update_info = True
-            # let's be safe.
-            if len(picks) == 0:
-                raise RuntimeError('Could not find any valid channels for '
-                                   'your Raw object. Please contact the '
-                                   'MNE-Python developers.')
-        elif h_freq is not None or l_freq is not None:
-            if np.in1d(data_picks, picks).all():
-                update_info = True
-            else:
-                logger.info('Filtering a subset of channels. The highpass and '
-                            'lowpass values in the measurement info will not '
-                            'be updated.')
-        filter_data(self._data, self.info['sfreq'], l_freq, h_freq, picks,
-                    filter_length, l_trans_bandwidth, h_trans_bandwidth,
-                    n_jobs, method, iir_params, copy=False, phase=phase,
-                    fir_window=fir_window)
+        update_info, picks = _filt_check_picks(self.info, picks,
+                                               l_freq, h_freq)
+        # Deal with annotations
+        if skip_by_annotation is None:
+            if self.annotations is not None and any(
+                    desc.upper().startswith('EDGE')
+                    for desc in self.annotations.description):
+                warn('skip_by_annotation defaults to [] in 0.15 but will '
+                     'change to "edge" in 0.16, set it explicitly to avoid '
+                     'this warning', DeprecationWarning)
+            skip_by_annotation = []
+        onsets, ends = _annotations_starts_stops(self, skip_by_annotation,
+                                                 'skip_by_annotation')
+        if len(onsets) == 0 or onsets[0] != 0:
+            onsets = np.concatenate([[0], onsets])
+            ends = np.concatenate([[0], ends])
+        if len(ends) == 1 or ends[-1] != len(self.times):
+            onsets = np.concatenate([onsets, [len(self.times)]])
+            ends = np.concatenate([ends, [len(self.times)]])
+        for start, stop in zip(ends[:-1], onsets[1:]):
+            filter_data(
+                self._data[:, start:stop], self.info['sfreq'], l_freq, h_freq,
+                picks, filter_length, l_trans_bandwidth, h_trans_bandwidth,
+                n_jobs, method, iir_params, copy=False, phase=phase,
+                fir_window=fir_window, fir_design=fir_design, pad=pad)
         # update info if filter is applied to all data channels,
         # and it's not a band-stop filter
-        if update_info:
-            if h_freq is not None and (l_freq is None or l_freq < h_freq) and \
-                    (self.info["lowpass"] is None or
-                     h_freq < self.info['lowpass']):
-                self.info['lowpass'] = float(h_freq)
-            if l_freq is not None and (h_freq is None or l_freq < h_freq) and \
-                    (self.info["highpass"] is None or
-                     l_freq > self.info['highpass']):
-                self.info['highpass'] = float(l_freq)
+        _filt_update_info(self.info, update_info, l_freq, h_freq)
         return self
 
     @verbose
-    def notch_filter(self, freqs, picks=None, filter_length='',
+    def notch_filter(self, freqs, picks=None, filter_length='auto',
                      notch_widths=None, trans_bandwidth=1.0, n_jobs=1,
                      method='fft', iir_params=None, mt_bandwidth=None,
-                     p_value=0.05, phase='', fir_window='', verbose=None):
+                     p_value=0.05, phase='zero', fir_window='hamming',
+                     fir_design=None, pad='reflect_limited', verbose=None):
         """Notch filter a subset of channels.
 
         Applies a zero-phase notch filter to the channels selected by
@@ -1099,11 +1300,11 @@ class _BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
             Length of the FIR filter to use (if applicable):
 
                 * int: specified length in samples.
-                * 'auto' (default in 0.14): the filter length is chosen based
+                * 'auto' (default): the filter length is chosen based
                   on the size of the transition regions (6.6 times the
                   reciprocal of the shortest transition band for
                   fir_window='hamming').
-                * str: (default in 0.13 is "10s") a human-readable time in
+                * str: a human-readable time in
                   units of "s" or "ms" (e.g., "10s" or "5500ms") will be
                   converted to that number of samples if ``phase="zero"``, or
                   the shortest power-of-two length at least that duration for
@@ -1137,22 +1338,37 @@ class _BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
         phase : str
             Phase of the filter, only used if ``method='fir'``.
             By default, a symmetric linear-phase FIR filter is constructed.
-            If ``phase='zero'`` (default in 0.14), the delay of this filter
-            is compensated for. If ``phase=='zero-double'`` (default in 0.13
-            and before), then this filter is applied twice, once forward, and
-            once backward.
+            If ``phase='zero'`` (default), the delay of this filter
+            is compensated for. If ``phase=='zero-double'``, then this filter
+            is applied twice, once forward, and once backward. If 'minimum',
+            then a minimum-phase, causal filter will be used.
 
             .. versionadded:: 0.13
-
         fir_window : str
-            The window to use in FIR design, can be "hamming" (default in
-            0.14), "hann" (default in 0.13), or "blackman".
+            The window to use in FIR design, can be "hamming" (default),
+            "hann", or "blackman".
 
             .. versionadded:: 0.13
+        fir_design : str
+            Can be "firwin" (default in 0.16) to use
+            :func:`scipy.signal.firwin`, or "firwin2" (default in 0.15 and
+            before) to use :func:`scipy.signal.firwin2`. "firwin" uses a
+            time-domain design technique that generally gives improved
+            attenuation using fewer samples than "firwin2".
+
+            ..versionadded:: 0.15
+        pad : str
+            The type of padding to use. Supports all :func:`numpy.pad` ``mode``
+            options. Can also be "reflect_limited" (default), which pads with a
+            reflected version of each vector mirrored on the first and last
+            values of the vector, followed by zeros.
+            Only used for ``method='fir'``.
 
+            .. versionadded:: 0.15
         verbose : bool, str, int, or None
-            If not None, override default verbose level (see mne.verbose).
-            Defaults to self.verbose.
+            If not None, override default verbose level (see
+            :func:`mne.verbose` and :ref:`Logging documentation <tut_logging>`
+            for more). Defaults to self.verbose.
 
         Returns
         -------
@@ -1181,12 +1397,13 @@ class _BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
             notch_widths=notch_widths, trans_bandwidth=trans_bandwidth,
             method=method, iir_params=iir_params, mt_bandwidth=mt_bandwidth,
             p_value=p_value, picks=picks, n_jobs=n_jobs, copy=False,
-            phase=phase, fir_window=fir_window)
+            phase=phase, fir_window=fir_window, fir_design=fir_design,
+            pad=pad)
         return self
 
     @verbose
     def resample(self, sfreq, npad='auto', window='boxcar', stim_picks=None,
-                 n_jobs=1, events=None, copy=None, verbose=None):
+                 n_jobs=1, events=None, pad='reflect_limited', verbose=None):
         """Resample all channels.
 
         The Raw object has to have the data loaded e.g. with ``preload=True``
@@ -1205,7 +1422,8 @@ class _BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
                      If resampling the continuous data is desired, it is
                      recommended to construct events using the original data.
                      The event onsets can be jointly resampled with the raw
-                     data using the 'events' parameter.
+                     data using the 'events' parameter (a resampled copy is
+                     returned).
 
         Parameters
         ----------
@@ -1229,18 +1447,26 @@ class _BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
             is installed properly and CUDA is initialized.
         events : 2D array, shape (n_events, 3) | None
             An optional event matrix. When specified, the onsets of the events
-            are resampled jointly with the data.
-        copy : bool
-            Whether to operate on a copy of the data (True) or modify data
-            in-place (False). Defaults to False.
+            are resampled jointly with the data. NB: The input events are not
+            modified, but a new array is returned with the raw instead.
+        pad : str
+            The type of padding to use. Supports all :func:`numpy.pad` ``mode``
+            options. Can also be "reflect_limited" (default), which pads with a
+            reflected version of each vector mirrored on the first and last
+            values of the vector, followed by zeros.
+
+            .. versionadded:: 0.15
         verbose : bool, str, int, or None
-            If not None, override default verbose level (see mne.verbose).
-            Defaults to self.verbose.
+            If not None, override default verbose level (see
+            :func:`mne.verbose` and :ref:`Logging documentation <tut_logging>`
+            for more). Defaults to self.verbose.
 
         Returns
         -------
         raw : instance of Raw
             The resampled version of the raw object.
+        events : 2D array, shape (n_events, 3) | None
+            If events are jointly resampled, these are returned with the raw.
 
         See Also
         --------
@@ -1251,9 +1477,8 @@ class _BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
         -----
         For some data, it may be more accurate to use ``npad=0`` to reduce
         artifacts. This is dataset dependent -- check your data!
-        """  # noqa
+        """  # noqa: E501
         _check_preload(self, 'raw.resample')
-        inst = _check_copy_dep(self, copy)
 
         # When no event object is supplied, some basic detection of dropped
         # events is performed to generate a warning. Finding events can fail
@@ -1262,28 +1487,28 @@ class _BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
         # warning should simply not be generated in this case.
         if events is None:
             try:
-                original_events = find_events(inst)
-            except:
+                original_events = find_events(self)
+            except Exception:
                 pass
 
         sfreq = float(sfreq)
-        o_sfreq = float(inst.info['sfreq'])
+        o_sfreq = float(self.info['sfreq'])
 
-        offsets = np.concatenate(([0], np.cumsum(inst._raw_lengths)))
+        offsets = np.concatenate(([0], np.cumsum(self._raw_lengths)))
         new_data = list()
 
         ratio = sfreq / o_sfreq
 
         # set up stim channel processing
         if stim_picks is None:
-            stim_picks = pick_types(inst.info, meg=False, ref_meg=False,
+            stim_picks = pick_types(self.info, meg=False, ref_meg=False,
                                     stim=True, exclude=[])
         stim_picks = np.asanyarray(stim_picks)
 
-        for ri in range(len(inst._raw_lengths)):
-            data_chunk = inst._data[:, offsets[ri]:offsets[ri + 1]]
+        for ri in range(len(self._raw_lengths)):
+            data_chunk = self._data[:, offsets[ri]:offsets[ri + 1]]
             new_data.append(resample(data_chunk, sfreq, o_sfreq, npad,
-                                     window=window, n_jobs=n_jobs))
+                                     window=window, n_jobs=n_jobs, pad=pad))
             new_ntimes = new_data[ri].shape[1]
 
             # In empirical testing, it was faster to resample all channels
@@ -1296,41 +1521,41 @@ class _BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
                     data_chunk.shape[1])
                 new_data[ri][stim_picks] = stim_resampled
 
-            inst._first_samps[ri] = int(inst._first_samps[ri] * ratio)
-            inst._last_samps[ri] = inst._first_samps[ri] + new_ntimes - 1
-            inst._raw_lengths[ri] = new_ntimes
+            self._first_samps[ri] = int(self._first_samps[ri] * ratio)
+            self._last_samps[ri] = self._first_samps[ri] + new_ntimes - 1
+            self._raw_lengths[ri] = new_ntimes
 
-        inst._data = np.concatenate(new_data, axis=1)
-        inst.info['sfreq'] = sfreq
-        if inst.info.get('lowpass') is not None:
-            inst.info['lowpass'] = min(inst.info['lowpass'], sfreq / 2.)
-        inst._update_times()
+        self._data = np.concatenate(new_data, axis=1)
+        self.info['sfreq'] = sfreq
+        if self.info.get('lowpass') is not None:
+            self.info['lowpass'] = min(self.info['lowpass'], sfreq / 2.)
+        self._update_times()
 
         # See the comment above why we ignore all errors here.
         if events is None:
             try:
                 # Did we loose events?
-                resampled_events = find_events(inst)
+                resampled_events = find_events(self)
                 if len(resampled_events) != len(original_events):
                     warn('Resampling of the stim channels caused event '
                          'information to become unreliable. Consider finding '
                          'events on the original data and passing the event '
                          'matrix as a parameter.')
-            except:
+            except Exception:
                 pass
 
-            return inst
+            return self
         else:
-            if copy:
-                events = events.copy()
+            # always make a copy of events
+            events = events.copy()
 
             events[:, 0] = np.minimum(
                 np.round(events[:, 0] * ratio).astype(int),
-                inst._data.shape[1]
+                self._data.shape[1] + self.first_samp
             )
-            return inst, events
+            return self, events
 
-    def crop(self, tmin=0.0, tmax=None, copy=None):
+    def crop(self, tmin=0.0, tmax=None):
         """Crop raw data file.
 
         Limit the data from the raw file to go between specific times. Note
@@ -1344,18 +1569,13 @@ class _BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
             New start time in seconds (must be >= 0).
         tmax : float | None
             New end time in seconds of the data (cannot exceed data duration).
-        copy : bool
-            This parameter has been deprecated and will be removed in 0.14.
-            Use inst.copy() instead.
-            Whether to return a new instance or modify in place.
 
         Returns
         -------
         raw : instance of Raw
             The cropped raw object.
         """
-        raw = _check_copy_dep(self, copy)
-        max_time = (raw.n_times - 1) / raw.info['sfreq']
+        max_time = (self.n_times - 1) / self.info['sfreq']
         if tmax is None:
             tmax = max_time
 
@@ -1369,36 +1589,38 @@ class _BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
 
         smin, smax = np.where(_time_mask(self.times, tmin, tmax,
                                          sfreq=self.info['sfreq']))[0][[0, -1]]
-        cumul_lens = np.concatenate(([0], np.array(raw._raw_lengths,
+        cumul_lens = np.concatenate(([0], np.array(self._raw_lengths,
                                                    dtype='int')))
         cumul_lens = np.cumsum(cumul_lens)
         keepers = np.logical_and(np.less(smin, cumul_lens[1:]),
                                  np.greater_equal(smax, cumul_lens[:-1]))
         keepers = np.where(keepers)[0]
-        raw._first_samps = np.atleast_1d(raw._first_samps[keepers])
+        self._first_samps = np.atleast_1d(self._first_samps[keepers])
         # Adjust first_samp of first used file!
-        raw._first_samps[0] += smin - cumul_lens[keepers[0]]
-        raw._last_samps = np.atleast_1d(raw._last_samps[keepers])
-        raw._last_samps[-1] -= cumul_lens[keepers[-1] + 1] - 1 - smax
-        raw._raw_extras = [r for ri, r in enumerate(raw._raw_extras)
+        self._first_samps[0] += smin - cumul_lens[keepers[0]]
+        self._last_samps = np.atleast_1d(self._last_samps[keepers])
+        self._last_samps[-1] -= cumul_lens[keepers[-1] + 1] - 1 - smax
+        self._raw_extras = [r for ri, r in enumerate(self._raw_extras)
+                            if ri in keepers]
+        self._filenames = [r for ri, r in enumerate(self._filenames)
                            if ri in keepers]
-        raw._filenames = [r for ri, r in enumerate(raw._filenames)
-                          if ri in keepers]
-        if raw.preload:
+        if self.preload:
             # slice and copy to avoid the reference to large array
-            raw._data = raw._data[:, smin:smax + 1].copy()
-        raw._update_times()
-        if raw.annotations is not None:
-            annotations = raw.annotations
+            self._data = self._data[:, smin:smax + 1].copy()
+        self._update_times()
+
+        if self.annotations is not None:
+            annotations = self.annotations
             annotations.onset -= tmin
-            raw.annotations = annotations
-        return raw
+            BaseRaw.annotations.fset(self, annotations, emit_warning=False)
+
+        return self
 
     @verbose
     def save(self, fname, picks=None, tmin=0, tmax=None, buffer_size_sec=None,
              drop_small_buffer=False, proj=False, fmt='single',
              overwrite=False, split_size='2GB', verbose=None):
-        """Save raw data to file
+        """Save raw data to file.
 
         Parameters
         ----------
@@ -1450,8 +1672,9 @@ class _BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
                       size is 2GB.
 
         verbose : bool, str, int, or None
-            If not None, override default verbose level (see mne.verbose).
-            Defaults to self.verbose.
+            If not None, override default verbose level (see
+            :func:`mne.verbose` and :ref:`Logging documentation <tut_logging>`
+            for more). Defaults to self.verbose.
 
         Notes
         -----
@@ -1528,42 +1751,50 @@ class _BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
     @copy_function_doc_to_method_doc(plot_raw)
     def plot(self, events=None, duration=10.0, start=0.0, n_channels=20,
              bgcolor='w', color=None, bad_color=(0.8, 0.8, 0.8),
-             event_color='cyan', scalings=None, remove_dc=True, order='type',
+             event_color='cyan', scalings=None, remove_dc=True, order=None,
              show_options=False, title=None, show=True, block=False,
-             highpass=None, lowpass=None, filtorder=4, clipping=None):
+             highpass=None, lowpass=None, filtorder=4, clipping=None,
+             show_first_samp=False, proj=True, group_by='type',
+             butterfly=False, decim='auto'):
         return plot_raw(self, events, duration, start, n_channels, bgcolor,
                         color, bad_color, event_color, scalings, remove_dc,
                         order, show_options, title, show, block, highpass,
-                        lowpass, filtorder, clipping)
+                        lowpass, filtorder, clipping, show_first_samp, proj,
+                        group_by, butterfly, decim)
 
     @verbose
     @copy_function_doc_to_method_doc(plot_raw_psd)
-    def plot_psd(self, tmin=0.0, tmax=60.0, fmin=0, fmax=np.inf,
-                 proj=False, n_fft=2048, picks=None, ax=None,
+    def plot_psd(self, tmin=0.0, tmax=np.inf, fmin=0, fmax=np.inf,
+                 proj=False, n_fft=None, picks=None, ax=None,
                  color='black', area_mode='std', area_alpha=0.33,
-                 n_overlap=0, dB=True, show=True, n_jobs=1, verbose=None):
-        return plot_raw_psd(self, tmin=tmin, tmax=tmax, fmin=fmin, fmax=fmax,
-                            proj=proj, n_fft=n_fft, picks=picks, ax=ax,
-                            color=color, area_mode=area_mode,
-                            area_alpha=area_alpha, n_overlap=n_overlap,
-                            dB=dB, show=show, n_jobs=n_jobs)
+                 n_overlap=0, dB=True, estimate='auto', average=None,
+                 show=True, n_jobs=1, line_alpha=None, spatial_colors=None,
+                 xscale='linear', reject_by_annotation=True, verbose=None):
+        return plot_raw_psd(
+            self, tmin=tmin, tmax=tmax, fmin=fmin, fmax=fmax, proj=proj,
+            n_fft=n_fft, picks=picks, ax=ax, color=color, area_mode=area_mode,
+            area_alpha=area_alpha, n_overlap=n_overlap, dB=dB,
+            estimate=estimate, average=average, show=show, n_jobs=n_jobs,
+            line_alpha=line_alpha, spatial_colors=spatial_colors,
+            xscale=xscale, reject_by_annotation=reject_by_annotation)
 
     @copy_function_doc_to_method_doc(plot_raw_psd_topo)
     def plot_psd_topo(self, tmin=0., tmax=None, fmin=0, fmax=100, proj=False,
                       n_fft=2048, n_overlap=0, layout=None, color='w',
                       fig_facecolor='k', axis_facecolor='k', dB=True,
-                      show=True, block=False, n_jobs=1, verbose=None):
+                      show=True, block=False, n_jobs=1, axes=None,
+                      verbose=None):
         return plot_raw_psd_topo(self, tmin=tmin, tmax=tmax, fmin=fmin,
                                  fmax=fmax, proj=proj, n_fft=n_fft,
                                  n_overlap=n_overlap, layout=layout,
                                  color=color, fig_facecolor=fig_facecolor,
                                  axis_facecolor=axis_facecolor, dB=dB,
                                  show=show, block=block, n_jobs=n_jobs,
-                                 verbose=verbose)
+                                 axes=axes, verbose=verbose)
 
     def estimate_rank(self, tstart=0.0, tstop=30.0, tol=1e-4,
                       return_singular=False, picks=None, scalings='norm'):
-        """Estimate rank of the raw data
+        """Estimate rank of the raw data.
 
         This function is meant to provide a reasonable estimate of the rank.
         The true rank of the data depends on many factors, so use at your
@@ -1641,21 +1872,21 @@ class _BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
 
     @property
     def ch_names(self):
-        """Channel names"""
+        """Channel names."""
         return self.info['ch_names']
 
     @property
     def times(self):
-        """Time points"""
+        """Time points."""
         return self._times
 
     @property
     def n_times(self):
-        """Number of time points"""
+        """Number of time points."""
         return self.last_samp - self.first_samp + 1
 
     def __len__(self):
-        """The number of time points
+        """Return the number of time points.
 
         Returns
         -------
@@ -1673,8 +1904,7 @@ class _BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
         return self.n_times
 
     def load_bad_channels(self, bad_file=None, force=False):
-        """
-        Mark channels as bad from a text file
+        """Mark channels as bad from a text file.
 
         This function operates mostly in the style of the C function
         ``mne_mark_bad_channels``.
@@ -1690,7 +1920,6 @@ class _BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
             that exist) if channels are not found, instead of
             raising an error.
         """
-
         if bad_file is not None:
             # Check to make sure bad channels are there
             names = frozenset(self.info['ch_names'])
@@ -1703,16 +1932,21 @@ class _BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
                 if not force:
                     raise ValueError('Bad channels from:\n%s\n not found '
                                      'in:\n%s' % (bad_file,
-                                                  self._filenames[0]))
+                                                  self.filenames[0]))
                 else:
                     warn('%d bad channels from:\n%s\nnot found in:\n%s'
-                         % (count_diff, bad_file, self._filenames[0]))
+                         % (count_diff, bad_file, self.filenames[0]))
             self.info['bads'] = names_there
         else:
             self.info['bads'] = []
 
     def append(self, raws, preload=None):
-        """Concatenate raw instances as if they were continuous
+        """Concatenate raw instances as if they were continuous.
+
+        .. note:: Boundaries of the raw files are annotated bad. If you wish to
+                  use the data as continuous recording, you can remove the
+                  boundary annotations after concatenation (see
+                  :meth:`mne.Annotations.delete`).
 
         Parameters
         ----------
@@ -1780,19 +2014,28 @@ class _BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
 
         # now combine information from each raw file to construct new self
         annotations = self.annotations
-        for r in raws:
+        edge_samps = list()
+        for ri, r in enumerate(raws):
+            annotations = _combine_annotations((annotations, r.annotations),
+                                               self._last_samps,
+                                               self._first_samps,
+                                               self.info['sfreq'],
+                                               self.info['meas_date'])
+            edge_samps.append(sum(self._last_samps) -
+                              sum(self._first_samps) + (ri + 1))
             self._first_samps = np.r_[self._first_samps, r._first_samps]
             self._last_samps = np.r_[self._last_samps, r._last_samps]
             self._raw_extras += r._raw_extras
             self._filenames += r._filenames
-            annotations = _combine_annotations((annotations, r.annotations),
-                                               self._last_samps,
-                                               self._first_samps,
-                                               self.info['sfreq'])
 
         self._update_times()
+        if annotations is None:
+            annotations = Annotations([], [], [])
         self.annotations = annotations
-
+        for edge_samp in edge_samps:
+            onset = _sync_onset(self, (edge_samp) / self.info['sfreq'], True)
+            self.annotations.append(onset, 0., 'BAD boundary')
+            self.annotations.append(onset, 0., 'EDGE boundary')
         if not (len(self._first_samps) == len(self._last_samps) ==
                 len(self._raw_extras) == len(self._filenames)):
             raise RuntimeError('Append error')  # should never happen
@@ -1806,12 +2049,11 @@ class _BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
         pass
 
     def copy(self):
-        """ Return copy of Raw instance
-        """
+        """Return copy of Raw instance."""
         return deepcopy(self)
 
-    def __repr__(self):
-        name = self._filenames[0]
+    def __repr__(self):  # noqa: D105
+        name = self.filenames[0]
         name = 'None' if name is None else op.basename(name)
         size_str = str(sizeof_fmt(self._size))  # str in case it fails -> None
         size_str += ', data%s loaded' % ('' if self.preload else ' not')
@@ -1821,7 +2063,7 @@ class _BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
         return "<%s  |  %s>" % (self.__class__.__name__, s)
 
     def add_events(self, events, stim_channel=None):
-        """Add events to stim channel
+        """Add events to stim channel.
 
         Parameters
         ----------
@@ -1859,22 +2101,14 @@ class _BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
         self._data[pick, idx - self.first_samp] += events[:, 2]
 
     def _get_buffer_size(self, buffer_size_sec=None):
-        """Helper to get the buffer size"""
+        """Get the buffer size."""
         if buffer_size_sec is None:
             buffer_size_sec = self.info.get('buffer_size_sec', 1.)
         return int(np.ceil(buffer_size_sec * self.info['sfreq']))
 
 
-def _check_preload(raw, msg):
-    """Helper to ensure data are preloaded"""
-    if not raw.preload:
-        raise RuntimeError(msg + ' requires raw data to be loaded. Use '
-                           'preload=True (or string) in the constructor or '
-                           'raw.load_data().')
-
-
 def _allocate_data(data, data_buffer, data_shape, dtype):
-    """Helper to data in memory or in memmap for preloading"""
+    """Allocate data in memory or in memmap for preloading."""
     if data is None:
         # if not already done, allocate array with right type
         if isinstance(data_buffer, string_types):
@@ -1887,7 +2121,7 @@ def _allocate_data(data, data_buffer, data_shape, dtype):
 
 
 def _index_as_time(index, sfreq, first_samp=0, use_first_samp=False):
-    """Convert indices to time
+    """Convert indices to time.
 
     Parameters
     ----------
@@ -1907,9 +2141,9 @@ def _index_as_time(index, sfreq, first_samp=0, use_first_samp=False):
 
 
 class _RawShell():
-    """Used for creating a temporary raw object"""
+    """Create a temporary raw object."""
 
-    def __init__(self):
+    def __init__(self):  # noqa: D102
         self.first_samp = None
         self.last_samp = None
         self._cals = None
@@ -1917,7 +2151,7 @@ class _RawShell():
         self._projector = None
 
     @property
-    def n_times(self):
+    def n_times(self):  # noqa: D102
         return self.last_samp - self.first_samp + 1
 
 
@@ -1926,8 +2160,7 @@ class _RawShell():
 def _write_raw(fname, raw, info, picks, fmt, data_type, reset_range, start,
                stop, buffer_size, projector, drop_small_buffer,
                split_size, part_idx, prev_fname):
-    """Write raw file with splitting
-    """
+    """Write raw file with splitting."""
     # we've done something wrong if we hit this
     n_times_max = len(raw.times)
     if start >= stop or stop > n_times_max:
@@ -1962,6 +2195,7 @@ def _write_raw(fname, raw, info, picks, fmt, data_type, reset_range, start,
 
     pos_prev = fid.tell()
     if pos_prev > split_size:
+        fid.close()
         raise ValueError('file is larger than "split_size" after writing '
                          'measurement information, you must use a larger '
                          'value for split size: %s plus enough bytes for '
@@ -1981,7 +2215,7 @@ def _write_raw(fname, raw, info, picks, fmt, data_type, reset_range, start,
             logger.info('Skipping data chunk due to small buffer ... '
                         '[done]')
             break
-        logger.info('Writing ...')
+        logger.debug('Writing ...')
         _write_raw_buffer(fid, data, cals, fmt)
 
         pos = fid.tell()
@@ -1990,6 +2224,7 @@ def _write_raw(fname, raw, info, picks, fmt, data_type, reset_range, start,
         if overage > 0:
             # This should occur on the first buffer write of the file, so
             # we should mention the space required for the meas info
+            fid.close()
             raise ValueError(
                 'buffer size (%s) is too large for the given split size (%s) '
                 'by %s bytes after writing info (%s) and leaving enough space '
@@ -2031,9 +2266,7 @@ def _write_raw(fname, raw, info, picks, fmt, data_type, reset_range, start,
 
 def _start_writing_raw(name, info, sel=None, data_type=FIFF.FIFFT_FLOAT,
                        reset_range=True, annotations=None):
-    """Start write raw data in file
-
-    Data will be written in float
+    """Start write raw data in file.
 
     Parameters
     ----------
@@ -2087,7 +2320,7 @@ def _start_writing_raw(name, info, sel=None, data_type=FIFF.FIFFT_FLOAT,
     #
     # Annotations
     #
-    if annotations is not None:
+    if annotations is not None and len(annotations.onset) > 0:
         start_block(fid, FIFF.FIFFB_MNE_ANNOTATIONS)
         write_float(fid, FIFF.FIFF_MNE_BASELINE_MIN, annotations.onset)
         write_float(fid, FIFF.FIFF_MNE_BASELINE_MAX,
@@ -2111,7 +2344,7 @@ def _start_writing_raw(name, info, sel=None, data_type=FIFF.FIFFT_FLOAT,
 
 
 def _write_raw_buffer(fid, buf, cals, fmt):
-    """Write raw buffer
+    """Write raw buffer.
 
     Parameters
     ----------
@@ -2155,7 +2388,7 @@ def _write_raw_buffer(fid, buf, cals, fmt):
 
 
 def _my_hilbert(x, n_fft=None, envelope=False):
-    """ Compute Hilbert transform of signals w/ zero padding.
+    """Compute Hilbert transform of signals w/ zero padding.
 
     Parameters
     ----------
@@ -2182,8 +2415,7 @@ def _my_hilbert(x, n_fft=None, envelope=False):
 
 
 def _check_raw_compatibility(raw):
-    """Check to make sure all instances of Raw
-    in the input list raw have compatible parameters"""
+    """Ensure all instances of Raw have compatible parameters."""
     for ri in range(1, len(raw)):
         if not isinstance(raw[ri], type(raw[0])):
             raise ValueError('raw[%d] type must match' % ri)
@@ -2209,8 +2441,13 @@ def _check_raw_compatibility(raw):
 
 
 def concatenate_raws(raws, preload=None, events_list=None):
-    """Concatenate raw instances as if they were continuous. Note that raws[0]
-    is modified in-place to achieve the concatenation.
+    """Concatenate raw instances as if they were continuous.
+
+    .. note:: ``raws[0]`` is modified in-place to achieve the concatenation.
+              Boundaries of the raw files are annotated bad. If you wish to use
+              the data as continuous recording, you can remove the boundary
+              annotations after concatenation (see
+              :meth:`mne.Annotations.delete`).
 
     Parameters
     ----------
@@ -2245,7 +2482,7 @@ def concatenate_raws(raws, preload=None, events_list=None):
 
 
 def _check_update_montage(info, montage, path=None, update_ch_names=False):
-    """ Helper function for eeg readers to add montage"""
+    """Help eeg readers to add montage."""
     if montage is not None:
         if not isinstance(montage, (string_types, Montage)):
             err = ("Montage must be str, None, or instance of Montage. "
diff --git a/mne/io/brainvision/__init__.py b/mne/io/brainvision/__init__.py
index 17a7db2..adcbc5a 100644
--- a/mne/io/brainvision/__init__.py
+++ b/mne/io/brainvision/__init__.py
@@ -1,4 +1,4 @@
-"""Brainvision module for conversion to FIF"""
+"""Brainvision module for conversion to FIF."""
 
 # Author: Teon Brooks <teon.brooks at gmail.com>
 #
diff --git a/mne/io/brainvision/brainvision.py b/mne/io/brainvision/brainvision.py
index 55bd100..f76c413 100644
--- a/mne/io/brainvision/brainvision.py
+++ b/mne/io/brainvision/brainvision.py
@@ -1,11 +1,12 @@
 # -*- coding: utf-8 -*-
-"""Conversion tool from Brain Vision EEG to FIF"""
+"""Conversion tool from Brain Vision EEG to FIF."""
 
 # Authors: Teon Brooks <teon.brooks at gmail.com>
 #          Christian Brodbeck <christianbrodbeck at nyu.edu>
 #          Eric Larson <larson.eric.d at gmail.com>
 #          Jona Sassenhagen <jona.sassenhagen at gmail.com>
 #          Phillip Alday <phillip.alday at unisa.edu.au>
+#          Okba Bekhelifi <okba.bekhelifi at gmail.com>
 #
 # License: BSD (3-clause)
 
@@ -18,24 +19,26 @@ import numpy as np
 from ...utils import verbose, logger, warn
 from ..constants import FIFF
 from ..meas_info import _empty_info
-from ..base import _BaseRaw, _check_update_montage
-from ..utils import _read_segments_file, _synthesize_stim_channel
+from ..base import BaseRaw, _check_update_montage
+from ..utils import (_read_segments_file, _synthesize_stim_channel,
+                     _mult_cal_one)
 
-from ...externals.six import StringIO
+from ...externals.six import StringIO, string_types
 from ...externals.six.moves import configparser
 
 
-class RawBrainVision(_BaseRaw):
-    """Raw object from Brain Vision EEG file
+class RawBrainVision(BaseRaw):
+    """Raw object from Brain Vision EEG file.
 
     Parameters
     ----------
     vhdr_fname : str
         Path to the EEG header file.
     montage : str | None | instance of Montage
-        Path or instance of montage containing electrode positions.
-        If None, sensor locations are (0,0,0). See the documentation of
-        :func:`mne.channels.read_montage` for more information.
+        Path or instance of montage containing electrode positions. If None,
+        read sensor locations from header file if present, otherwise (0, 0, 0).
+        See the documentation of :func:`mne.channels.read_montage` for more
+        information.
     eog : list or tuple
         Names of channels or list of indices that should be designated
         EOG channels. Values should correspond to the vhdr file.
@@ -64,46 +67,73 @@ class RawBrainVision(_BaseRaw):
         or an empty dict (default), only stimulus events are added to the
         stimulus channel. Keys are case sensitive.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     See Also
     --------
     mne.io.Raw : Documentation of attribute and methods.
     """
+
     @verbose
     def __init__(self, vhdr_fname, montage=None,
                  eog=('HEOGL', 'HEOGR', 'VEOGb'), misc='auto',
                  scale=1., preload=False, response_trig_shift=0,
-                 event_id=None, verbose=None):
+                 event_id=None, verbose=None):  # noqa: D102
         # Channel info and events
         logger.info('Extracting parameters from %s...' % vhdr_fname)
         vhdr_fname = os.path.abspath(vhdr_fname)
-        info, fmt, self._order, mrk_fname, montage = _get_vhdr_info(
-            vhdr_fname, eog, misc, scale, montage)
+        info, data_filename, fmt, order, mrk_fname, montage, n_samples = \
+            _get_vhdr_info(vhdr_fname, eog, misc, scale, montage)
+        self._order = order
+        self._n_samples = n_samples
         events = _read_vmrk_events(mrk_fname, event_id, response_trig_shift)
         _check_update_montage(info, montage)
-        with open(info['filename'], 'rb') as f:
-            f.seek(0, os.SEEK_END)
-            n_samples = f.tell()
-        dtype_bytes = _fmt_byte_dict[fmt]
+        with open(data_filename, 'rb') as f:
+            if isinstance(fmt, dict):  # ASCII, this will be slow :(
+                n_skip = 0
+                for ii in range(int(fmt['skiplines'])):
+                    n_skip += len(f.readline())
+                offsets = np.cumsum([n_skip] + [len(line) for line in f])
+                n_samples = len(offsets) - 1
+            else:
+                f.seek(0, os.SEEK_END)
+                n_samples = f.tell()
+                dtype_bytes = _fmt_byte_dict[fmt]
+                offsets = None
+                n_samples = n_samples // (dtype_bytes * (info['nchan'] - 1))
         self.preload = False  # so the event-setting works
-        last_samps = [(n_samples // (dtype_bytes * (info['nchan'] - 1))) - 1]
-        self._create_event_ch(events, last_samps[0] + 1)
+        self._create_event_ch(events, n_samples)
         super(RawBrainVision, self).__init__(
-            info, last_samps=last_samps, filenames=[info['filename']],
-            orig_format=fmt, preload=preload, verbose=verbose)
+            info, last_samps=[n_samples - 1], filenames=[data_filename],
+            orig_format=fmt, preload=preload, verbose=verbose,
+            raw_extras=[offsets])
 
     def _read_segment_file(self, data, idx, fi, start, stop, cals, mult):
-        """Read a chunk of raw data"""
+        """Read a chunk of raw data."""
         # read data
-        dtype = _fmt_dtype_dict[self.orig_format]
-        n_data_ch = len(self.ch_names) - 1
-        _read_segments_file(self, data, idx, fi, start, stop, cals, mult,
-                            dtype=dtype, n_channels=n_data_ch,
-                            trigger_ch=self._event_ch)
+        if self._order == 'C':
+            _read_segments_c(self, data, idx, fi, start, stop, cals, mult)
+        elif isinstance(self.orig_format, string_types):
+            dtype = _fmt_dtype_dict[self.orig_format]
+            n_data_ch = len(self.ch_names) - 1
+            _read_segments_file(self, data, idx, fi, start, stop, cals, mult,
+                                dtype=dtype, n_channels=n_data_ch,
+                                trigger_ch=self._event_ch)
+        else:
+            offsets = self._raw_extras[fi]
+            with open(self._filenames[fi], 'rb') as fid:
+                fid.seek(offsets[start])
+                block = np.empty((len(self.ch_names), stop - start))
+                for ii in range(stop - start):
+                    line = fid.readline().decode('ASCII')
+                    line = line.strip().replace(',', '.').split()
+                    block[:-1, ii] = list(map(float, line))
+            block[-1] = self._event_ch[start:stop]
+            _mult_cal_one(data, block, idx, cals, mult)
 
     def _get_brainvision_events(self):
-        """Retrieve the events associated with the Brain Vision Raw object
+        """Retrieve the events associated with the Brain Vision Raw object.
 
         Returns
         -------
@@ -114,7 +144,7 @@ class RawBrainVision(_BaseRaw):
         return self._events.copy()
 
     def _set_brainvision_events(self, events):
-        """Set the events and update the synthesized stim channel
+        """Set the events and update the synthesized stim channel.
 
         Parameters
         ----------
@@ -125,7 +155,7 @@ class RawBrainVision(_BaseRaw):
         self._create_event_ch(events)
 
     def _create_event_ch(self, events, n_samp=None):
-        """Create the event channel"""
+        """Create the event channel."""
         if n_samp is None:
             n_samp = self.last_samp - self.first_samp + 1
         events = np.array(events, int)
@@ -138,8 +168,28 @@ class RawBrainVision(_BaseRaw):
             self._data[-1] = self._event_ch
 
 
+def _read_segments_c(raw, data, idx, fi, start, stop, cals, mult):
+    """Read chunk of vectorized raw data."""
+    n_samples = raw._n_samples
+    dtype = _fmt_dtype_dict[raw.orig_format]
+    n_bytes = _fmt_byte_dict[raw.orig_format]
+    n_channels = len(raw.ch_names)
+    trigger_ch = raw._event_ch
+    block = np.zeros((n_channels, stop - start))
+    with open(raw._filenames[fi], 'rb', buffering=0) as fid:
+        for ch_id in np.arange(n_channels)[idx]:
+            if ch_id == n_channels - 1:  # stim channel
+                stim_ch = trigger_ch[start:stop]
+                block[ch_id] = stim_ch
+                continue
+            fid.seek(start * n_bytes + ch_id * n_bytes * n_samples)
+            block[ch_id] = np.fromfile(fid, dtype, stop - start)
+
+        _mult_cal_one(data, block, idx, cals, mult)
+
+
 def _read_vmrk_events(fname, event_id=None, response_trig_shift=0):
-    """Read events from a vmrk file
+    """Read events from a vmrk file.
 
     Parameters
     ----------
@@ -182,15 +232,20 @@ def _read_vmrk_events(fname, event_id=None, response_trig_shift=0):
     # blocks, such as that the filename are specifying are not
     # guaranteed to be ASCII.
 
-    codepage = 'utf-8'
     try:
         # if there is an explicit codepage set, use it
         # we pretend like it's ascii when searching for the codepage
         cp_setting = re.search('Codepage=(.+)',
                                txt.decode('ascii', 'ignore'),
                                re.IGNORECASE & re.MULTILINE)
+        codepage = 'utf-8'
         if cp_setting:
             codepage = cp_setting.group(1).strip()
+        # BrainAmp Recorder also uses ANSI codepage
+        # an ANSI codepage raises a LookupError exception
+        # python recognize ANSI decoding as cp1252
+        if codepage == 'ANSI':
+            codepage = 'cp1252'
         txt = txt.decode(codepage)
     except UnicodeDecodeError:
         # if UTF-8 (new standard) or explicit codepage setting fails,
@@ -199,16 +254,16 @@ def _read_vmrk_events(fname, event_id=None, response_trig_shift=0):
         txt = txt.decode('latin-1')
 
     # extract Marker Infos block
-    m = re.search("\[Marker Infos\]", txt)
+    m = re.search(r"\[Marker Infos\]", txt)
     if not m:
         return np.zeros(0)
     mk_txt = txt[m.end():]
-    m = re.search("\[.*\]", mk_txt)
+    m = re.search(r"\[.*\]", mk_txt)
     if m:
         mk_txt = mk_txt[:m.start()]
 
     # extract event information
-    items = re.findall("^Mk\d+=(.*)", mk_txt, re.MULTILINE)
+    items = re.findall(r"^Mk\d+=(.*)", mk_txt, re.MULTILINE)
     events, dropped = list(), list()
     for info in items:
         mtype, mdesc, onset, duration = info.split(',')[:4]
@@ -218,7 +273,7 @@ def _read_vmrk_events(fname, event_id=None, response_trig_shift=0):
             trigger = event_id[mdesc]
         else:
             try:
-                trigger = int(re.findall('[A-Za-z]*\s*?(\d+)', mdesc)[0])
+                trigger = int(re.findall(r'[A-Za-z]*\s*?(\d+)', mdesc)[0])
             except IndexError:
                 trigger = None
             if mtype.lower().startswith('response'):
@@ -247,15 +302,18 @@ def _read_vmrk_events(fname, event_id=None, response_trig_shift=0):
 
 
 def _check_hdr_version(header):
-    tags = ['Brain Vision Data Exchange Header File Version 1.0',
-            'Brain Vision Data Exchange Header File Version 2.0']
-    if header not in tags:
-        raise ValueError("Currently only support %r, not %r"
-                         "Contact MNE-Developers for support."
-                         % (str(tags), header))
+    """Check the header version."""
+    if header == 'Brain Vision Data Exchange Header File Version 1.0':
+        return 1
+    elif header == 'Brain Vision Data Exchange Header File Version 2.0':
+        return 2
+    else:
+        raise ValueError("Currently only support versions 1.0 and 2.0, not %r "
+                         "Contact MNE-Developers for support." % header)
 
 
 def _check_mrk_version(header):
+    """Check the marker version."""
     tags = ['Brain Vision Data Exchange Marker File, Version 1.0',
             'Brain Vision Data Exchange Marker File, Version 2.0']
     if header not in tags:
@@ -280,7 +338,7 @@ _unit_dict = {'V': 1.,  # V stands for Volt
 
 
 def _get_vhdr_info(vhdr_fname, eog, misc, scale, montage):
-    """Extracts all the information from the header file.
+    """Extract all the information from the header file.
 
     Parameters
     ----------
@@ -297,10 +355,11 @@ def _get_vhdr_info(vhdr_fname, eog, misc, scale, montage):
     scale : float
         The scaling factor for EEG data. Unless specified otherwise by
         header file, units are in microvolts. Default scale factor is 1.
-    montage : str | True | None | instance of Montage
-        Path or instance of montage containing electrode positions.
-        If None, sensor locations are (0,0,0). See the documentation of
-        :func:`mne.channels.read_montage` for more information.
+    montage : str | None | instance of Montage
+        Path or instance of montage containing electrode positions. If None,
+        read sensor locations from header file if present, otherwise (0, 0, 0).
+        See the documentation of :func:`mne.channels.read_montage` for more
+        information.
 
     Returns
     -------
@@ -314,11 +373,10 @@ def _get_vhdr_info(vhdr_fname, eog, misc, scale, montage):
         Events from the corresponding vmrk file.
     """
     scale = float(scale)
-
     ext = os.path.splitext(vhdr_fname)[-1]
     if ext != '.vhdr':
         raise IOError("The header file must be given to read the data, "
-                      "not the '%s' file." % ext)
+                      "not a file with extension '%s'." % ext)
     with open(vhdr_fname, 'rb') as f:
         # extract the first section to resemble a cfg
         header = f.readline()
@@ -338,6 +396,11 @@ def _get_vhdr_info(vhdr_fname, eog, misc, scale, montage):
                                    re.IGNORECASE & re.MULTILINE)
             if cp_setting:
                 codepage = cp_setting.group(1).strip()
+            # BrainAmp Recorder also uses ANSI codepage
+            # an ANSI codepage raises a LookupError exception
+            # python recognize ANSI decoding as cp1252
+            if codepage == 'ANSI':
+                codepage = 'cp1252'
             settings = settings.decode(codepage)
         except UnicodeDecodeError:
             # if UTF-8 (new standard) or explicit codepage setting fails,
@@ -360,21 +423,42 @@ def _get_vhdr_info(vhdr_fname, eog, misc, scale, montage):
     sfreq = 1e6 / cfg.getfloat('Common Infos', 'SamplingInterval')
     info = _empty_info(sfreq)
 
-    # check binary format
-    assert cfg.get('Common Infos', 'DataFormat') == 'BINARY'
     order = cfg.get('Common Infos', 'DataOrientation')
     if order not in _orientation_dict:
         raise NotImplementedError('Data Orientation %s is not supported'
                                   % order)
     order = _orientation_dict[order]
 
-    fmt = cfg.get('Binary Infos', 'BinaryFormat')
-    if fmt not in _fmt_dict:
-        raise NotImplementedError('Datatype %s is not supported' % fmt)
-    fmt = _fmt_dict[fmt]
+    data_format = cfg.get('Common Infos', 'DataFormat')
+    if data_format == 'BINARY':
+        fmt = cfg.get('Binary Infos', 'BinaryFormat')
+        if fmt not in _fmt_dict:
+            raise NotImplementedError('Datatype %s is not supported' % fmt)
+        fmt = _fmt_dict[fmt]
+    else:
+        fmt = dict((key, cfg.get('ASCII Infos', key))
+                   for key in cfg.options('ASCII Infos'))
+
+    # locate EEG and marker files
+    path = os.path.dirname(vhdr_fname)
+    data_filename = os.path.join(path, cfg.get('Common Infos', 'DataFile'))
+    info['meas_date'] = int(time.time())
+    info['buffer_size_sec'] = 1.  # reasonable default
 
     # load channel labels
     nchan = cfg.getint('Common Infos', 'NumberOfChannels') + 1
+    n_samples = None
+    if order == 'C':
+        try:
+            n_samples = cfg.getint('Common Infos', 'DataPoints')
+        except configparser.NoOptionError:
+            logger.warning('No info on DataPoints found. Inferring number of '
+                           'samples from the data file size.')
+            with open(data_filename, 'rb') as fid:
+                fid.seek(0, 2)
+                n_bytes = fid.tell()
+                n_samples = n_bytes // _fmt_byte_dict[fmt] // (nchan - 1)
+
     ch_names = [''] * nchan
     cals = np.empty(nchan)
     ranges = np.empty(nchan)
@@ -406,20 +490,31 @@ def _get_vhdr_info(vhdr_fname, eog, misc, scale, montage):
     misc = list(misc_chs.keys()) if misc == 'auto' else misc
 
     # create montage
-    if montage is True:
-        from ...transforms import _sphere_to_cartesian
+    if cfg.has_section('Coordinates') and montage is None:
+        from ...transforms import _sph_to_cart
         from ...channels.montage import Montage
         montage_pos = list()
         montage_names = list()
+        to_misc = list()
         for ch in cfg.items('Coordinates'):
-            montage_names.append(ch_dict[ch[0]])
+            ch_name = ch_dict[ch[0]]
+            montage_names.append(ch_name)
             radius, theta, phi = map(float, ch[1].split(','))
             # 1: radius, 2: theta, 3: phi
-            pos = _sphere_to_cartesian(r=radius, theta=theta, phi=phi)
+            pol = np.deg2rad(theta)
+            az = np.deg2rad(phi)
+            pos = _sph_to_cart(np.array([[radius * 85., az, pol]]))[0]
+            if (pos == 0).all() and ch_name not in list(eog) + misc:
+                to_misc.append(ch_name)
             montage_pos.append(pos)
         montage_sel = np.arange(len(montage_pos))
         montage = Montage(montage_pos, montage_names, 'Brainvision',
                           montage_sel)
+        if len(to_misc) > 0:
+            misc += to_misc
+            warn('No coordinate information found for channels {}. '
+                 'Setting channel types to misc. To avoid this warning, set '
+                 'channel types explicitly.'.format(to_misc))
 
     ch_names[-1] = 'STI 014'
     cals[-1] = 1.
@@ -437,7 +532,7 @@ def _get_vhdr_info(vhdr_fname, eog, misc, scale, montage):
         settings = settings[idx + 1:]
         hp_col, lp_col = 4, 5
         for idx, setting in enumerate(settings):
-            if re.match('#\s+Name', setting):
+            if re.match(r'#\s+Name', setting):
                 break
             else:
                 idx = None
@@ -450,7 +545,7 @@ def _get_vhdr_info(vhdr_fname, eog, misc, scale, montage):
     if 'S o f t w a r e  F i l t e r s' in settings:
         idx = settings.index('S o f t w a r e  F i l t e r s')
         for idx, setting in enumerate(settings[idx + 1:], idx + 1):
-            if re.match('#\s+Low Cutoff', setting):
+            if re.match(r'#\s+Low Cutoff', setting):
                 hp_col, lp_col = 1, 2
                 warn('Online software filter detected. Using software '
                      'filter settings and ignoring hardware values')
@@ -462,28 +557,34 @@ def _get_vhdr_info(vhdr_fname, eog, misc, scale, montage):
         lowpass = []
         highpass = []
 
+        # for newer BV files, the unit is specified for every channel
+        # separated by a single space, while for older files, the unit is
+        # specified in the column headers
+        divider = r'\s+'
+        if 'Resolution / Unit' in settings[idx]:
+            shift = 1  # shift for unit
+        else:
+            shift = 0
+
         # extract filter units and convert s to Hz if necessary
         # this cannot be done as post-processing as the inverse t-f
         # relationship means that the min/max comparisons don't make sense
         # unless we know the units
-        header = re.split('\s\s+', settings[idx])
+        header = re.split(r'\s\s+', settings[idx])
         hp_s = '[s]' in header[hp_col]
         lp_s = '[s]' in header[lp_col]
 
         for i, ch in enumerate(ch_names[:-1], 1):
-            line = re.split('\s\s+', settings[idx + i])
+            line = re.split(divider, settings[idx + i])
             # double check alignment with channel by using the hw settings
-            # the actual divider is multiple spaces -- for newer BV
-            # files, the unit is specified for every channel separated
-            # by a single space, while for older files, the unit is
-            # specified in the column headers
             if idx == idx_amp:
                 line_amp = line
             else:
-                line_amp = re.split('\s\s+', settings[idx_amp + i])
+                line_amp = re.split(divider, settings[idx_amp + i])
             assert ch in line_amp
-            highpass.append(line[hp_col])
-            lowpass.append(line[lp_col])
+
+            highpass.append(line[hp_col + shift])
+            lowpass.append(line[lp_col + shift])
         if len(highpass) == 0:
             pass
         elif len(set(highpass)) == 1:
@@ -587,12 +688,6 @@ def _get_vhdr_info(vhdr_fname, eog, misc, scale, montage):
                      'Highest (weakest) filter setting (%0.2f Hz%s) '
                      'will be stored.' % (info['lowpass'], nyquist))
 
-    # locate EEG and marker files
-    path = os.path.dirname(vhdr_fname)
-    info['filename'] = os.path.join(path, cfg.get('Common Infos', 'DataFile'))
-    info['meas_date'] = int(time.time())
-    info['buffer_size_sec'] = 1.  # reasonable default
-
     # Creates a list of dicts of eeg channels for raw.info
     logger.info('Setting channel info structure...')
     info['chs'] = []
@@ -626,14 +721,14 @@ def _get_vhdr_info(vhdr_fname, eog, misc, scale, montage):
     mrk_fname = os.path.join(path, cfg.get('Common Infos', 'MarkerFile'))
     info._update_redundant()
     info._check_consistency()
-    return info, fmt, order, mrk_fname, montage
+    return info, data_filename, fmt, order, mrk_fname, montage, n_samples
 
 
 def read_raw_brainvision(vhdr_fname, montage=None,
                          eog=('HEOGL', 'HEOGR', 'VEOGb'), misc='auto',
                          scale=1., preload=False, response_trig_shift=0,
                          event_id=None, verbose=None):
-    """Reader for Brain Vision EEG file
+    """Reader for Brain Vision EEG file.
 
     Parameters
     ----------
@@ -671,7 +766,8 @@ def read_raw_brainvision(vhdr_fname, montage=None,
         or an empty dict (default), only stimulus events are added to the
         stimulus channel. Keys are case sensitive.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
diff --git a/mne/io/brainvision/tests/test_brainvision.py b/mne/io/brainvision/tests/test_brainvision.py
index bbb4dc9..edb248e 100644
--- a/mne/io/brainvision/tests/test_brainvision.py
+++ b/mne/io/brainvision/tests/test_brainvision.py
@@ -5,8 +5,9 @@ from __future__ import print_function
 #
 # License: BSD (3-clause)
 
-import os.path as op
 import inspect
+import os.path as op
+import shutil
 import warnings
 
 from nose.tools import assert_equal, assert_raises, assert_true
@@ -24,6 +25,7 @@ FILE = inspect.getfile(inspect.currentframe())
 data_dir = op.join(op.dirname(op.abspath(FILE)), 'data')
 vhdr_path = op.join(data_dir, 'test.vhdr')
 vmrk_path = op.join(data_dir, 'test.vmrk')
+eeg_path = op.join(data_dir, 'test.eeg')
 
 vhdr_partially_disabled_hw_filter_path = op.join(data_dir,
                                                  'test_partially_disabled'
@@ -51,10 +53,81 @@ vhdr_mixed_lowpass_s_path = op.join(data_dir, 'test_mixed_lowpass_s.vhdr')
 montage = op.join(data_dir, 'test.hpts')
 eeg_bin = op.join(data_dir, 'test_bin_raw.fif')
 eog = ['HL', 'HR', 'Vb']
+event_id = {'Sync On': 5}
 
 warnings.simplefilter('always')
 
 
+def test_vhdr_codepage_ansi():
+    """Test BV reading with ANSI codepage."""
+    raw_init = read_raw_brainvision(vhdr_path, event_id=event_id)
+    tempdir = _TempDir()
+    ansi_vhdr_path = op.join(tempdir, op.split(vhdr_path)[-1])
+    ansi_vmrk_path = op.join(tempdir, op.split(vmrk_path)[-1])
+    ansi_eeg_path = op.join(tempdir, op.split(eeg_path)[-1])
+    # copy data file
+    shutil.copy(eeg_path, ansi_eeg_path)
+    # modify header file
+    with open(ansi_vhdr_path, 'wb') as fout:
+        with open(vhdr_path, 'rb') as fin:
+            for line in fin:
+                # Common Infos section
+                if line.startswith(b'Codepage'):
+                    line = b'Codepage=ANSI\n'
+                fout.write(line)
+    # modify marker file
+    with open(ansi_vmrk_path, 'wb') as fout:
+        with open(vmrk_path, 'rb') as fin:
+            for line in fin:
+                # Common Infos section
+                if line.startswith(b'Codepage'):
+                    line = b'Codepage=ANSI\n'
+                fout.write(line)
+    raw = read_raw_brainvision(ansi_vhdr_path, event_id=event_id)
+    data_new, times_new = raw[:]
+    assert_equal(raw_init.ch_names, raw.ch_names)
+
+
+def test_ascii():
+    """Test ASCII BV reading."""
+    raw = read_raw_brainvision(vhdr_path, event_id=event_id)
+    tempdir = _TempDir()
+    ascii_vhdr_path = op.join(tempdir, op.split(vhdr_path)[-1])
+    # copy marker file
+    shutil.copy(vhdr_path.replace('.vhdr', '.vmrk'),
+                ascii_vhdr_path.replace('.vhdr', '.vmrk'))
+    # modify header file
+    skipping = False
+    with open(ascii_vhdr_path, 'wb') as fout:
+        with open(vhdr_path, 'rb') as fin:
+            for line in fin:
+                # Common Infos section
+                if line.startswith(b'DataFormat'):
+                    line = b'DataFormat=ASCII\n'
+                elif line.startswith(b'DataFile='):
+                    line = b'DataFile=test.dat\n'
+                # Replace the "'Binary Infos'" section
+                elif line.startswith(b'[Binary Infos]'):
+                    skipping = True
+                    fout.write(b'[ASCII Infos]\nDecimalSymbol=.\nSkipLines=1\n'
+                               b'SkipColumns=0\n\n')
+                elif skipping and line.startswith(b'['):
+                    skipping = False
+                if not skipping:
+                    fout.write(line)
+    # create the .dat file
+    data, times = raw[:]
+    with open(ascii_vhdr_path.replace('.vhdr', '.dat'), 'wb') as fid:
+        fid.write(b' '.join(ch_name.encode('ASCII')
+                  for ch_name in raw.ch_names) + b'\n')
+        fid.write(b'\n'.join(b' '.join(b'%.3f' % dd for dd in d)
+                  for d in data[:-1].T / raw._cals[:-1]))
+    raw = read_raw_brainvision(ascii_vhdr_path, event_id=event_id)
+    data_new, times_new = raw[:]
+    assert_allclose(data_new, data, atol=1e-15)
+    assert_allclose(times_new, times)
+
+
 def test_brainvision_data_highpass_filters():
     """Test reading raw Brain Vision files with amplifier filter settings."""
     # Homogeneous highpass in seconds (default measurement unit)
@@ -71,37 +144,34 @@ def test_brainvision_data_highpass_filters():
     with warnings.catch_warnings(record=True) as w:  # event parsing
         raw = _test_raw_reader(
             read_raw_brainvision, vhdr_fname=vhdr_mixed_highpass_path,
-            montage=montage, eog=eog)
+            montage=montage, eog=eog, event_id=event_id)
 
-    trigger_warning = ['parse triggers that' in str(ww.message)
-                       for ww in w]
     lowpass_warning = ['different lowpass filters' in str(ww.message)
                        for ww in w]
     highpass_warning = ['different highpass filters' in str(ww.message)
                         for ww in w]
 
-    expected_warnings = zip(trigger_warning, lowpass_warning, highpass_warning)
+    expected_warnings = zip(lowpass_warning, highpass_warning)
 
-    assert_true(all(any([trg, lp, hp]) for trg, lp, hp in expected_warnings))
+    assert_true(all(any([lp, hp]) for lp, hp in expected_warnings))
 
     assert_equal(raw.info['highpass'], 0.1)
     assert_equal(raw.info['lowpass'], 250.)
 
     # Homogeneous highpass in Hertz
-    with warnings.catch_warnings(record=True) as w:  # event parsing
+    with warnings.catch_warnings(record=True):  # filter settings
         raw = _test_raw_reader(
             read_raw_brainvision, vhdr_fname=vhdr_highpass_hz_path,
-            montage=montage, eog=eog)
-    assert_true(all('parse triggers that' in str(ww.message) for ww in w))
+            montage=montage, eog=eog, event_id=event_id)
 
     assert_equal(raw.info['highpass'], 10.)
     assert_equal(raw.info['lowpass'], 250.)
 
     # Heterogeneous highpass in Hertz
-    with warnings.catch_warnings(record=True) as w:  # event parsing
+    with warnings.catch_warnings(record=True):  # filter settings
         raw = _test_raw_reader(
             read_raw_brainvision, vhdr_fname=vhdr_mixed_highpass_hz_path,
-            montage=montage, eog=eog)
+            montage=montage, eog=eog, event_id=event_id)
 
     trigger_warning = ['parse triggers that' in str(ww.message)
                        for ww in w]
@@ -119,14 +189,12 @@ def test_brainvision_data_highpass_filters():
 
 
 def test_brainvision_data_lowpass_filters():
-    """Test reading raw Brain Vision files with amplifier LP filter settings"""
+    """Test files with amplifier LP filter settings."""
 
     # Homogeneous lowpass in Hertz (default measurement unit)
-    with warnings.catch_warnings(record=True) as w:  # event parsing
-        raw = _test_raw_reader(
-            read_raw_brainvision, vhdr_fname=vhdr_lowpass_path,
-            montage=montage, eog=eog)
-    assert_true(all('parse triggers that' in str(ww.message) for ww in w))
+    raw = _test_raw_reader(
+        read_raw_brainvision, vhdr_fname=vhdr_lowpass_path,
+        montage=montage, eog=eog, event_id=event_id)
 
     assert_equal(raw.info['highpass'], 0.1)
     assert_equal(raw.info['lowpass'], 250.)
@@ -135,57 +203,49 @@ def test_brainvision_data_lowpass_filters():
     with warnings.catch_warnings(record=True) as w:  # event parsing
         raw = _test_raw_reader(
             read_raw_brainvision, vhdr_fname=vhdr_mixed_lowpass_path,
-            montage=montage, eog=eog)
+            montage=montage, eog=eog, event_id=event_id)
 
-    trigger_warning = ['parse triggers that' in str(ww.message)
-                       for ww in w]
     lowpass_warning = ['different lowpass filters' in str(ww.message)
                        for ww in w]
     highpass_warning = ['different highpass filters' in str(ww.message)
                         for ww in w]
 
-    expected_warnings = zip(trigger_warning, lowpass_warning, highpass_warning)
+    expected_warnings = zip(lowpass_warning, highpass_warning)
 
-    assert_true(all(any([trg, lp, hp]) for trg, lp, hp in expected_warnings))
+    assert_true(all(any([lp, hp]) for lp, hp in expected_warnings))
 
     assert_equal(raw.info['highpass'], 0.1)
     assert_equal(raw.info['lowpass'], 250.)
 
     # Homogeneous lowpass in seconds
-    with warnings.catch_warnings(record=True) as w:  # event parsing
-        raw = _test_raw_reader(
-            read_raw_brainvision, vhdr_fname=vhdr_lowpass_s_path,
-            montage=montage, eog=eog)
-    assert_true(all('parse triggers that' in str(ww.message) for ww in w))
+    raw = _test_raw_reader(
+        read_raw_brainvision, vhdr_fname=vhdr_lowpass_s_path,
+        montage=montage, eog=eog, event_id=event_id)
 
     assert_equal(raw.info['highpass'], 0.1)
     assert_equal(raw.info['lowpass'], 250.)
 
     # Heterogeneous lowpass in seconds
-    with warnings.catch_warnings(record=True) as w:  # event parsing
+    with warnings.catch_warnings(record=True) as w:  # filter settings
         raw = _test_raw_reader(
             read_raw_brainvision, vhdr_fname=vhdr_mixed_lowpass_s_path,
-            montage=montage, eog=eog)
+            montage=montage, eog=eog, event_id=event_id)
 
-    trigger_warning = ['parse triggers that' in str(ww.message)
-                       for ww in w]
     lowpass_warning = ['different lowpass filters' in str(ww.message)
                        for ww in w]
     highpass_warning = ['different highpass filters' in str(ww.message)
                         for ww in w]
 
-    expected_warnings = zip(trigger_warning, lowpass_warning, highpass_warning)
+    expected_warnings = zip(lowpass_warning, highpass_warning)
 
-    assert_true(all(any([trg, lp, hp]) for trg, lp, hp in expected_warnings))
+    assert_true(all(any([lp, hp]) for lp, hp in expected_warnings))
 
     assert_equal(raw.info['highpass'], 0.1)
     assert_equal(raw.info['lowpass'], 250.)
 
 
 def test_brainvision_data_partially_disabled_hw_filters():
-    """Test reading raw Brain Vision files with heterogeneous amplifier
-       filter settings including non-numeric values
-    """
+    """Test heterogeneous filter settings including non-numeric values."""
     with warnings.catch_warnings(record=True) as w:  # event parsing
         raw = _test_raw_reader(
             read_raw_brainvision,
@@ -225,11 +285,9 @@ def test_brainvision_data():
     assert_raises(ValueError, read_raw_brainvision, vhdr_path, montage,
                   preload=True, scale="foo")
 
-    with warnings.catch_warnings(record=True) as w:  # event parsing
-        raw_py = _test_raw_reader(
-            read_raw_brainvision, vhdr_fname=vhdr_path, montage=montage,
-            eog=eog, misc='auto')
-    assert_true(all('parse triggers that' in str(ww.message) for ww in w))
+    raw_py = _test_raw_reader(
+        read_raw_brainvision, vhdr_fname=vhdr_path, montage=montage,
+        eog=eog, misc='auto', event_id=event_id)
 
     assert_true('RawBrainVision' in repr(raw_py))
 
@@ -240,7 +298,7 @@ def test_brainvision_data():
     data_py, times_py = raw_py[picks]
 
     # compare with a file that was generated using MNE-C
-    raw_bin = read_raw_fif(eeg_bin, preload=True, add_eeg_ref=False)
+    raw_bin = read_raw_fif(eeg_bin, preload=True)
     picks = pick_types(raw_py.info, meg=False, eeg=True, exclude='bads')
     data_bin, times_bin = raw_bin[picks]
 
@@ -267,7 +325,49 @@ def test_brainvision_data():
 
     # test loading v2
     read_raw_brainvision(vhdr_v2_path, eog=eog, preload=True,
-                         response_trig_shift=1000)
+                         response_trig_shift=1000, verbose='error')
+
+
+def test_brainvision_vectorized_data():
+    """Test reading BrainVision data files with vectorized data."""
+
+    with warnings.catch_warnings(record=True):  # software filter settings
+        raw = read_raw_brainvision(vhdr_old_path, preload=True)
+
+    assert_array_equal(raw._data.shape, (30, 251))
+
+    first_two_samples_all_chs = np.array([[+5.22000008e-06, +5.10000000e-06],
+                                          [+2.10000000e-06, +2.27000008e-06],
+                                          [+1.15000000e-06, +1.33000002e-06],
+                                          [+4.00000000e-07, +4.00000000e-07],
+                                          [-3.02999992e-06, -2.82000008e-06],
+                                          [+2.71000004e-06, +2.45000000e-06],
+                                          [+2.41000004e-06, +2.36000004e-06],
+                                          [+1.01999998e-06, +1.18000002e-06],
+                                          [-1.33999996e-06, -1.25000000e-06],
+                                          [-2.60000000e-06, -2.46000004e-06],
+                                          [+6.80000019e-07, +8.00000000e-07],
+                                          [+1.48000002e-06, +1.48999996e-06],
+                                          [+1.61000004e-06, +1.51000004e-06],
+                                          [+7.19999981e-07, +8.60000038e-07],
+                                          [-3.00000000e-07, -4.00000006e-08],
+                                          [-1.20000005e-07, +6.00000024e-08],
+                                          [+8.19999981e-07, +9.89999962e-07],
+                                          [+1.13000002e-06, +1.28000002e-06],
+                                          [+1.08000002e-06, +1.33999996e-06],
+                                          [+2.20000005e-07, +5.69999981e-07],
+                                          [-4.09999990e-07, +4.00000006e-08],
+                                          [+5.19999981e-07, +9.39999962e-07],
+                                          [+1.01000004e-06, +1.51999998e-06],
+                                          [+1.01000004e-06, +1.55000000e-06],
+                                          [-1.43000002e-06, -1.13999996e-06],
+                                          [+3.65000000e-06, +3.65999985e-06],
+                                          [+4.15999985e-06, +3.79000015e-06],
+                                          [+9.26999969e-06, +8.95999985e-06],
+                                          [-7.35999985e-06, -7.18000031e-06],
+                                          [+0.00000000e+00, +0.00000000e+00]])
+
+    assert_array_almost_equal(raw._data[:, :2], first_two_samples_all_chs)
 
 
 def test_events():
@@ -275,82 +375,75 @@ def test_events():
     tempdir = _TempDir()
 
     # check that events are read and stim channel is synthesized correcly
-    with warnings.catch_warnings(record=True) as w:
-        warnings.simplefilter('always')
-        raw = read_raw_brainvision(vhdr_path, eog=eog, preload=True)
-        events = raw._get_brainvision_events()
-        assert_array_equal(events, [[487, 1, 253],
-                                    [497, 1, 255],
-                                    [1770, 1, 254],
-                                    [1780, 1, 255],
-                                    [3253, 1, 254],
-                                    [3263, 1, 255],
-                                    [4936, 1, 253],
-                                    [4946, 1, 255],
-                                    [6000, 1, 255],
-                                    [6620, 1, 254],
-                                    [6630, 1, 255]])
-        assert_equal(len(w), 1)  # for dropping Sync & R255 events
+    raw = read_raw_brainvision(vhdr_path, eog=eog, event_id=event_id)
+    events = raw._get_brainvision_events()
+    events = events[events[:, 2] != event_id['Sync On']]
+    assert_array_equal(events, [[487, 1, 253],
+                                [497, 1, 255],
+                                [1770, 1, 254],
+                                [1780, 1, 255],
+                                [3253, 1, 254],
+                                [3263, 1, 255],
+                                [4936, 1, 253],
+                                [4946, 1, 255],
+                                [6000, 1, 255],
+                                [6620, 1, 254],
+                                [6630, 1, 255]])
 
     # check that events are read and stim channel is synthesized correcly and
     # response triggers are shifted like they're supposed to be.
-    with warnings.catch_warnings(record=True) as w:
-        warnings.simplefilter('always')
-        raw = read_raw_brainvision(vhdr_path, eog=eog, preload=True,
-                                   response_trig_shift=1000)
-        events = raw._get_brainvision_events()
-        assert_array_equal(events, [[487, 1, 253],
-                                    [497, 1, 255],
-                                    [1770, 1, 254],
-                                    [1780, 1, 255],
-                                    [3253, 1, 254],
-                                    [3263, 1, 255],
-                                    [4936, 1, 253],
-                                    [4946, 1, 255],
-                                    [6000, 1, 1255],
-                                    [6620, 1, 254],
-                                    [6630, 1, 255]])
-        assert_equal(len(w), 1)  # for dropping Sync & R255 events
+    raw = read_raw_brainvision(vhdr_path, eog=eog,
+                               response_trig_shift=1000, event_id=event_id)
+    events = raw._get_brainvision_events()
+    events = events[events[:, 2] != event_id['Sync On']]
+    assert_array_equal(events, [[487, 1, 253],
+                                [497, 1, 255],
+                                [1770, 1, 254],
+                                [1780, 1, 255],
+                                [3253, 1, 254],
+                                [3263, 1, 255],
+                                [4936, 1, 253],
+                                [4946, 1, 255],
+                                [6000, 1, 1255],
+                                [6620, 1, 254],
+                                [6630, 1, 255]])
 
     # check that events are read and stim channel is synthesized correcly and
     # response triggers are ignored.
-    with warnings.catch_warnings(record=True) as w:
-        warnings.simplefilter('always')
-        raw = read_raw_brainvision(vhdr_path, eog=eog, preload=True,
+    with warnings.catch_warnings(record=True):  # ignored events
+        raw = read_raw_brainvision(vhdr_path, eog=eog,
                                    response_trig_shift=None)
-        events = raw._get_brainvision_events()
-        assert_array_equal(events, [[487, 1, 253],
-                                    [497, 1, 255],
-                                    [1770, 1, 254],
-                                    [1780, 1, 255],
-                                    [3253, 1, 254],
-                                    [3263, 1, 255],
-                                    [4936, 1, 253],
-                                    [4946, 1, 255],
-                                    [6620, 1, 254],
-                                    [6630, 1, 255]])
-        assert_equal(len(w), 1)  # for dropping Sync & R255 events
+    events = raw._get_brainvision_events()
+    events = events[events[:, 2] != event_id['Sync On']]
+    assert_array_equal(events, [[487, 1, 253],
+                                [497, 1, 255],
+                                [1770, 1, 254],
+                                [1780, 1, 255],
+                                [3253, 1, 254],
+                                [3263, 1, 255],
+                                [4936, 1, 253],
+                                [4946, 1, 255],
+                                [6620, 1, 254],
+                                [6630, 1, 255]])
 
     # check that events are read properly when event_id is specified for
     # auxiliary events
-    with warnings.catch_warnings(record=True) as w:
-        warnings.simplefilter('always')
+    with warnings.catch_warnings(record=True):  # dropped events
         raw = read_raw_brainvision(vhdr_path, eog=eog, preload=True,
                                    response_trig_shift=None,
-                                   event_id={'Sync On': 5})
-        events = raw._get_brainvision_events()
-        assert_array_equal(events, [[487, 1, 253],
-                                    [497, 1, 255],
-                                    [1770, 1, 254],
-                                    [1780, 1, 255],
-                                    [3253, 1, 254],
-                                    [3263, 1, 255],
-                                    [4936, 1, 253],
-                                    [4946, 1, 255],
-                                    [6620, 1, 254],
-                                    [6630, 1, 255],
-                                    [7630, 1, 5]])
-        assert_equal(len(w), 1)  # parsing Sync event, missing R255
+                                   event_id=event_id)
+    events = raw._get_brainvision_events()
+    assert_array_equal(events, [[487, 1, 253],
+                                [497, 1, 255],
+                                [1770, 1, 254],
+                                [1780, 1, 255],
+                                [3253, 1, 254],
+                                [3263, 1, 255],
+                                [4936, 1, 253],
+                                [4946, 1, 255],
+                                [6620, 1, 254],
+                                [6630, 1, 255],
+                                [7630, 1, 5]])
 
     assert_raises(TypeError, read_raw_brainvision, vhdr_path, eog=eog,
                   preload=True, response_trig_shift=0.1)
@@ -358,15 +451,10 @@ def test_events():
                   preload=True, response_trig_shift=np.nan)
 
     # Test that both response_trig_shit and event_id can be set
-    with warnings.catch_warnings(record=True) as w:
-        warnings.simplefilter('always')
-        read_raw_brainvision(vhdr_path, eog=eog, preload=False,
-                             response_trig_shift=100,
-                             event_id={'Sync On': 5})
-
-        mne_events = find_events(raw, stim_channel='STI 014')
-        assert_array_equal(events[:, [0, 2]], mne_events[:, [0, 2]])
-        assert_equal(len(w), 0)  # parsing the Sync event
+    read_raw_brainvision(vhdr_path, eog=eog, preload=False,
+                         response_trig_shift=100, event_id=event_id)
+    mne_events = find_events(raw, stim_channel='STI 014')
+    assert_array_equal(events[:, [0, 2]], mne_events[:, [0, 2]])
 
     # modify events and check that stim channel is updated
     index = events[:, 2] == 255
@@ -396,4 +484,19 @@ def test_events():
     assert_equal(raw.info['chs'][-1]['ch_name'], 'STI 014')
 
 
+def test_brainvision_with_montage():
+    """Test reading embedded montage information"""
+    raw = read_raw_brainvision(vhdr_v2_path, eog=eog, misc=['ReRef'])
+    for i, d in enumerate(raw.info['dig'], 1):
+        assert_equal(d['coord_frame'], FIFF.FIFFV_COORD_HEAD)
+        assert_equal(d['ident'], i)
+        assert_equal(d['kind'], FIFF.FIFFV_POINT_EEG)
+        assert_equal(len(d['r']), 3)
+
+    raw_none = read_raw_brainvision(vhdr_v2_path, verbose='error')
+    for r, n in zip(raw.info['chs'], raw_none.info['chs']):
+        if r['kind'] != n['kind']:
+            assert_array_equal(r['loc'], n['loc'])
+
+
 run_tests_if_main()
diff --git a/mne/io/bti/__init__.py b/mne/io/bti/__init__.py
index 1272b62..aeb4d18 100644
--- a/mne/io/bti/__init__.py
+++ b/mne/io/bti/__init__.py
@@ -1,4 +1,4 @@
-"""Bti module for conversion to FIF"""
+"""BTi module for conversion to FIF."""
 
 # Author: Denis A. Engemann <denis.engemann at gmail.com>
 
diff --git a/mne/io/bti/bti.py b/mne/io/bti/bti.py
index 9e41ff5..425eb7e 100644
--- a/mne/io/bti/bti.py
+++ b/mne/io/bti/bti.py
@@ -16,13 +16,13 @@ from ...utils import logger, verbose, sum_squared
 from ...transforms import (combine_transforms, invert_transform, apply_trans,
                            Transform)
 from ..constants import FIFF
-from .. import _BaseRaw, _coil_trans_to_loc, _loc_to_coil_trans, _empty_info
+from .. import BaseRaw, _coil_trans_to_loc, _loc_to_coil_trans, _empty_info
 from ..utils import _mult_cal_one, read_str
 from .constants import BTI
 from .read import (read_int32, read_int16, read_float, read_double,
                    read_transform, read_char, read_int64, read_uint16,
                    read_uint32, read_double_matrix, read_float_matrix,
-                   read_int16_matrix)
+                   read_int16_matrix, read_dev_header)
 from ...externals import six
 
 FIFF_INFO_CHS_FIELDS = ('loc',
@@ -46,19 +46,20 @@ DTYPES = dict((i, np.dtype(t)) for i, t in dtypes)
 
 
 class _bytes_io_mock_context():
+    """Make a context for BytesIO."""
 
-    def __init__(self, target):
+    def __init__(self, target):  # noqa: D102
         self.target = target
 
-    def __enter__(self):
+    def __enter__(self):  # noqa: D105
         return self.target
 
-    def __exit__(self, type, value, tb):
+    def __exit__(self, type, value, tb):  # noqa: D105
         pass
 
 
 def _bti_open(fname, *args, **kwargs):
-    """Handle bytes io"""
+    """Handle BytesIO."""
     if isinstance(fname, six.string_types):
         return open(fname, *args, **kwargs)
     elif isinstance(fname, six.BytesIO):
@@ -68,7 +69,7 @@ def _bti_open(fname, *args, **kwargs):
 
 
 def _get_bti_dev_t(adjust=0., translation=(0.0, 0.02, 0.11)):
-    """Get the general Magnes3600WH to Neuromag coordinate transform
+    """Get the general Magnes3600WH to Neuromag coordinate transform.
 
     Parameters
     ----------
@@ -98,7 +99,7 @@ def _get_bti_dev_t(adjust=0., translation=(0.0, 0.02, 0.11)):
 
 
 def _rename_channels(names, ecg_ch='E31', eog_ch=('E63', 'E64')):
-    """Renames appropriately ordered list of channel names
+    """Rename appropriately ordered list of channel names.
 
     Parameters
     ----------
@@ -140,8 +141,7 @@ def _rename_channels(names, ecg_ch='E31', eog_ch=('E63', 'E64')):
 
 
 def _read_head_shape(fname):
-    """ Helper Function """
-
+    """Read the head shape."""
     with _bti_open(fname, 'rb') as fid:
         fid.seek(BTI.FILE_HS_N_DIGPOINTS)
         _n_dig_points = read_int32(fid)
@@ -152,8 +152,7 @@ def _read_head_shape(fname):
 
 
 def _get_ctf_head_to_head_t(idx_points):
-    """ Helper function """
-
+    """Get the CTF head transform."""
     fp = idx_points.astype('>f8')
     dp = np.sum(fp[2] * (fp[0] - fp[1]))
     tmp1, tmp2 = sum_squared(fp[2]), sum_squared(fp[0] - fp[1])
@@ -170,14 +169,14 @@ def _get_ctf_head_to_head_t(idx_points):
 
 
 def _flip_fiducials(idx_points_nm):
-    # adjust order of fiducials to Neuromag
+    """Adjust order of fiducials to Neuromag."""
     # XXX presumably swap LPA and RPA
     idx_points_nm[[1, 2]] = idx_points_nm[[2, 1]]
     return idx_points_nm
 
 
 def _process_bti_headshape(fname, convert=True, use_hpi=True):
-    """Read index points and dig points from BTi head shape file
+    """Read index points and dig points from BTi head shape file.
 
     Parameters
     ----------
@@ -199,7 +198,7 @@ def _process_bti_headshape(fname, convert=True, use_hpi=True):
     if convert:
         ctf_head_t = _get_ctf_head_to_head_t(idx_points)
     else:
-        ctf_head_t = Transform('ctf_head', 'ctf_head', np.eye(4))
+        ctf_head_t = Transform('ctf_head', 'ctf_head')
 
     if dig_points is not None:
         # dig_points = apply_trans(ctf_head_t['trans'], dig_points)
@@ -215,14 +214,14 @@ def _process_bti_headshape(fname, convert=True, use_hpi=True):
 
 
 def _convert_hs_points(points, t):
-    """convert to Neuromag"""
+    """Convert headshape points to Neuromag."""
     points = apply_trans(t['trans'], points)
     points = _flip_fiducials(points).astype(np.float32)
     return points
 
 
 def _points_to_dig(points, n_idx_points, use_hpi):
-    """Put points in info dig structure"""
+    """Put points in info dig structure."""
     idx_idents = list(range(1, 4)) + list(range(1, (n_idx_points + 1) - 3))
     dig = []
     for idx in range(points.shape[0]):
@@ -247,7 +246,7 @@ def _points_to_dig(points, n_idx_points, use_hpi):
 
 
 def _convert_coil_trans(coil_trans, dev_ctf_t, bti_dev_t):
-    """ Helper Function """
+    """Convert the coil trans."""
     t = combine_transforms(invert_transform(dev_ctf_t), bti_dev_t,
                            'ctf_head', 'meg')
     t = np.dot(t['trans'], coil_trans)
@@ -255,7 +254,7 @@ def _convert_coil_trans(coil_trans, dev_ctf_t, bti_dev_t):
 
 
 def _correct_offset(fid):
-    """ Align fid pointer """
+    """Align fid pointer."""
     current = fid.tell()
     if ((current % BTI.FILE_CURPOS) != 0):
         offset = current % BTI.FILE_CURPOS
@@ -263,7 +262,7 @@ def _correct_offset(fid):
 
 
 def _read_config(fname):
-    """Read BTi system config file
+    """Read BTi system config file.
 
     Parameters
     ----------
@@ -274,9 +273,7 @@ def _read_config(fname):
     -------
     cfg : dict
         The config blocks found.
-
     """
-
     with _bti_open(fname, 'rb') as fid:
         cfg = dict()
         cfg['hdr'] = {'version': read_int16(fid),
@@ -303,15 +300,16 @@ def _read_config(fname):
         for block in range(cfg['hdr']['total_user_blocks']):
             ub = dict()
 
-            ub['hdr'] = {'nbytes': read_int32(fid),
+            ub['hdr'] = {'nbytes': read_uint32(fid),
                          'kind': read_str(fid, 20),
                          'checksum': read_int32(fid),
                          'username': read_str(fid, 32),
-                         'timestamp': read_int32(fid),
-                         'user_space_size': read_int32(fid),
+                         'timestamp': read_uint32(fid),
+                         'user_space_size': read_uint32(fid),
                          'reserved': read_char(fid, 32)}
 
             _correct_offset(fid)
+            start_bytes = fid.tell()
             kind = ub['hdr'].pop('kind')
             if not kind:  # make sure reading goes right. Should never be empty
                 raise RuntimeError('Could not read user block. Probably you '
@@ -467,20 +465,19 @@ def _read_config(fname):
                         cols = dta['hdr']['n_e_values']
                         dta['etable'] = read_float_matrix(fid, rows, cols)
 
-                        _correct_offset(fid)
-
                 elif any([kind == BTI.UB_B_WEIGHTS_USED,
                           kind[:4] == BTI.UB_B_WEIGHT_TABLE]):
-                    dta['hdr'] = {'version': read_int32(fid),
-                                  'entry_size': read_int32(fid),
-                                  'n_entries': read_int32(fid),
-                                  'name': read_str(fid, 32),
-                                  'description': read_str(fid, 80),
-                                  'n_anlg': read_int32(fid),
-                                  'n_dsp': read_int32(fid),
-                                  'reserved': read_str(fid, 72)}
-
+                    dta['hdr'] = dict(
+                        version=read_int32(fid),
+                        n_bytes=read_uint32(fid),
+                        n_entries=read_uint32(fid),
+                        name=read_str(fid, 32))
                     if dta['hdr']['version'] == 2:
+                        dta['hdr'].update(
+                            description=read_str(fid, 80),
+                            n_anlg=read_uint32(fid),
+                            n_dsp=read_uint32(fid),
+                            reserved=read_str(fid, 72))
                         dta['ch_names'] = [read_str(fid, 16) for ch in
                                            range(dta['hdr']['n_entries'])]
                         dta['anlg_ch_names'] = [read_str(fid, 16) for ch in
@@ -488,34 +485,39 @@ def _read_config(fname):
 
                         dta['dsp_ch_names'] = [read_str(fid, 16) for ch in
                                                range(dta['hdr']['n_dsp'])]
-
-                        rows = dta['hdr']['n_entries']
-                        cols = dta['hdr']['n_dsp']
-                        dta['dsp_wts'] = read_float_matrix(fid, rows, cols)
-                        cols = dta['hdr']['n_anlg']
-                        dta['anlg_wts'] = read_int16_matrix(fid, rows, cols)
-
+                        dta['dsp_wts'] = read_float_matrix(
+                            fid, dta['hdr']['n_entries'], dta['hdr']['n_dsp'])
+                        dta['anlg_wts'] = read_int16_matrix(
+                            fid, dta['hdr']['n_entries'], dta['hdr']['n_anlg'])
                     else:  # handle MAGNES2500 naming scheme
+                        fid.seek(start_bytes + ub['hdr']['user_space_size'] -
+                                 dta['hdr']['n_bytes'] *
+                                 dta['hdr']['n_entries'], 0)
+
+                        dta['hdr']['n_dsp'] = dta['hdr']['n_bytes'] // 4 - 2
+                        assert (dta['hdr']['n_dsp'] ==
+                                len(BTI_WH2500_REF_MAG) +
+                                len(BTI_WH2500_REF_GRAD))
                         dta['ch_names'] = ['WH2500'] * dta['hdr']['n_entries']
-                        dta['anlg_ch_names'] = BTI_WH2500_REF_MAG[:3]
-                        dta['hdr']['n_anlg'] = len(dta['anlg_ch_names'])
-                        dta['dsp_ch_names'] = BTI_WH2500_REF_GRAD
-                        dta['hdr.n_dsp'] = len(dta['dsp_ch_names'])
-                        dta['anlg_wts'] = np.zeros((dta['hdr']['n_entries'],
-                                                    dta['hdr']['n_anlg']),
-                                                   dtype='i2')
-                        dta['dsp_wts'] = np.zeros((dta['hdr']['n_entries'],
-                                                   dta['hdr']['n_dsp']),
-                                                  dtype='f4')
+                        dta['hdr']['n_anlg'] = 3
+                        # These orders could be wrong, so don't set them
+                        # for now
+                        # dta['anlg_ch_names'] = BTI_WH2500_REF_MAG[:3]
+                        # dta['dsp_ch_names'] = (BTI_WH2500_REF_GRAD +
+                        #                        BTI_WH2500_REF_MAG)
+                        dta['anlg_wts'] = np.zeros(
+                            (dta['hdr']['n_entries'], dta['hdr']['n_anlg']),
+                            dtype='i2')
+                        dta['dsp_wts'] = np.zeros(
+                            (dta['hdr']['n_entries'], dta['hdr']['n_dsp']),
+                            dtype='f4')
                         for n in range(dta['hdr']['n_entries']):
-                            dta['anlg_wts'][d] = read_int16_matrix(
+                            dta['anlg_wts'][n] = read_int16_matrix(
                                 fid, 1, dta['hdr']['n_anlg'])
                             read_int16(fid)
-                            dta['dsp_wts'][d] = read_float_matrix(
+                            dta['dsp_wts'][n] = read_float_matrix(
                                 fid, 1, dta['hdr']['n_dsp'])
 
-                        _correct_offset(fid)
-
                 elif kind == BTI.UB_B_TRIG_MASK:
                     dta['version'] = read_int32(fid)
                     dta['entries'] = read_int32(fid)
@@ -534,16 +536,18 @@ def _read_config(fname):
                 dta['unknown'] = {'hdr': read_char(fid,
                                   ub['hdr']['user_space_size'])}
 
+            n_read = fid.tell() - start_bytes
+            if n_read != ub['hdr']['user_space_size']:
+                raise RuntimeError('Internal MNE reading error, read size %d '
+                                   '!= %d expected size for kind %s'
+                                   % (n_read, ub['hdr']['user_space_size'],
+                                      kind))
             ub.update(dta)  # finally update the userblock data
             _correct_offset(fid)  # after reading.
 
         cfg['chs'] = list()
 
         # prepare reading channels
-        def dev_header(x):
-            return dict(size=read_int32(x), checksum=read_int32(x),
-                        reserved=read_str(x, 32))
-
         for channel in range(cfg['hdr']['total_chans']):
             ch = {'name': read_str(fid, 16),
                   'chan_no': read_int16(fid),
@@ -563,10 +567,10 @@ def _read_config(fname):
             _correct_offset(fid)  # before and after
             dta = dict()
             if ch['ch_type'] in [BTI.CHTYPE_MEG, BTI.CHTYPE_REFERENCE]:
-                dev = {'device_info': dev_header(fid),
+                dev = {'device_info': read_dev_header(fid),
                        'inductance': read_float(fid),
                        'padding': read_str(fid, 4),
-                       'transform': _correct_trans(read_transform(fid)),
+                       'transform': _correct_trans(read_transform(fid), False),
                        'xform_flag': read_int16(fid),
                        'total_loops': read_int16(fid)}
 
@@ -585,30 +589,30 @@ def _read_config(fname):
                     dta['loops'] += [d]
 
             elif ch['ch_type'] == BTI.CHTYPE_EEG:
-                dta = {'device_info': dev_header(fid),
+                dta = {'device_info': read_dev_header(fid),
                        'impedance': read_float(fid),
                        'padding': read_str(fid, 4),
                        'transform': read_transform(fid),
                        'reserved': read_char(fid, 32)}
 
             elif ch['ch_type'] == BTI.CHTYPE_EXTERNAL:
-                dta = {'device_info': dev_header(fid),
+                dta = {'device_info': read_dev_header(fid),
                        'user_space_size': read_int32(fid),
                        'reserved': read_str(fid, 32)}
 
             elif ch['ch_type'] == BTI.CHTYPE_TRIGGER:
-                dta = {'device_info': dev_header(fid),
+                dta = {'device_info': read_dev_header(fid),
                        'user_space_size': read_int32(fid)}
                 fid.seek(2, 1)
                 dta['reserved'] = read_str(fid, 32)
 
             elif ch['ch_type'] in [BTI.CHTYPE_UTILITY, BTI.CHTYPE_DERIVED]:
-                dta = {'device_info': dev_header(fid),
+                dta = {'device_info': read_dev_header(fid),
                        'user_space_size': read_int32(fid),
                        'reserved': read_str(fid, 32)}
 
             elif ch['ch_type'] == BTI.CHTYPE_SHORTED:
-                dta = {'device_info': dev_header(fid),
+                dta = {'device_info': read_dev_header(fid),
                        'reserved': read_str(fid, 32)}
 
             ch.update(dta)  # add data collected
@@ -618,7 +622,7 @@ def _read_config(fname):
 
 
 def _read_epoch(fid):
-    """Read BTi PDF epoch"""
+    """Read BTi PDF epoch."""
     out = {'pts_in_epoch': read_int32(fid),
            'epoch_duration': read_float(fid),
            'expected_iti': read_float(fid),
@@ -633,7 +637,7 @@ def _read_epoch(fid):
 
 
 def _read_channel(fid):
-    """Read BTi PDF channel"""
+    """Read BTi PDF channel."""
     out = {'chan_label': read_str(fid, 16),
            'chan_no': read_int16(fid),
            'attributes': read_int16(fid),
@@ -655,7 +659,7 @@ def _read_channel(fid):
 
 
 def _read_event(fid):
-    """Read BTi PDF event"""
+    """Read BTi PDF event."""
     out = {'event_name': read_str(fid, 16),
            'start_lat': read_float(fid),
            'end_lat': read_float(fid),
@@ -670,8 +674,7 @@ def _read_event(fid):
 
 
 def _read_process(fid):
-    """Read BTi PDF process"""
-
+    """Read BTi PDF process."""
     out = {'nbytes': read_int32(fid),
            'process_type': read_str(fid, 20),
            'checksum': read_int32(fid),
@@ -716,8 +719,7 @@ def _read_process(fid):
 
 
 def _read_assoc_file(fid):
-    """Read BTi PDF assocfile"""
-
+    """Read BTi PDF assocfile."""
     out = {'file_id': read_int16(fid),
            'length': read_int16(fid)}
 
@@ -728,8 +730,7 @@ def _read_assoc_file(fid):
 
 
 def _read_pfid_ed(fid):
-    """Read PDF ed file"""
-
+    """Read PDF ed file."""
     out = {'comment_size': read_int32(fid),
            'name': read_str(fid, 17)}
 
@@ -750,7 +751,7 @@ def _read_pfid_ed(fid):
 
 
 def _read_coil_def(fid):
-    """ Read coil definition """
+    """Read coil definition."""
     coildef = {'position': read_double_matrix(fid, 1, 3),
                'orientation': read_double_matrix(fid, 1, 3),
                'radius': read_double(fid),
@@ -763,8 +764,7 @@ def _read_coil_def(fid):
 
 
 def _read_ch_config(fid):
-    """Read BTi channel config"""
-
+    """Read BTi channel config."""
     cfg = {'name': read_str(fid, BTI.FILE_CONF_CH_NAME),
            'chan_no': read_int16(fid),
            'ch_type': read_uint16(fid),
@@ -814,7 +814,7 @@ def _read_ch_config(fid):
 
 
 def _read_bti_header_pdf(pdf_fname):
-    """Read header from pdf file"""
+    """Read header from pdf file."""
     with _bti_open(pdf_fname, 'rb') as fid:
         fid.seek(-8, 2)
         start = fid.tell()
@@ -892,9 +892,7 @@ def _read_bti_header_pdf(pdf_fname):
 
 
 def _read_bti_header(pdf_fname, config_fname, sort_by_ch_name=True):
-    """ Read bti PDF header
-    """
-
+    """Read bti PDF header."""
     info = _read_bti_header_pdf(pdf_fname) if pdf_fname is not None else dict()
     cfg = _read_config(config_fname)
     info['bti_transform'] = cfg['transforms']
@@ -962,17 +960,20 @@ def _read_bti_header(pdf_fname, config_fname, sort_by_ch_name=True):
     return info
 
 
-def _correct_trans(t):
-    """Helper to convert to a transformation matrix"""
+def _correct_trans(t, check=True):
+    """Convert to a transformation matrix."""
     t = np.array(t, np.float64)
     t[:3, :3] *= t[3, :3][:, np.newaxis]  # apply scalings
     t[3, :3] = 0.  # remove them
-    assert t[3, 3] == 1.
+    if check:
+        assert t[3, 3] == 1.
+    else:
+        t[3, 3] = 1.
     return t
 
 
-class RawBTi(_BaseRaw):
-    """ Raw object from 4D Neuroimaging MagnesWH3600 data
+class RawBTi(BaseRaw):
+    """Raw object from 4D Neuroimaging MagnesWH3600 data.
 
     Parameters
     ----------
@@ -1011,15 +1012,17 @@ class RawBTi(_BaseRaw):
         ..versionadded:: 0.11
 
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
     """
+
     @verbose
     def __init__(self, pdf_fname, config_fname='config',
                  head_shape_fname='hs_file', rotation_x=0.,
                  translation=(0.0, 0.02, 0.11), convert=True,
                  rename_channels=True, sort_by_ch_name=True,
                  ecg_ch='E31', eog_ch=('E63', 'E64'),
-                 preload=False, verbose=None):
+                 preload=False, verbose=None):  # noqa: D102
         info, bti_info = _get_bti_info(
             pdf_fname=pdf_fname, config_fname=config_fname,
             head_shape_fname=head_shape_fname, rotation_x=rotation_x,
@@ -1035,7 +1038,7 @@ class RawBTi(_BaseRaw):
             last_samps=[bti_info['total_slices'] - 1], verbose=verbose)
 
     def _read_segment_file(self, data, idx, fi, start, stop, cals, mult):
-        """Read a segment of data from a file"""
+        """Read a segment of data from a file."""
         bti_info = self._raw_extras[fi]
         fname = bti_info['pdf_fname']
         dtype = bti_info['dtype']
@@ -1072,7 +1075,7 @@ class RawBTi(_BaseRaw):
 def _get_bti_info(pdf_fname, config_fname, head_shape_fname, rotation_x,
                   translation, convert, ecg_ch, eog_ch, rename_channels=True,
                   sort_by_ch_name=True):
-    """Helper to read BTI info
+    """Read BTI info.
 
     Note. This helper supports partial construction of infos when `pdf_fname`
     is None. Some datasets, such as the HCP, are shipped as a large collection
@@ -1096,9 +1099,14 @@ def _get_bti_info(pdf_fname, config_fname, head_shape_fname, rotation_x,
 
     if not isinstance(config_fname, six.BytesIO):
         if not op.isabs(config_fname):
-            config_fname = op.abspath(config_fname)
-
-        if not op.exists(config_fname):
+            config_tries = [op.abspath(config_fname),
+                            op.abspath(op.join(op.dirname(pdf_fname),
+                                               config_fname))]
+            for config_try in config_tries:
+                if op.isfile(config_try):
+                    config_fname = config_try
+                    break
+        if not op.isfile(config_fname):
             raise ValueError('Could not find the config file %s. Please check'
                              ' whether you are in the right directory '
                              'or pass the full name' % config_fname)
@@ -1113,7 +1121,8 @@ def _get_bti_info(pdf_fname, config_fname, head_shape_fname, rotation_x,
         if not op.isfile(head_shape_fname):
             raise ValueError('Could not find the head_shape file "%s". '
                              'You should check whether you are in the '
-                             'right directory or pass the full file name.'
+                             'right directory, pass the full file name, '
+                             'or pass head_shape_fname=None.'
                              % orig_name)
 
     logger.info('Reading 4D PDF file %s...' % pdf_fname)
@@ -1125,10 +1134,7 @@ def _get_bti_info(pdf_fname, config_fname, head_shape_fname, rotation_x,
 
     # for old backward compatibility and external processing
     rotation_x = 0. if rotation_x is None else rotation_x
-    if convert:
-        bti_dev_t = _get_bti_dev_t(rotation_x, translation)
-    else:
-        bti_dev_t = np.eye(4)
+    bti_dev_t = _get_bti_dev_t(rotation_x, translation) if convert else None
     bti_dev_t = Transform('ctf_meg', 'meg', bti_dev_t)
 
     use_hpi = False  # hard coded, but marked as later option.
@@ -1217,7 +1223,7 @@ def _get_bti_info(pdf_fname, config_fname, head_shape_fname, rotation_x,
 
         elif chan_4d.startswith('M'):
             chan_info['kind'] = FIFF.FIFFV_REF_MEG_CH
-            chan_info['coil_type'] = FIFF.FIFFV_COIL_MAGNES_R_MAG
+            chan_info['coil_type'] = FIFF.FIFFV_COIL_MAGNES_REF_MAG
             chan_info['coord_frame'] = meg_frame
             chan_info['unit'] = FIFF.FIFF_UNIT_T
 
@@ -1226,9 +1232,10 @@ def _get_bti_info(pdf_fname, config_fname, head_shape_fname, rotation_x,
             chan_info['coord_frame'] = meg_frame
             chan_info['unit'] = FIFF.FIFF_UNIT_T_M
             if chan_4d in ('GxxA', 'GyyA'):
-                chan_info['coil_type'] = FIFF.FIFFV_COIL_MAGNES_R_GRAD_DIA
+                chan_info['coil_type'] = FIFF.FIFFV_COIL_MAGNES_REF_GRAD
             elif chan_4d in ('GyxA', 'GzxA', 'GzyA'):
-                chan_info['coil_type'] = FIFF.FIFFV_COIL_MAGNES_R_GRAD_OFF
+                chan_info['coil_type'] = \
+                    FIFF.FIFFV_COIL_MAGNES_OFFDIAG_REF_GRAD
 
         elif chan_4d.startswith('EEG'):
             chan_info['kind'] = FIFF.FIFFV_EEG_CH
@@ -1269,12 +1276,12 @@ def _get_bti_info(pdf_fname, config_fname, head_shape_fname, rotation_x,
                                    'meg', 'ctf_head')
             dev_head_t = combine_transforms(t, ctf_head_t, 'meg', 'head')
         else:
-            dev_head_t = Transform('meg', 'head', np.eye(4))
+            dev_head_t = Transform('meg', 'head')
         logger.info('Done.')
     else:
         logger.info('... no headshape file supplied, doing nothing.')
-        dev_head_t = Transform('meg', 'head', np.eye(4))
-        ctf_head_t = Transform('ctf_head', 'head', np.eye(4))
+        dev_head_t = Transform('meg', 'head')
+        ctf_head_t = Transform('ctf_head', 'head')
     info.update(dev_head_t=dev_head_t, dev_ctf_t=dev_ctf_t,
                 ctf_head_t=ctf_head_t)
 
@@ -1322,7 +1329,7 @@ def read_raw_bti(pdf_fname, config_fname='config',
                  rename_channels=True, sort_by_ch_name=True,
                  ecg_ch='E31', eog_ch=('E63', 'E64'), preload=False,
                  verbose=None):
-    """ Raw object from 4D Neuroimaging MagnesWH3600 data
+    """Raw object from 4D Neuroimaging MagnesWH3600 data.
 
     .. note::
         1. Currently direct inclusion of reference channel weights
@@ -1371,7 +1378,8 @@ def read_raw_bti(pdf_fname, config_fname='config',
         ..versionadded:: 0.11
 
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
diff --git a/mne/io/bti/read.py b/mne/io/bti/read.py
index 30ae81b..6c24c18 100644
--- a/mne/io/bti/read.py
+++ b/mne/io/bti/read.py
@@ -3,9 +3,11 @@
 
 import numpy as np
 
+from ..utils import read_str
+
 
 def _unpack_matrix(fid, rows, cols, dtype, out_dtype):
-    """ Aux Function """
+    """Unpack matrix."""
     dtype = np.dtype(dtype)
 
     string = fid.read(int(dtype.itemsize * rows * cols))
@@ -15,7 +17,7 @@ def _unpack_matrix(fid, rows, cols, dtype, out_dtype):
 
 
 def _unpack_simple(fid, dtype, out_dtype):
-    """ Aux Function """
+    """Unpack a NumPy type."""
     dtype = np.dtype(dtype)
     string = fid.read(dtype.itemsize)
     out = np.fromstring(string, dtype=dtype).astype(out_dtype)
@@ -26,83 +28,89 @@ def _unpack_simple(fid, dtype, out_dtype):
 
 
 def read_char(fid, count=1):
-    " Read character from bti file """
+    """Read character from bti file."""
     return _unpack_simple(fid, '>S%s' % count, 'S')
 
 
 def read_bool(fid):
-    """ Read bool value from bti file """
+    """Read bool value from bti file."""
     return _unpack_simple(fid, '>?', np.bool)
 
 
 def read_uint8(fid):
-    """ Read unsigned 8bit integer from bti file """
+    """Read unsigned 8bit integer from bti file."""
     return _unpack_simple(fid, '>u1', np.uint8)
 
 
 def read_int8(fid):
-    """ Read 8bit integer from bti file """
+    """Read 8bit integer from bti file."""
     return _unpack_simple(fid, '>i1', np.int8)
 
 
 def read_uint16(fid):
-    """ Read unsigned 16bit integer from bti file """
+    """Read unsigned 16bit integer from bti file."""
     return _unpack_simple(fid, '>u2', np.uint16)
 
 
 def read_int16(fid):
-    """ Read 16bit integer from bti file """
+    """Read 16bit integer from bti file."""
     return _unpack_simple(fid, '>i2', np.int16)
 
 
 def read_uint32(fid):
-    """ Read unsigned 32bit integer from bti file """
+    """Read unsigned 32bit integer from bti file."""
     return _unpack_simple(fid, '>u4', np.uint32)
 
 
 def read_int32(fid):
-    """ Read 32bit integer from bti file """
+    """Read 32bit integer from bti file."""
     return _unpack_simple(fid, '>i4', np.int32)
 
 
 def read_uint64(fid):
-    """ Read unsigned 64bit integer from bti file """
+    """Read unsigned 64bit integer from bti file."""
     return _unpack_simple(fid, '>u8', np.uint64)
 
 
 def read_int64(fid):
-    """ Read 64bit integer from bti file """
+    """Read 64bit integer from bti file."""
     return _unpack_simple(fid, '>u8', np.int64)
 
 
 def read_float(fid):
-    """ Read 32bit float from bti file """
+    """Read 32bit float from bti file."""
     return _unpack_simple(fid, '>f4', np.float32)
 
 
 def read_double(fid):
-    """ Read 64bit float from bti file """
+    """Read 64bit float from bti file."""
     return _unpack_simple(fid, '>f8', np.float64)
 
 
 def read_int16_matrix(fid, rows, cols):
-    """ Read 16bit integer matrix from bti file """
+    """Read 16bit integer matrix from bti file."""
     return _unpack_matrix(fid, rows, cols, dtype='>i2',
                           out_dtype=np.int16)
 
 
 def read_float_matrix(fid, rows, cols):
-    """ Read 32bit float matrix from bti file """
+    """Read 32bit float matrix from bti file."""
     return _unpack_matrix(fid, rows, cols, dtype='>f4',
                           out_dtype=np.float32)
 
 
 def read_double_matrix(fid, rows, cols):
-    """ Read 64bit float matrix from bti file """
+    """Read 64bit float matrix from bti file."""
     return _unpack_matrix(fid, rows, cols, dtype='>f8',
                           out_dtype=np.float64)
 
 
 def read_transform(fid):
-    """ Read 64bit float matrix transform from bti file """
+    """Read 64bit float matrix transform from bti file."""
     return read_double_matrix(fid, rows=4, cols=4)
+
+
+def read_dev_header(x):
+    """Create a dev header."""
+    return dict(size=read_int32(x), checksum=read_int32(x),
+                reserved=read_str(x, 32))
diff --git a/mne/io/bti/tests/test_bti.py b/mne/io/bti/tests/test_bti.py
index 49c7719..d36955b 100644
--- a/mne/io/bti/tests/test_bti.py
+++ b/mne/io/bti/tests/test_bti.py
@@ -12,7 +12,9 @@ import numpy as np
 from numpy.testing import (assert_array_almost_equal, assert_array_equal,
                            assert_allclose)
 from nose.tools import assert_true, assert_raises, assert_equal
+import pytest
 
+from mne.datasets import testing
 from mne.io import read_raw_fif, read_raw_bti
 from mne.io.bti.bti import (_read_config, _process_bti_headshape,
                             _read_bti_header, _get_bti_dev_t,
@@ -38,10 +40,19 @@ exported_fnames = [op.join(base_dir, 'exported4D_%s_raw.fif' % a)
                    for a in archs]
 tmp_raw_fname = op.join(base_dir, 'tmp_raw.fif')
 
+fname_2500 = op.join(testing.data_path(download=False), 'BTi', 'erm_HFH',
+                     'c,rfDC')
+
 # the 4D exporter doesn't export all channels, so we confine our comparison
 NCH = 248
 
 
+ at testing.requires_testing_data
+def test_read_2500():
+    """Test reading data from 2500 system."""
+    _test_raw_reader(read_raw_bti, pdf_fname=fname_2500, head_shape_fname=None)
+
+
 def test_read_config():
     """Test read bti config file."""
     # for config in config_fname, config_solaris_fname:
@@ -59,7 +70,7 @@ def test_crop_append():
     y, t = raw[:]
     t0, t1 = 0.25 * t[-1], 0.75 * t[-1]
     mask = (t0 <= t) * (t <= t1)
-    raw_ = raw.copy().crop(t0, t1, copy=False)
+    raw_ = raw.copy().crop(t0, t1)
     y_, _ = raw_[:]
     assert_true(y_.shape[1] == mask.sum())
     assert_true(y_.shape[0] == y.shape[0])
@@ -87,6 +98,7 @@ def test_transforms():
         assert_array_equal(dev_head_t_new['trans'], dev_head_t_old['trans'])
 
 
+ at pytest.mark.slowtest
 def test_raw():
     """Test bti conversion to Raw object."""
     for pdf, config, hs, exported in zip(pdf_fnames, config_fnames, hs_fnames,
@@ -97,7 +109,7 @@ def test_raw():
                       preload=False)
         if op.exists(tmp_raw_fname):
             os.remove(tmp_raw_fname)
-        ex = read_raw_fif(exported, preload=True, add_eeg_ref=False)
+        ex = read_raw_fif(exported, preload=True)
         ra = read_raw_bti(pdf, config, hs, preload=False)
         assert_true('RawBTi' in repr(ra))
         assert_equal(ex.ch_names[:NCH], ra.ch_names[:NCH])
@@ -132,7 +144,7 @@ def test_raw():
                                     ra.info[key][ent])
 
         ra.save(tmp_raw_fname)
-        re = read_raw_fif(tmp_raw_fname, add_eeg_ref=False)
+        re = read_raw_fif(tmp_raw_fname)
         print(re)
         for key in ('dev_head_t', 'dev_ctf_t', 'ctf_head_t'):
             assert_true(isinstance(re.info[key], dict))
diff --git a/mne/io/cnt/__init__.py b/mne/io/cnt/__init__.py
index 7f9ac44..5021fd7 100644
--- a/mne/io/cnt/__init__.py
+++ b/mne/io/cnt/__init__.py
@@ -1 +1,3 @@
+"""CNT data reader."""
+
 from .cnt import read_raw_cnt
diff --git a/mne/io/cnt/cnt.py b/mne/io/cnt/cnt.py
index 80413b8..88fcff5 100644
--- a/mne/io/cnt/cnt.py
+++ b/mne/io/cnt/cnt.py
@@ -1,5 +1,4 @@
-"""Conversion tool from Neuroscan CNT to FIF
-"""
+"""Conversion tool from Neuroscan CNT to FIF."""
 
 # Author: Jaakko Leppakangas <jaeilepp at student.jyu.fi>
 #
@@ -15,7 +14,7 @@ from ...channels.layout import _topo_to_sphere
 from ..constants import FIFF
 from ..utils import _mult_cal_one, _find_channels, _create_chs
 from ..meas_info import _empty_info
-from ..base import _BaseRaw, _check_update_montage
+from ..base import BaseRaw, _check_update_montage
 from ..utils import read_str
 
 
@@ -75,7 +74,8 @@ def read_raw_cnt(input_fname, montage, eog=(), misc=(), ecg=(), emg=(),
         file name of a memory-mapped file which is used to store the data
         on the hard drive (slower, requires less memory).
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -96,7 +96,7 @@ def read_raw_cnt(input_fname, montage, eog=(), misc=(), ecg=(), emg=(),
 
 
 def _get_cnt_info(input_fname, eog, ecg, emg, misc, data_format, date_format):
-    """Helper for reading the cnt header."""
+    """Read the cnt header."""
     data_offset = 900  # Size of the 'SETUP' header.
     cnt_info = dict()
     # Reading only the fields of interest. Structure of the whole header at
@@ -129,34 +129,27 @@ def _get_cnt_info(input_fname, eog, ecg, emg, misc, data_format, date_format):
         session_date = read_str(fid, 10)
         time = read_str(fid, 12)
         date = session_date.split('/')
-        if len(date) != 3:
-            meas_date = -1
-        else:
+        if len(date) == 3 and len(time) == 3:
             if date[2].startswith('9'):
                 date[2] = '19' + date[2]
             elif len(date[2]) == 2:
                 date[2] = '20' + date[2]
             time = time.split(':')
-            if len(time) == 3:
-                if date_format == 'mm/dd/yy':
-                    pass
-                elif date_format == 'dd/mm/yy':
-                    date[0], date[1] = date[1], date[0]
-                else:
-                    raise ValueError("Only date formats 'mm/dd/yy' and "
-                                     "'dd/mm/yy' supported. "
-                                     "Got '%s'." % date_format)
-                # Assuming mm/dd/yy
-                date = datetime.datetime(int(date[2]), int(date[0]),
-                                         int(date[1]), int(time[0]),
-                                         int(time[1]), int(time[2]))
-                meas_date = calendar.timegm(date.utctimetuple())
-            else:
-                meas_date = -1
-        if meas_date < 0:
-            warn('  Could not parse meas date from the header. Setting to '
-                 '[0, 0]...')
-            meas_date = 0
+            if date_format == 'dd/mm/yy':
+                date[0], date[1] = date[1], date[0]
+            elif date_format != 'mm/dd/yy':
+                raise ValueError("Only date formats 'mm/dd/yy' and "
+                                 "'dd/mm/yy' supported. "
+                                 "Got '%s'." % date_format)
+            # Assuming mm/dd/yy
+            date = datetime.datetime(int(date[2]), int(date[0]),
+                                     int(date[1]), int(time[0]),
+                                     int(time[1]), int(time[2]))
+            meas_date = np.array([calendar.timegm(date.utctimetuple()), 0])
+        else:
+            warn('  Could not parse meas date from the header. '
+                 'Setting to None.')
+            meas_date = None
         fid.seek(370)
         n_channels = np.fromfile(fid, dtype='<u2', count=1)[0]
         fid.seek(376)
@@ -183,7 +176,10 @@ def _get_cnt_info(input_fname, eog, ecg, emg, misc, data_format, date_format):
         cnt_info['continuous_seconds'] = np.fromfile(fid, dtype='<f4',
                                                      count=1)[0]
 
-        data_size = event_offset - (900 + 75 * n_channels)
+        if event_offset < data_offset:  # no events
+            data_size = n_samples * n_channels
+        else:
+            data_size = event_offset - (data_offset + 75 * n_channels)
         if data_format == 'auto':
             if (n_samples == 0 or
                     data_size // (n_samples * n_channels) not in [2, 4]):
@@ -228,25 +224,30 @@ def _get_cnt_info(input_fname, eog, ecg, emg, misc, data_format, date_format):
             cal = np.fromfile(fid, dtype='f4', count=1)
             cals.append(cal * sensitivity * 1e-6 / 204.8)
 
-        fid.seek(event_offset)
-        event_type = np.fromfile(fid, dtype='<i1', count=1)[0]
-        event_size = np.fromfile(fid, dtype='<i4', count=1)[0]
-        if event_type == 1:
-            event_bytes = 8
-        elif event_type == 2:
-            event_bytes = 19
+        if event_offset > data_offset:
+            fid.seek(event_offset)
+            event_type = np.fromfile(fid, dtype='<i1', count=1)[0]
+            event_size = np.fromfile(fid, dtype='<i4', count=1)[0]
+            if event_type == 1:
+                event_bytes = 8
+            elif event_type in (2, 3):
+                event_bytes = 19
+            else:
+                raise IOError('Unexpected event size.')
+            n_events = event_size // event_bytes
         else:
-            raise IOError('Unexpected event size.')
+            n_events = 0
 
-        n_events = event_size // event_bytes
         stim_channel = np.zeros(n_samples)  # Construct stim channel
         for i in range(n_events):
             fid.seek(event_offset + 9 + i * event_bytes)
             event_id = np.fromfile(fid, dtype='u2', count=1)[0]
             fid.seek(event_offset + 9 + i * event_bytes + 4)
             offset = np.fromfile(fid, dtype='<i4', count=1)[0]
-            event_time = (offset - 900 - 75 * n_channels) // (n_channels *
-                                                              n_bytes)
+            if event_type == 3:
+                offset *= n_bytes * n_channels
+            event_time = offset - 900 - 75 * n_channels
+            event_time //= n_channels * n_bytes
             stim_channel[event_time - 1] = event_id
 
     info = _empty_info(sfreq)
@@ -284,14 +285,14 @@ def _get_cnt_info(input_fname, eog, ecg, emg, misc, data_format, date_format):
     baselines.append(0)  # For stim channel
     cnt_info.update(baselines=np.array(baselines), n_samples=n_samples,
                     stim_channel=stim_channel, n_bytes=n_bytes)
-    info.update(filename=input_fname, meas_date=np.array([meas_date, 0]),
+    info.update(meas_date=meas_date,
                 description=str(session_label), buffer_size_sec=10., bads=bads,
                 subject_info=subject_info, chs=chs)
     info._update_redundant()
     return info, cnt_info
 
 
-class RawCNT(_BaseRaw):
+class RawCNT(BaseRaw):
     """Raw object from Neuroscan CNT file.
 
     .. Note::
@@ -344,15 +345,17 @@ class RawCNT(_BaseRaw):
         file name of a memory-mapped file which is used to store the data
         on the hard drive (slower, requires less memory).
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     See Also
     --------
     mne.io.Raw : Documentation of attribute and methods.
     """
+
     def __init__(self, input_fname, montage, eog=(), misc=(), ecg=(), emg=(),
                  data_format='auto', date_format='mm/dd/yy', preload=False,
-                 verbose=None):
+                 verbose=None):  # noqa: D102
         input_fname = path.abspath(input_fname)
         info, cnt_info = _get_cnt_info(input_fname, eog, ecg, emg, misc,
                                        data_format, date_format)
@@ -365,7 +368,7 @@ class RawCNT(_BaseRaw):
 
     @verbose
     def _read_segment_file(self, data, idx, fi, start, stop, cals, mult):
-        """Take a chunk of raw data, multiply by mult or cals, and store"""
+        """Take a chunk of raw data, multiply by mult or cals, and store."""
         n_channels = self.info['nchan'] - 1  # Stim channel already read.
         channel_offset = self._raw_extras[0]['channel_offset']
         baselines = self._raw_extras[0]['baselines']
diff --git a/mne/io/compensator.py b/mne/io/compensator.py
index 70060d5..fa13c68 100644
--- a/mne/io/compensator.py
+++ b/mne/io/compensator.py
@@ -5,8 +5,7 @@ from .constants import FIFF
 
 
 def get_current_comp(info):
-    """Get the current compensation in effect in the data
-    """
+    """Get the current compensation in effect in the data."""
     comp = None
     first_comp = -1
     for k, chan in enumerate(info['chs']):
@@ -21,8 +20,7 @@ def get_current_comp(info):
 
 
 def set_current_comp(info, comp):
-    """Set the current compensation in effect in the data
-    """
+    """Set the current compensation in effect in the data."""
     comp_now = get_current_comp(info)
     for k, chan in enumerate(info['chs']):
         if chan['kind'] == FIFF.FIFFV_MEG_CH:
@@ -31,8 +29,7 @@ def set_current_comp(info, comp):
 
 
 def _make_compensator(info, grade):
-    """Auxiliary function for make_compensator
-    """
+    """Auxiliary function for make_compensator."""
     for k in range(len(info['comps'])):
         if info['comps'][k]['kind'] == grade:
             this_data = info['comps'][k]['data']
@@ -67,7 +64,7 @@ def _make_compensator(info, grade):
 
 
 def make_compensator(info, from_, to, exclude_comp_chs=False):
-    """Returns compensation matrix eg. for CTF system.
+    """Return compensation matrix eg. for CTF system.
 
     Create a compensation matrix to bring the data from one compensation
     state to another.
diff --git a/mne/io/constants.py b/mne/io/constants.py
index e72eb52..3147314 100644
--- a/mne/io/constants.py
+++ b/mne/io/constants.py
@@ -5,18 +5,17 @@
 
 
 class Bunch(dict):
-    """ Container object for datasets: dictionnary-like object that
-        exposes its keys as attributes.
-    """
+    """Dictionnary-like object thatexposes its keys as attributes."""
 
-    def __init__(self, **kwargs):
+    def __init__(self, **kwargs):  # noqa: D102
         dict.__init__(self, kwargs)
         self.__dict__ = self
 
 
 class BunchConst(Bunch):
-    """Class to prevent us from re-defining constants (DRY)"""
-    def __setattr__(self, attr, val):
+    """Class to prevent us from re-defining constants (DRY)."""
+
+    def __setattr__(self, attr, val):  # noqa: D105
         if attr != '__dict__' and hasattr(self, attr):
             raise AttributeError('Attribute "%s" already set' % attr)
         super(BunchConst, self).__setattr__(attr, val)
@@ -42,9 +41,9 @@ FIFF.FIFFB_EVOKED             = 104
 FIFF.FIFFB_ASPECT             = 105
 FIFF.FIFFB_SUBJECT            = 106
 FIFF.FIFFB_ISOTRAK            = 107
-FIFF.FIFFB_HPI_MEAS           = 108
-FIFF.FIFFB_HPI_RESULT         = 109
-FIFF.FIFFB_HPI_COIL           = 110
+FIFF.FIFFB_HPI_MEAS           = 108  # HPI measurement
+FIFF.FIFFB_HPI_RESULT         = 109  # Result of a HPI fitting procedure
+FIFF.FIFFB_HPI_COIL           = 110  # Data acquired from one HPI coil
 FIFF.FIFFB_PROJECT            = 111
 FIFF.FIFFB_CONTINUOUS_DATA    = 112
 FIFF.FIFFB_VOID               = 114
@@ -121,7 +120,7 @@ FIFF.FIFF_REF_EVENT    = 211
 FIFF.FIFF_EXPERIMENTER = 212
 FIFF.FIFF_DIG_POINT   = 213
 FIFF.FIFF_CH_POS      = 214
-FIFF.FIFF_HPI_SLOPES  = 215
+FIFF.FIFF_HPI_SLOPES  = 215           # HPI data
 FIFF.FIFF_HPI_NCOIL   = 216
 FIFF.FIFF_REQ_EVENT   = 217
 FIFF.FIFF_REQ_LIMIT   = 218
@@ -132,7 +131,7 @@ FIFF.FIFF_COORD_TRANS = 222
 FIFF.FIFF_HIGHPASS    = 223
 FIFF.FIFF_CH_CALS        = 22     # This will not occur in new files
 FIFF.FIFF_HPI_BAD_CHS    = 225    # List of channels considered to be bad in hpi
-FIFF.FIFF_HPI_CORR_COEFF = 226    # Hpi curve fit correlations
+FIFF.FIFF_HPI_CORR_COEFF = 226    # HPI curve fit correlations
 FIFF.FIFF_EVENT_COMMENT  = 227    # Comment about the events used in averaging
 FIFF.FIFF_NO_SAMPLES     = 228    # Number of samples in an epoch
 FIFF.FIFF_FIRST_TIME     = 229    # Time scale minimum
@@ -294,6 +293,9 @@ FIFF.FIFFV_BEM_SURF_ID_BRAIN      = 1
 FIFF.FIFFV_BEM_SURF_ID_SKULL      = 3
 FIFF.FIFFV_BEM_SURF_ID_HEAD       = 4
 
+FIFF.FIFF_SPHERE_ORIGIN          = 3001
+FIFF.FIFF_SPHERE_RADIUS          = 3002
+
 FIFF.FIFF_BEM_SURF_ID           = 3101  # int    surface number
 FIFF.FIFF_BEM_SURF_NAME         = 3102  # string surface name
 FIFF.FIFF_BEM_SURF_NNODE        = 3103  # int    number of nodes on a surface
@@ -796,15 +798,16 @@ FIFF.FIFFV_COIL_VV_MAG_T3          = 3024  # Vectorview SQ20950N magnetometer
 
 FIFF.FIFFV_COIL_MAGNES_MAG         = 4001  # Magnes WH magnetometer
 FIFF.FIFFV_COIL_MAGNES_GRAD        = 4002  # Magnes WH gradiometer
-FIFF.FIFFV_COIL_MAGNES_R_MAG       = 4003  # Magnes WH reference magnetometer
-FIFF.FIFFV_COIL_MAGNES_R_GRAD_DIA  = 4004  # Magnes WH reference diagonal gradioometer
-FIFF.FIFFV_COIL_MAGNES_R_GRAD_OFF  = 4005  # Magnes WH reference off-diagonal gradiometer
 #
 # Magnes reference sensors
 #
 FIFF.FIFFV_COIL_MAGNES_REF_MAG          = 4003
 FIFF.FIFFV_COIL_MAGNES_REF_GRAD         = 4004
 FIFF.FIFFV_COIL_MAGNES_OFFDIAG_REF_GRAD = 4005
+FIFF.FIFFV_COIL_MAGNES_R_MAG = FIFF.FIFFV_COIL_MAGNES_REF_MAG
+FIFF.FIFFV_COIL_MAGNES_R_GRAD = FIFF.FIFFV_COIL_MAGNES_REF_GRAD
+FIFF.FIFFV_COIL_MAGNES_R_GRAD_OFF = FIFF.FIFFV_COIL_MAGNES_OFFDIAG_REF_GRAD
+
 #
 # CTF coil and channel types
 #
@@ -820,9 +823,24 @@ FIFF.FIFFV_COIL_KIT_REF_MAG      = 6002
 #
 # BabySQUID sensors
 #
-FIFF.FIFFV_COIL_BABY_GRAD               = 7001
-FIFF.FIFFV_COIL_BABY_MAG                = 7002
-FIFF.FIFFV_COIL_BABY_REF_MAG            = 7003
+FIFF.FIFFV_COIL_BABY_GRAD          = 7001
+FIFF.FIFFV_COIL_BABY_MAG           = 7002
+FIFF.FIFFV_COIL_BABY_REF_MAG       = 7003
+FIFF.FIFFV_COIL_BABY_REF_MAG2      = 7004
+#
+# Artemis123 sensors
+#
+FIFF.FIFFV_COIL_ARTEMIS123_GRAD         = 7501
+FIFF.FIFFV_COIL_ARTEMIS123_REF_MAG      = 7502
+FIFF.FIFFV_COIL_ARTEMIS123_REF_GRAD     = 7503
+#
+# KRISS sensors
+#
+FIFF.FIFFV_COIL_KRISS_GRAD         = 8001
+#
+# Sample TMS sensors
+#
+FIFF.FIFFV_COIL_SAMPLE_TMS_PLANAR  = 9001
 
 # MNE RealTime
 FIFF.FIFF_MNE_RT_COMMAND           = 3700  # realtime command
diff --git a/mne/io/ctf/__init__.py b/mne/io/ctf/__init__.py
index 8250246..1945e13 100644
--- a/mne/io/ctf/__init__.py
+++ b/mne/io/ctf/__init__.py
@@ -1,4 +1,4 @@
-"""CTF module for conversion to FIF"""
+"""CTF module for conversion to FIF."""
 
 # Author: Eric Larson <larson.eric.d at gmail.com>
 #
diff --git a/mne/io/ctf/constants.py b/mne/io/ctf/constants.py
index 9642d78..8386dce 100644
--- a/mne/io/ctf/constants.py
+++ b/mne/io/ctf/constants.py
@@ -1,4 +1,4 @@
-"""CTF constants"""
+"""CTF constants."""
 
 # Author: Eric Larson <larson.eric.d at gmail.com>
 #
diff --git a/mne/io/ctf/ctf.py b/mne/io/ctf/ctf.py
index fd7b2fd..c5b841b 100644
--- a/mne/io/ctf/ctf.py
+++ b/mne/io/ctf/ctf.py
@@ -1,5 +1,4 @@
-"""Conversion tool from CTF to FIF
-"""
+"""Conversion tool from CTF to FIF."""
 
 # Author: Eric Larson <larson.eric.d<gmail.com>
 #
@@ -13,7 +12,7 @@ import numpy as np
 from ...utils import verbose, logger
 from ...externals.six import string_types
 
-from ..base import _BaseRaw
+from ..base import BaseRaw
 from ..utils import _mult_cal_one, _blk_read_lims
 
 from .res4 import _read_res4, _make_ctf_name
@@ -26,12 +25,12 @@ from .constants import CTF
 
 def read_raw_ctf(directory, system_clock='truncate', preload=False,
                  verbose=None):
-    """Raw object from CTF directory
+    """Raw object from CTF directory.
 
     Parameters
     ----------
     directory : str
-        Path to the KIT data (ending in ``'.ds'``).
+        Path to the CTF data (ending in ``'.ds'``).
     system_clock : str
         How to treat the system clock. Use "truncate" (default) to truncate
         the data file when the system clock drops to zero, and use "ignore"
@@ -44,7 +43,8 @@ def read_raw_ctf(directory, system_clock='truncate', preload=False,
         file name of a memory-mapped file which is used to store the data
         on the hard drive (slower, requires less memory).
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -62,13 +62,13 @@ def read_raw_ctf(directory, system_clock='truncate', preload=False,
     return RawCTF(directory, system_clock, preload=preload, verbose=verbose)
 
 
-class RawCTF(_BaseRaw):
-    """Raw object from CTF directory
+class RawCTF(BaseRaw):
+    """Raw object from CTF directory.
 
     Parameters
     ----------
     directory : str
-        Path to the KIT data (ending in ``'.ds'``).
+        Path to the CTF data (ending in ``'.ds'``).
     system_clock : str
         How to treat the system clock. Use "truncate" (default) to truncate
         the data file when the system clock drops to zero, and use "ignore"
@@ -81,15 +81,17 @@ class RawCTF(_BaseRaw):
         file name of a memory-mapped file which is used to store the data
         on the hard drive (slower, requires less memory).
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     See Also
     --------
     mne.io.Raw : Documentation of attribute and methods.
     """
+
     @verbose
     def __init__(self, directory, system_clock='truncate', preload=False,
-                 verbose=None):
+                 verbose=None):  # noqa: D102
         # adapted from mne_ctf2fiff.c
         if not isinstance(directory, string_types) or \
                 not directory.endswith('.ds'):
@@ -131,17 +133,18 @@ class RawCTF(_BaseRaw):
             if len(fnames) == 0:
                 info['buffer_size_sec'] = \
                     sample_info['block_size'] / info['sfreq']
-                info['filename'] = directory
             fnames.append(meg4_name)
             last_samps.append(sample_info['n_samp'] - 1)
             raw_extras.append(sample_info)
+            first_samps = [0] * len(last_samps)
         super(RawCTF, self).__init__(
-            info, preload, last_samps=last_samps, filenames=fnames,
+            info, preload, first_samps=first_samps,
+            last_samps=last_samps, filenames=fnames,
             raw_extras=raw_extras, orig_format='int', verbose=verbose)
 
     @verbose
     def _read_segment_file(self, data, idx, fi, start, stop, cals, mult):
-        """Read a chunk of raw data"""
+        """Read a chunk of raw data."""
         si = self._raw_extras[fi]
         offset = 0
         trial_start_idx, r_lims, d_lims = _blk_read_lims(start, stop,
@@ -164,7 +167,7 @@ class RawCTF(_BaseRaw):
 
 
 def _get_sample_info(fname, res4, system_clock):
-    """Helper to determine the number of valid samples"""
+    """Determine the number of valid samples."""
     logger.info('Finding samples for %s: ' % (fname,))
     if CTF.SYSTEM_CLOCK_CH in res4['ch_names']:
         clock_ch = res4['ch_names'].index(CTF.SYSTEM_CLOCK_CH)
@@ -214,11 +217,10 @@ def _get_sample_info(fname, res4, system_clock):
     else:
         n_trial = n_samp // res4['nsamp']
         n_omit = n_samp_tot - n_samp
-        n_samp = n_trial * res4['nsamp']
         logger.info('    %d x %d = %d samples from %d chs'
                     % (n_trial, res4['nsamp'], n_samp, res4['nchan']))
         if n_omit != 0:
             logger.info('    %d samples omitted at the end' % n_omit)
+
     return dict(n_samp=n_samp, n_samp_tot=n_samp_tot, block_size=res4['nsamp'],
-                n_trial=n_trial, res4_nsamp=res4['nsamp'],
-                n_chan=res4['nchan'])
+                res4_nsamp=res4['nsamp'], n_chan=res4['nchan'])
diff --git a/mne/io/ctf/eeg.py b/mne/io/ctf/eeg.py
index e090fdb..23e925f 100644
--- a/mne/io/ctf/eeg.py
+++ b/mne/io/ctf/eeg.py
@@ -1,5 +1,4 @@
-"""Read .eeg files
-"""
+"""Read .eeg files."""
 
 # Author: Eric Larson <larson.eric.d<gmail.com>
 #
@@ -21,7 +20,7 @@ _cardinal_dict = dict(nasion=FIFF.FIFFV_POINT_NASION,
 
 
 def _read_eeg(directory):
-    """Read the .eeg file"""
+    """Read the .eeg file."""
     # Missing file is ok
     fname = _make_ctf_name(directory, 'eeg', raise_error=False)
     if fname is None:
@@ -55,8 +54,7 @@ def _read_eeg(directory):
 
 
 def _read_pos(directory, transformations):
-    """Read the .pos file and return eeg positions as digitizer extra points.
-    """
+    """Read the .pos file and return eeg positions as dig extra points."""
     fname = [join(directory, f) for f in listdir(directory) if
              f.endswith('.pos')]
     if len(fname) < 1:
diff --git a/mne/io/ctf/hc.py b/mne/io/ctf/hc.py
index ddb4b19..bdbb04f 100644
--- a/mne/io/ctf/hc.py
+++ b/mne/io/ctf/hc.py
@@ -1,5 +1,4 @@
-"""Read .hc files
-"""
+"""Read .hc files."""
 
 # Author: Eric Larson <larson.eric.d<gmail.com>
 #
@@ -21,7 +20,7 @@ _coord_dict = {'relative to dewar': FIFF.FIFFV_MNE_COORD_CTF_DEVICE,
 
 
 def _read_one_coil_point(fid):
-    """Read coil coordinate information from the hc file"""
+    """Read coil coordinate information from the hc file."""
     # Descriptor
     one = '#'
     while len(one) > 0 and one[0] == '#':
@@ -64,7 +63,7 @@ def _read_one_coil_point(fid):
 
 
 def _read_hc(directory):
-    """Read the hc file to get the HPI info and to prepare for coord transs"""
+    """Read the hc file to get the HPI info and to prepare for coord trans."""
     fname = _make_ctf_name(directory, 'hc', raise_error=False)
     if fname is None:
         logger.info('    hc data not present')
diff --git a/mne/io/ctf/info.py b/mne/io/ctf/info.py
index da32be7..9dc5756 100644
--- a/mne/io/ctf/info.py
+++ b/mne/io/ctf/info.py
@@ -1,5 +1,4 @@
-"""Populate measurement info
-"""
+"""Populate measurement info."""
 
 # Author: Eric Larson <larson.eric.d<gmail.com>
 #
@@ -22,8 +21,13 @@ from ..constants import FIFF
 from .constants import CTF
 
 
+_ctf_to_fiff = {CTF.CTFV_COIL_LPA: FIFF.FIFFV_POINT_LPA,
+                CTF.CTFV_COIL_RPA: FIFF.FIFFV_POINT_RPA,
+                CTF.CTFV_COIL_NAS: FIFF.FIFFV_POINT_NASION}
+
+
 def _pick_isotrak_and_hpi_coils(res4, coils, t):
-    """Pick the HPI coil locations given in device coordinates"""
+    """Pick the HPI coil locations given in device coordinates."""
     if coils is None:
         return list(), list()
     dig = list()
@@ -32,11 +36,18 @@ def _pick_isotrak_and_hpi_coils(res4, coils, t):
     n_coil_head = 0
     for p in coils:
         if p['valid']:
+            if p['kind'] in [CTF.CTFV_COIL_LPA, CTF.CTFV_COIL_RPA,
+                             CTF.CTFV_COIL_NAS]:
+                kind = FIFF.FIFFV_POINT_CARDINAL
+                ident = _ctf_to_fiff[p['kind']]
+            else:  # CTF.CTFV_COIL_SPARE
+                kind = FIFF.FIFFV_POINT_HPI
+                ident = p['kind']
             if p['coord_frame'] == FIFF.FIFFV_MNE_COORD_CTF_DEVICE:
                 if t is None or t['t_ctf_dev_dev'] is None:
                     raise RuntimeError('No coordinate transformation '
                                        'available for HPI coil locations')
-                d = dict(kind=FIFF.FIFFV_POINT_HPI, ident=p['kind'],
+                d = dict(kind=kind, ident=ident,
                          r=apply_trans(t['t_ctf_dev_dev'], p['r']),
                          coord_frame=FIFF.FIFFV_COORD_UNKNOWN)
                 hpi_result['dig_points'].append(d)
@@ -45,7 +56,7 @@ def _pick_isotrak_and_hpi_coils(res4, coils, t):
                 if t is None or t['t_ctf_head_head'] is None:
                     raise RuntimeError('No coordinate transformation '
                                        'available for (virtual) Polhemus data')
-                d = dict(kind=FIFF.FIFFV_POINT_HPI, ident=p['kind'],
+                d = dict(kind=kind, ident=ident,
                          r=apply_trans(t['t_ctf_head_head'], p['r']),
                          coord_frame=FIFF.FIFFV_COORD_HEAD)
                 dig.append(d)
@@ -59,7 +70,7 @@ def _pick_isotrak_and_hpi_coils(res4, coils, t):
 
 
 def _convert_time(date_str, time_str):
-    """Convert date and time strings to float time"""
+    """Convert date and time strings to float time."""
     for fmt in ("%d/%m/%Y", "%d-%b-%Y", "%a, %b %d, %Y"):
         try:
             date = strptime(date_str, fmt)
@@ -95,7 +106,7 @@ def _convert_time(date_str, time_str):
 
 
 def _get_plane_vectors(ez):
-    """Get two orthogonal vectors orthogonal to ez (ez will be modified)"""
+    """Get two orthogonal vectors orthogonal to ez (ez will be modified)."""
     assert ez.shape == (3,)
     ez_len = np.sqrt(np.sum(ez * ez))
     if ez_len == 0:
@@ -116,7 +127,7 @@ def _get_plane_vectors(ez):
 
 
 def _at_origin(x):
-    """Determine if a vector is at the origin"""
+    """Determine if a vector is at the origin."""
     return (np.sum(x * x) < 1e-8)
 
 
@@ -136,7 +147,7 @@ def _check_comp_ch(cch, kind, desired=None):
 
 
 def _convert_channel_info(res4, t, use_eeg_pos):
-    """Convert CTF channel information to fif format"""
+    """Convert CTF channel information to fif format."""
     nmeg = neeg = nstim = nmisc = nref = 0
     chs = list()
     this_comp = None
@@ -258,12 +269,12 @@ def _convert_channel_info(res4, t, use_eeg_pos):
 
 
 def _comp_sort_keys(c):
-    """This is for sorting the compensation data"""
+    """Sort the compensation data."""
     return (int(c['coeff_type']), int(c['scanno']))
 
 
 def _check_comp(comp):
-    """Check that conversion to named matrices is, indeed possible"""
+    """Check that conversion to named matrices is possible."""
     ref_sens = None
     kind = -1
     for k, c_k in enumerate(comp):
@@ -276,18 +287,23 @@ def _check_comp(comp):
 
 
 def _conv_comp(comp, first, last, chs):
-    """Add a new converted compensation data item"""
-    ccomp = dict(ctfkind=np.array([comp[first]['coeff_type']]),
-                 save_calibrated=False)
-    _add_kind(ccomp)
+    """Add a new converted compensation data item."""
+    ch_names = [c['ch_name'] for c in chs]
     n_col = comp[first]['ncoeff']
-    n_row = last - first + 1
     col_names = comp[first]['sensors'][:n_col]
     row_names = [comp[p]['sensor_name'] for p in range(first, last + 1)]
+    mask = np.in1d(col_names, ch_names)  # missing channels excluded
+    col_names = np.array(col_names)[mask]
+    n_col = len(col_names)
+    n_row = len(row_names)
+    ccomp = dict(ctfkind=np.array([comp[first]['coeff_type']]),
+                 save_calibrated=False)
+    _add_kind(ccomp)
+
     data = np.empty((n_row, n_col))
     for ii, coeffs in enumerate(comp[first:last + 1]):
         # Pick the elements to the matrix
-        data[ii, :] = coeffs['coeffs'][:]
+        data[ii, :] = coeffs['coeffs'][mask]
     ccomp['data'] = dict(row_names=row_names, col_names=col_names,
                          data=data, nrow=len(row_names), ncol=len(col_names))
     mk = ('proper_gain', 'qgain')
@@ -296,7 +312,7 @@ def _conv_comp(comp, first, last, chs):
 
 
 def _convert_comp_data(res4):
-    """Convert the compensation data into named matrices"""
+    """Convert the compensation data into named matrices."""
     if res4['ncomp'] == 0:
         return
     # Sort the coefficients in our favorite order
@@ -320,7 +336,7 @@ def _convert_comp_data(res4):
 
 
 def _pick_eeg_pos(c):
-    """Pick EEG positions"""
+    """Pick EEG positions."""
     eeg = dict(coord_frame=FIFF.FIFFV_COORD_HEAD, assign_to_chs=False,
                labels=list(), ids=list(), rr=list(), kinds=list(), np=0)
     for ch in c['chs']:
@@ -338,7 +354,7 @@ def _pick_eeg_pos(c):
 
 
 def _add_eeg_pos(eeg, t, c):
-    """Pick the (virtual) EEG position data"""
+    """Pick the (virtual) EEG position data."""
     if eeg is None:
         return
     if t is None or t['t_ctf_head_head'] is None:
@@ -397,7 +413,7 @@ _filt_map = {CTF.CTFV_FILTER_LOWPASS: 'lowpass',
 
 
 def _compose_meas_info(res4, coils, trans, eeg):
-    """Create meas info from CTF data"""
+    """Create meas info from CTF data."""
     info = _empty_info(res4['sfreq'])
 
     # Collect all the necessary data from the structures read
diff --git a/mne/io/ctf/res4.py b/mne/io/ctf/res4.py
index 2c675c6..f6b63b8 100644
--- a/mne/io/ctf/res4.py
+++ b/mne/io/ctf/res4.py
@@ -1,5 +1,4 @@
-"""Read .res4 files
-"""
+"""Read .res4 files."""
 
 # Author: Eric Larson <larson.eric.d<gmail.com>
 #
@@ -14,7 +13,7 @@ from .constants import CTF
 
 
 def _make_ctf_name(directory, extra, raise_error=True):
-    """Helper to make a CTF name"""
+    """Make a CTF name."""
     fname = op.join(directory, op.basename(directory)[:-3] + '.' + extra)
     if not op.isfile(fname):
         if raise_error:
@@ -25,34 +24,34 @@ def _make_ctf_name(directory, extra, raise_error=True):
 
 
 def _read_double(fid, n=1):
-    """Read a double"""
+    """Read a double."""
     return np.fromfile(fid, '>f8', n)
 
 
 def _read_string(fid, n_bytes, decode=True):
-    """Read string"""
+    """Read string."""
     s0 = fid.read(n_bytes)
     s = s0.split(b'\x00')[0]
     return s.decode('utf-8') if decode else s
 
 
 def _read_ustring(fid, n_bytes):
-    """Read unsigned character string"""
+    """Read unsigned character string."""
     return np.fromfile(fid, '>B', n_bytes)
 
 
 def _read_int2(fid):
-    """Read int from short"""
+    """Read int from short."""
     return np.fromfile(fid, '>i2', 1)[0]
 
 
 def _read_int(fid):
-    """Read a 32-bit integer"""
+    """Read a 32-bit integer."""
     return np.fromfile(fid, '>i4', 1)[0]
 
 
 def _move_to_next(fid, byte=8):
-    """Move to next byte boundary"""
+    """Move to next byte boundary."""
     now = fid.tell()
     if now % byte != 0:
         now = now - (now % byte) + byte
@@ -60,7 +59,7 @@ def _move_to_next(fid, byte=8):
 
 
 def _read_filter(fid):
-    """Read filter information"""
+    """Read filter information."""
     f = dict()
     f['freq'] = _read_double(fid)[0]
     f['class'] = _read_int(fid)
@@ -71,7 +70,7 @@ def _read_filter(fid):
 
 
 def _read_channel(fid):
-    """Read channel information"""
+    """Read channel information."""
     ch = dict()
     ch['sensor_type_index'] = _read_int2(fid)
     ch['original_run_no'] = _read_int2(fid)
@@ -105,7 +104,7 @@ def _read_channel(fid):
 
 
 def _read_comp_coeff(fid, d):
-    """Read compensation coefficients"""
+    """Read compensation coefficients."""
     # Read the coefficients and initialize
     d['ncomp'] = _read_int2(fid)
     d['comp'] = list()
@@ -129,7 +128,7 @@ def _read_comp_coeff(fid, d):
 
 
 def _read_res4(dsdir):
-    """Read the magical res4 file"""
+    """Read the magical res4 file."""
     # adapted from read_res4.c
     name = _make_ctf_name(dsdir, 'res4')
     res = dict()
diff --git a/mne/io/ctf/tests/test_ctf.py b/mne/io/ctf/tests/test_ctf.py
index 7460dfe..76bbdb6 100644
--- a/mne/io/ctf/tests/test_ctf.py
+++ b/mne/io/ctf/tests/test_ctf.py
@@ -10,14 +10,16 @@ import warnings
 import numpy as np
 from nose.tools import assert_raises, assert_true, assert_false
 from numpy.testing import assert_allclose, assert_array_equal, assert_equal
+import pytest
 
 from mne import pick_types
-from mne.tests.common import assert_dig_allclose
+# from mne.tests.common import assert_dig_allclose
 from mne.transforms import apply_trans
-from mne.io import Raw, read_raw_ctf
+from mne.io import read_raw_fif, read_raw_ctf
 from mne.io.tests.test_raw import _test_raw_reader
-from mne.utils import _TempDir, run_tests_if_main, slow_test
+from mne.utils import _TempDir, run_tests_if_main
 from mne.datasets import testing, spm_face
+from mne.io.constants import FIFF
 
 ctf_dir = op.join(testing.data_path(download=False), 'CTF')
 ctf_fname_continuous = 'testdata_ctf.ds'
@@ -43,7 +45,7 @@ single_trials = (
 ctf_fnames = tuple(sorted(block_sizes.keys()))
 
 
- at slow_test
+ at pytest.mark.slowtest
 @testing.requires_testing_data
 def test_read_ctf():
     """Test CTF reader"""
@@ -96,7 +98,7 @@ def test_read_ctf():
     # All our files
     use_fnames = [op.join(ctf_dir, c) for c in ctf_fnames]
     for fname in use_fnames:
-        raw_c = Raw(fname + '_raw.fif', add_eeg_ref=False, preload=True)
+        raw_c = read_raw_fif(fname + '_raw.fif', preload=True)
         with warnings.catch_warnings(record=True) as w:  # reclassified ch
             raw = read_raw_ctf(fname)
         assert_true(all('MISC channel' in str(ww.message) for ww in w))
@@ -163,11 +165,14 @@ def test_read_ctf():
                                 err_msg='raw.info["chs"][%d][%s]' % (ii, key))
         if fname.endswith('catch-alp-good-f.ds'):  # omit points from .pos file
             raw.info['dig'] = raw.info['dig'][:-10]
-        assert_dig_allclose(raw.info, raw_c.info)
+
+        # XXX: Next test would fail because c-tools assign the fiducials from
+        # CTF data as HPI. Should eventually clarify/unify with Matti.
+        # assert_dig_allclose(raw.info, raw_c.info)
 
         # check data match
         raw_c.save(out_fname, overwrite=True, buffer_size_sec=1.)
-        raw_read = Raw(out_fname, add_eeg_ref=False)
+        raw_read = read_raw_fif(out_fname)
 
         # so let's check tricky cases based on sample boundaries
         rng = np.random.RandomState(0)
@@ -190,8 +195,6 @@ def test_read_ctf():
             raw = read_raw_ctf(fname, preload=True)
         assert_true(all('MISC channel' in str(ww.message) for ww in w))
         assert_allclose(raw[:][0], raw_c[:][0])
-    raw.plot(show=False)  # Test plotting with ref_meg channels.
-    assert_raises(ValueError, raw.plot, order='selection')
     assert_raises(TypeError, read_raw_ctf, 1)
     assert_raises(ValueError, read_raw_ctf, ctf_fname_continuous + 'foo.ds')
     # test ignoring of system clock
@@ -211,4 +214,15 @@ def test_read_spm_ctf():
     assert_equal(extras['n_samp'], raw.n_times)
     assert_false(extras['n_samp'] == extras['n_samp_tot'])
 
+    # Test that LPA, nasion and RPA are correct.
+    coord_frames = np.array([d['coord_frame'] for d in raw.info['dig']])
+    assert_true(np.all(coord_frames == FIFF.FIFFV_COORD_HEAD))
+    cardinals = {d['ident']: d['r'] for d in raw.info['dig']}
+    assert_true(cardinals[1][0] < cardinals[2][0] < cardinals[3][0])  # x coord
+    assert_true(cardinals[1][1] < cardinals[2][1])  # y coord
+    assert_true(cardinals[3][1] < cardinals[2][1])  # y coord
+    for key in cardinals.keys():
+        assert_allclose(cardinals[key][2], 0, atol=1e-6)  # z coord
+
+
 run_tests_if_main()
diff --git a/mne/io/ctf/trans.py b/mne/io/ctf/trans.py
index ed6dbf5..bd59a31 100644
--- a/mne/io/ctf/trans.py
+++ b/mne/io/ctf/trans.py
@@ -1,5 +1,4 @@
-"""Create coordinate transforms
-"""
+"""Create coordinate transforms."""
 
 # Author: Eric Larson <larson.eric.d<gmail.com>
 #
@@ -8,14 +7,15 @@
 import numpy as np
 from scipy import linalg
 
-from ...transforms import combine_transforms, invert_transform
+from ...transforms import combine_transforms, invert_transform, Transform
 from ...utils import logger
 from ..constants import FIFF
 from .constants import CTF
 
 
 def _make_transform_card(fro, to, r_lpa, r_nasion, r_rpa):
-    """Helper to make a transform from cardinal landmarks"""
+    """Make a transform from cardinal landmarks."""
+    # XXX de-duplicate this with code from Montage somewhere?
     diff_1 = r_nasion - r_lpa
     ex = r_rpa - r_lpa
     alpha = np.dot(diff_1, ex) / np.dot(ex, ex)
@@ -28,11 +28,11 @@ def _make_transform_card(fro, to, r_lpa, r_nasion, r_rpa):
     ey /= np.sqrt(np.sum(ey * ey))
     trans[:3, 1] = ey
     trans[:3, 2] = np.cross(ex, ey)  # ez
-    return {'from': fro, 'to': to, 'trans': trans}
+    return Transform(fro, to, trans)
 
 
 def _quaternion_align(from_frame, to_frame, from_pts, to_pts):
-    """Perform an alignment using the unit quaternions (modifies points)"""
+    """Perform an alignment using the unit quaternions (modifies points)."""
     assert from_pts.shape[1] == to_pts.shape[1] == 3
 
     # Calculate the centroids and subtract
@@ -88,11 +88,11 @@ def _quaternion_align(from_frame, to_frame, from_pts, to_pts):
         if diff > 1e-4:
             raise RuntimeError('Something is wrong: quaternion matching did '
                                'not work (see above)')
-    return {'from': from_frame, 'to': to_frame, 'trans': trans}
+    return Transform(from_frame, to_frame, trans)
 
 
 def _make_ctf_coord_trans_set(res4, coils):
-    """Figure out the necessary coordinate transforms"""
+    """Figure out the necessary coordinate transforms."""
     # CTF head > Neuromag head
     lpa = rpa = nas = T1 = T2 = T3 = T5 = None
     if coils is not None:
@@ -125,8 +125,8 @@ def _make_ctf_coord_trans_set(res4, coils):
     R[0, 1] = -val
     R[1, 0] = val
     R[1, 1] = val
-    T4 = {'from': FIFF.FIFFV_MNE_COORD_CTF_DEVICE,
-          'to': FIFF.FIFFV_COORD_DEVICE, 'trans': R}
+    T4 = Transform(FIFF.FIFFV_MNE_COORD_CTF_DEVICE,
+                   FIFF.FIFFV_COORD_DEVICE, R)
 
     # CTF device -> CTF head
     # We need to make the implicit transform explicit!
diff --git a/mne/io/ctf_comp.py b/mne/io/ctf_comp.py
index 1775236..fb6fd95 100644
--- a/mne/io/ctf_comp.py
+++ b/mne/io/ctf_comp.py
@@ -18,7 +18,7 @@ from ..utils import logger, verbose
 
 
 def _add_kind(one):
-    """Convert CTF kind to MNE kind"""
+    """Convert CTF kind to MNE kind."""
     if one['ctfkind'] == int('47314252', 16):
         one['kind'] = 1
     elif one['ctfkind'] == int('47324252', 16):
@@ -31,7 +31,7 @@ def _add_kind(one):
 
 def _calibrate_comp(comp, chs, row_names, col_names,
                     mult_keys=('range', 'cal'), flip=False):
-    """Helper to get row and column cals"""
+    """Get row and column cals."""
     ch_names = [c['ch_name'] for c in chs]
     row_cals = np.zeros(len(row_names))
     col_cals = np.zeros(len(col_names))
@@ -55,7 +55,7 @@ def _calibrate_comp(comp, chs, row_names, col_names,
 
 @verbose
 def read_ctf_comp(fid, node, chs, verbose=None):
-    """Read the CTF software compensation data from the given node
+    """Read the CTF software compensation data from the given node.
 
     Parameters
     ----------
@@ -67,7 +67,8 @@ def read_ctf_comp(fid, node, chs, verbose=None):
         The list of channels from info['chs'] to match with
         compensators that are read.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -124,7 +125,7 @@ def read_ctf_comp(fid, node, chs, verbose=None):
 # Writing
 
 def write_ctf_comp(fid, comps):
-    """Write the CTF compensation data into a fif file
+    """Write the CTF compensation data into a fif file.
 
     Parameters
     ----------
diff --git a/mne/io/diff.py b/mne/io/diff.py
index 9e1fd1c..2e16794 100644
--- a/mne/io/diff.py
+++ b/mne/io/diff.py
@@ -9,8 +9,9 @@ from ..utils import logger, verbose
 
 @verbose
 def is_equal(first, second, verbose=None):
-    """ Says if 2 python structures are the same. Designed to
-    handle dict, list, np.ndarray etc.
+    """Check if 2 python structures are the same.
+
+    Designed to handle dict, list, np.ndarray etc.
     """
     all_equal = True
     # Check all keys in first dict
diff --git a/mne/io/edf/__init__.py b/mne/io/edf/__init__.py
index f712d3d..d61ad85 100644
--- a/mne/io/edf/__init__.py
+++ b/mne/io/edf/__init__.py
@@ -1,7 +1,7 @@
-"""EDF+,BDF module for conversion to FIF"""
+"""EDF+,BDF module for conversion to FIF."""
 
 # Author: Teon Brooks <teon.brooks at gmail.com>
 #
 # License: BSD (3-clause)
 
-from .edf import read_raw_edf
+from .edf import read_raw_edf, find_edf_events
diff --git a/mne/io/edf/edf.py b/mne/io/edf/edf.py
index 67215dc..d932e38 100644
--- a/mne/io/edf/edf.py
+++ b/mne/io/edf/edf.py
@@ -1,9 +1,8 @@
-"""Conversion tool from EDF, EDF+, BDF to FIF
-
-"""
+"""Conversion tool from EDF, EDF+, BDF to FIF."""
 
 # Authors: Teon Brooks <teon.brooks at gmail.com>
 #          Martin Billinger <martin.billinger at tugraz.at>
+#          Nicolas Barascud <nicolas.barascud at ens.fr>
 #
 # License: BSD (3-clause)
 
@@ -16,15 +15,58 @@ import numpy as np
 
 from ...utils import verbose, logger, warn
 from ..utils import _blk_read_lims
-from ..base import _BaseRaw, _check_update_montage
+from ..base import BaseRaw, _check_update_montage
 from ..meas_info import _empty_info
 from ..constants import FIFF
 from ...filter import resample
 from ...externals.six.moves import zip
+from ...utils import copy_function_doc_to_method_doc
+
+
+def find_edf_events(raw):
+    """Get original EDF events as read from the header.
+
+    For GDF, the values are returned in form
+    [n_events, pos, typ, chn, dur]
+    where:
 
+    ========  ===================================  =======
+    name      description                          type
+    ========  ===================================  =======
+    n_events  The number of all events             integer
+    pos       Beginnning of the events in samples  array
+    typ       The event identifiers                array
+    chn       The associated channels (0 for all)  array
+    dur       The durations of the events          array
+    ========  ===================================  =======
 
-class RawEDF(_BaseRaw):
-    """Raw object from EDF, EDF+, BDF file
+    For EDF+, the values are returned in form
+    n_events * [onset, dur, desc]
+    where:
+
+    ========  ===================================  =======
+    name      description                          type
+    ========  ===================================  =======
+    onset     Onset of the event in seconds        float
+    dur       Duration of the event in seconds     float
+    desc      Description of the event             str
+    ========  ===================================  =======
+
+    Parameters
+    ----------
+    raw : Instance of RawEDF
+        The raw object for finding the events.
+
+    Returns
+    -------
+    events : ndarray
+        The events as they are in the file header.
+    """
+    return raw.find_edf_events()
+
+
+class RawEDF(BaseRaw):
+    """Raw object from EDF, EDF+, BDF file.
 
     Parameters
     ----------
@@ -42,10 +84,12 @@ class RawEDF(_BaseRaw):
         Names of channels or list of indices that should be designated
         MISC channels. Values should correspond to the electrodes in the
         edf file. Default is None.
-    stim_channel : str | int | None
-        The channel name or channel index (starting at 0).
-        -1 corresponds to the last channel (default).
-        If None, there will be no stim channel added.
+    stim_channel : str | int | 'auto' | None
+        The channel name or channel index (starting at 0). -1 corresponds to
+        the last channel. If None, there will be no stim channel added. If
+        'auto' (default), the stim channel will be added as the last channel if
+        the header contains ``'EDF Annotations'`` or GDF events (otherwise stim
+        channel will not be added).
     annot : str | None
         Path to annotation file.
         If None, no derived stim channel will be added (for files requiring
@@ -53,6 +97,9 @@ class RawEDF(_BaseRaw):
     annotmap : str | None
         Path to annotation map file containing mapping from label to trigger.
         Must be specified if annot is not None.
+    exclude : list of str
+        Channel names to exclude. This can help when reading data with
+        different sampling rates to avoid unnecessary resampling.
     preload : bool or str (default False)
         Preload data into memory for data manipulation and faster indexing.
         If True, the data will be preloaded into memory (fast, requires
@@ -60,22 +107,62 @@ class RawEDF(_BaseRaw):
         file name of a memory-mapped file which is used to store the data
         on the hard drive (slower, requires less memory).
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
+
+    Notes
+    -----
+    Biosemi devices trigger codes are encoded in 16-bit format, whereas system
+    codes (CMS in/out-of range, battery low, etc.) are coded in bits 16-23 of
+    the status channel (see http://www.biosemi.com/faq/trigger_signals.htm).
+    To retrieve correct event values (bits 1-16), one could do:
+
+        >>> events = mne.find_events(...)  # doctest:+SKIP
+        >>> events[:, 2] &= (2**16 - 1)  # doctest:+SKIP
+
+    The above operation can be carried out directly in :func:`mne.find_events`
+    using the ``mask`` and ``mask_type`` parameters
+    (see :func:`mne.find_events` for more details).
+
+    It is also possible to retrieve system codes, but no particular effort has
+    been made to decode these in MNE. In case it is necessary, for instance to
+    check the CMS bit, the following operation can be carried out:
+
+        >>> cms_bit = 20  # doctest:+SKIP
+        >>> cms_high = (events[:, 2] & (1 << cms_bit)) != 0  # doctest:+SKIP
+
+    It is worth noting that in some special cases, it may be necessary to
+    shift the event values in order to retrieve correct event triggers. This
+    depends on the triggering device used to perform the synchronization.
+    For instance, some GDF files need a 8 bits shift:
+
+        >>> events[:, 2] >>= 8  # doctest:+SKIP
+
+    In addition, for GDF files, the stimulus channel is constructed from the
+    events in the header. The id numbers of overlapping events are simply
+    combined through addition. To get the original events from the header,
+    use function :func:`mne.io.find_edf_events`.
 
     See Also
     --------
     mne.io.Raw : Documentation of attribute and methods.
     """
+
     @verbose
     def __init__(self, input_fname, montage, eog=None, misc=None,
-                 stim_channel=-1, annot=None, annotmap=None,
-                 preload=False, verbose=None):
+                 stim_channel=True, annot=None, annotmap=None, exclude=(),
+                 preload=False, verbose=None):  # noqa: D102
+        if stim_channel is True:
+            warn("stim_channel will default to 'auto' in version 0.16. "
+                 "Set stim_channel explicitly to avoid this warning.",
+                 DeprecationWarning)
+            stim_channel = 'auto' if input_fname[-4:] == '.gdf' else -1
+
         logger.info('Extracting edf Parameters from %s...' % input_fname)
         input_fname = os.path.abspath(input_fname)
-        info, edf_info = _get_edf_info(input_fname, stim_channel,
-                                       annot, annotmap,
-                                       eog, misc, preload)
-        logger.info('Creating Raw.info structure...')
+        info, edf_info = _get_info(input_fname, stim_channel, annot,
+                                   annotmap, eog, misc, exclude, preload)
+        logger.info('Created Raw.info structure...')
         _check_update_montage(info, montage)
 
         if bool(annot) != bool(annotmap):
@@ -86,111 +173,142 @@ class RawEDF(_BaseRaw):
         last_samps = [edf_info['nsamples'] - 1]
         super(RawEDF, self).__init__(
             info, preload, filenames=[input_fname], raw_extras=[edf_info],
-            last_samps=last_samps, orig_format='int',
-            verbose=verbose)
+            last_samps=last_samps, orig_format='int', verbose=verbose)
 
         logger.info('Ready.')
 
     @verbose
     def _read_segment_file(self, data, idx, fi, start, stop, cals, mult):
-        """Read a chunk of raw data"""
+        """Read a chunk of raw data."""
         from scipy.interpolate import interp1d
         if mult is not None:
             # XXX "cals" here does not function the same way as in RawFIF,
             # and for efficiency we want to be able to combine mult and cals
             # so proj support will have to wait until this is resolved
             raise NotImplementedError('mult is not supported yet')
+        exclude = self._raw_extras[fi]['exclude']
         sel = np.arange(self.info['nchan'])[idx]
-
         n_samps = self._raw_extras[fi]['n_samps']
         buf_len = int(self._raw_extras[fi]['max_samp'])
         sfreq = self.info['sfreq']
-        n_chan = self.info['nchan']
-        data_size = self._raw_extras[fi]['data_size']
+        dtype = self._raw_extras[fi]['dtype_np']
+        dtype_byte = self._raw_extras[fi]['dtype_byte']
         data_offset = self._raw_extras[fi]['data_offset']
         stim_channel = self._raw_extras[fi]['stim_channel']
-        tal_channel = self._raw_extras[fi]['tal_channel']
+        tal_channels = self._raw_extras[fi]['tal_channel']
         annot = self._raw_extras[fi]['annot']
         annotmap = self._raw_extras[fi]['annotmap']
         subtype = self._raw_extras[fi]['subtype']
+        stim_data = self._raw_extras[fi].get('stim_data', None)  # for GDF
+
+        if np.size(dtype_byte) > 1:
+            if len(np.unique(dtype_byte)) > 1:
+                warn("Multiple data type not supported")
+            dtype = dtype[0]
+            dtype_byte = dtype_byte[0]
 
         # gain constructor
         physical_range = np.array([ch['range'] for ch in self.info['chs']])
         cal = np.array([ch['cal'] for ch in self.info['chs']])
-        gains = np.atleast_2d(self._raw_extras[fi]['units'] *
-                              (physical_range / cal))
+        cal = np.atleast_2d(physical_range / cal)  # physical / digital
+        gains = np.atleast_2d(self._raw_extras[fi]['units'])
 
         # physical dimension in uV
-        physical_min = np.atleast_2d(self._raw_extras[fi]['units'] *
-                                     self._raw_extras[fi]['physical_min'])
+        physical_min = self._raw_extras[fi]['physical_min']
         digital_min = self._raw_extras[fi]['digital_min']
 
-        offsets = np.atleast_2d(physical_min - (digital_min * gains)).T
-        if tal_channel is not None:
-            offsets[tal_channel] = 0
+        offsets = np.atleast_2d(physical_min - (digital_min * cal)).T
+        if tal_channels is not None:
+            for tal_channel in tal_channels:
+                offsets[tal_channel] = 0
+
+        # This is needed to rearrange the indices to correspond to correct
+        # chunks on the file if excluded channels exist:
+        selection = sel.copy()
+        idx_map = np.argsort(selection)
+        for ei in sorted(exclude):
+            for ii, si in enumerate(sorted(selection)):
+                if si >= ei:
+                    selection[idx_map[ii]] += 1
+            if tal_channels is not None:
+                tal_channels = [tc + 1 if tc >= ei else tc for tc in
+                                sorted(tal_channels)]
 
+        # We could read this one EDF block at a time, which would be this:
+        ch_offsets = np.cumsum(np.concatenate([[0], n_samps]))
         block_start_idx, r_lims, d_lims = _blk_read_lims(start, stop, buf_len)
-        read_size = len(r_lims) * buf_len
+        # But to speed it up, we really need to read multiple blocks at once,
+        # Otherwise we can end up with e.g. 18,181 chunks for a 20 MB file!
+        # Let's do ~10 MB chunks:
+        n_per = max(10 * 1024 * 1024 // (ch_offsets[-1] * dtype_byte), 1)
         with open(self._filenames[fi], 'rb', buffering=0) as fid:
-            # extract data
+
+            # Extract data
             start_offset = (data_offset +
-                            block_start_idx * buf_len * n_chan * data_size)
-            ch_offsets = np.cumsum(np.concatenate([[0], n_samps * data_size]))
-            this_data = np.empty((len(sel), buf_len))
-            for bi in range(len(r_lims)):
-                block_offset = bi * ch_offsets[-1]
-                d_sidx, d_eidx = d_lims[bi]
-                r_sidx, r_eidx = r_lims[bi]
-                n_buf_samp = r_eidx - r_sidx
-                for ii, ci in enumerate(sel):
-                    n_samp = n_samps[ci]
-                    # bdf data: 24bit data
-                    fid.seek(start_offset + block_offset + ch_offsets[ci], 0)
-                    if n_samp == buf_len:
-                        # use faster version with skips built in
-                        fid.seek(r_sidx * data_size, 1)
-                        ch_data = _read_ch(fid, subtype, n_buf_samp, data_size)
-                    else:
-                        # read in all the data and triage appropriately
-                        ch_data = _read_ch(fid, subtype, n_samp, data_size)
-                        if ci == tal_channel:
-                            # don't resample tal_channel,
-                            # pad with zeros instead.
-                            n_missing = int(buf_len - n_samp)
-                            ch_data = np.hstack([ch_data, [0] * n_missing])
-                            ch_data = ch_data[r_sidx:r_eidx]
+                            block_start_idx * ch_offsets[-1] * dtype_byte)
+            for ai in range(0, len(r_lims), n_per):
+                block_offset = ai * ch_offsets[-1] * dtype_byte
+                n_read = min(len(r_lims) - ai, n_per)
+                fid.seek(start_offset + block_offset, 0)
+                # Read and reshape to (n_chunks_read, ch0_ch1_ch2_ch3...)
+                many_chunk = _read_ch(fid, subtype, ch_offsets[-1] * n_read,
+                                      dtype_byte, dtype).reshape(n_read, -1)
+                for ii, ci in enumerate(selection):
+                    # This now has size (n_chunks_read, n_samp[ci])
+                    ch_data = many_chunk[:, ch_offsets[ci]:ch_offsets[ci + 1]]
+                    r_sidx = r_lims[ai][0]
+                    r_eidx = (buf_len * (n_read - 1) +
+                              r_lims[ai + n_read - 1][1])
+                    d_sidx = d_lims[ai][0]
+                    d_eidx = d_lims[ai + n_read - 1][1]
+                    if n_samps[ci] != buf_len:
+                        if tal_channels is not None and ci in tal_channels:
+                            # don't resample tal_channels, zero-pad instead.
+                            if n_samps[ci] < buf_len:
+                                z = np.zeros((len(ch_data),
+                                              buf_len - n_samps[ci]))
+                                ch_data = np.append(ch_data, z, -1)
+                            else:
+                                ch_data = ch_data[:, :buf_len]
                         elif ci == stim_channel:
-                            if annot and annotmap or tal_channel is not None:
-                                # don't bother with resampling the stim ch
-                                # because it gets overwritten later on.
-                                ch_data = np.zeros(n_buf_samp)
+                            if (annot and annotmap or stim_data is not None or
+                                    tal_channels is not None):
+                                # don't resample, it gets overwritten later
+                                ch_data = np.zeros((len(ch_data), buf_len))
                             else:
                                 # Stim channel will be interpolated
-                                oldrange = np.linspace(0, 1, n_samp + 1, True)
-                                newrange = np.linspace(0, 1, buf_len, False)
-                                newrange = newrange[r_sidx:r_eidx]
-                                ch_data = interp1d(
-                                    oldrange, np.append(ch_data, 0),
-                                    kind='zero')(newrange)
+                                old = np.linspace(0, 1, n_samps[ci] + 1, True)
+                                new = np.linspace(0, 1, buf_len, False)
+                                ch_data = np.append(
+                                    ch_data, np.zeros((len(ch_data), 1)), -1)
+                                ch_data = interp1d(old, ch_data,
+                                                   kind='zero', axis=-1)(new)
                         else:
-                            ch_data = resample(ch_data, buf_len, n_samp,
-                                               npad=0)[r_sidx:r_eidx]
-                    this_data[ii, :n_buf_samp] = ch_data
-                data[:, d_sidx:d_eidx] = this_data[:, :n_buf_samp]
-        data *= gains.T[sel]
-        data += offsets[sel]
+                            # XXX resampling each chunk isn't great,
+                            # it forces edge artifacts to appear at
+                            # each buffer boundary :(
+                            # it can also be very slow...
+                            ch_data = resample(ch_data, buf_len, n_samps[ci],
+                                               npad=0, axis=-1)
+                    assert ch_data.shape == (len(ch_data), buf_len)
+                    data[ii, d_sidx:d_eidx] = ch_data.ravel()[r_sidx:r_eidx]
+
+        data *= cal.T[sel]  # scale
+        data += offsets[sel]  # offset
+        data *= gains.T[sel]  # apply units gain last
 
         # only try to read the stim channel if it's not None and it's
         # actually one of the requested channels
+        read_size = len(r_lims) * buf_len
         if stim_channel is not None and (sel == stim_channel).sum() > 0:
             stim_channel_idx = np.where(sel == stim_channel)[0]
             if annot and annotmap:
                 evts = _read_annot(annot, annotmap, sfreq,
                                    self._last_samps[fi])
                 data[stim_channel_idx, :] = evts[start:stop + 1]
-            elif tal_channel is not None:
-                tal_channel_idx = np.where(sel == tal_channel)[0][0]
-                evts = _parse_tal_channel(data[tal_channel_idx])
+            elif tal_channels is not None:
+                tal_channel_idx = np.intersect1d(sel, tal_channels)
+                evts = _parse_tal_channel(np.atleast_2d(data[tal_channel_idx]))
                 self._raw_extras[fi]['events'] = evts
 
                 unique_annots = sorted(set([e[2] for e in evts]))
@@ -206,39 +324,45 @@ class RawEDF(_BaseRaw):
                     if any(stim[n_start:n_stop]):
                         warn('EDF+ with overlapping events'
                              ' are not fully supported')
-                    stim[n_start:n_stop] = evid
+                    stim[n_start:n_stop] += evid
                 data[stim_channel_idx, :] = stim[start:stop]
+            elif stim_data is not None:  # GDF events
+                data[stim_channel_idx, :] = stim_data[start:stop]
             else:
-                # Allows support for up to 17-bit trigger values (2 ** 17 - 1)
                 stim = np.bitwise_and(data[stim_channel_idx].astype(int),
-                                      131071)
+                                      2**17 - 1)
                 data[stim_channel_idx, :] = stim
 
+    @copy_function_doc_to_method_doc(find_edf_events)
+    def find_edf_events(self):
+        return self._raw_extras[0]['events']
+
 
-def _read_ch(fid, subtype, samp, data_size):
-    """Helper to read a number of samples for a single channel"""
+def _read_ch(fid, subtype, samp, dtype_byte, dtype=None):
+    """Read a number of samples for a single channel."""
+    # BDF
     if subtype in ('24BIT', 'bdf'):
-        ch_data = np.fromfile(fid, dtype=np.uint8,
-                              count=samp * data_size)
+        ch_data = np.fromfile(fid, dtype=dtype, count=samp * dtype_byte)
         ch_data = ch_data.reshape(-1, 3).astype(np.int32)
         ch_data = ((ch_data[:, 0]) +
                    (ch_data[:, 1] << 8) +
                    (ch_data[:, 2] << 16))
         # 24th bit determines the sign
         ch_data[ch_data >= (1 << 23)] -= (1 << 24)
-    # edf data: 16bit data
+
+    # GDF data and EDF data
     else:
-        ch_data = np.fromfile(fid, dtype='<i2', count=samp)
+        ch_data = np.fromfile(fid, dtype=dtype, count=samp)
+
     return ch_data
 
 
 def _parse_tal_channel(tal_channel_data):
-    """Parse time-stamped annotation lists (TALs) in stim_channel
-    and return list of events.
+    """Parse time-stamped annotation lists (TALs) in stim_channel.
 
     Parameters
     ----------
-    tal_channel_data : ndarray, shape = [n_samples]
+    tal_channel_data : ndarray, shape = [n_chans, n_samples]
         channel data in EDF+ TAL format
 
     Returns
@@ -250,14 +374,14 @@ def _parse_tal_channel(tal_channel_data):
     ----------
     http://www.edfplus.info/specs/edfplus.html#tal
     """
-
     # convert tal_channel to an ascii string
     tals = bytearray()
-    for s in tal_channel_data:
-        i = int(s)
-        tals.extend(np.uint8([i % 256, i // 256]))
+    for chan in tal_channel_data:
+        for s in chan:
+            i = int(s)
+            tals.extend(np.uint8([i % 256, i // 256]))
 
-    regex_tal = '([+-]\d+\.?\d*)(\x15(\d+\.?\d*))?(\x14.*?)\x14\x00'
+    regex_tal = '([+-]\\d+\\.?\\d*)(\x15(\\d+\\.?\\d*))?(\x14.*?)\x14\x00'
     # use of latin-1 because characters are only encoded for the first 256
     # code points and utf-8 can triggers an "invalid continuation byte" error
     tal_list = re.findall(regex_tal, tals.decode('latin-1'))
@@ -273,104 +397,72 @@ def _parse_tal_channel(tal_channel_data):
     return events
 
 
-def _get_edf_info(fname, stim_channel, annot, annotmap, eog, misc, preload):
-    """Extracts all the information from the EDF+,BDF file"""
-
+def _get_info(fname, stim_channel, annot, annotmap, eog, misc, exclude,
+              preload):
+    """Extract all the information from the EDF+, BDF or GDF file."""
     if eog is None:
         eog = []
     if misc is None:
         misc = []
 
-    edf_info = dict()
-    edf_info['annot'] = annot
-    edf_info['annotmap'] = annotmap
-    edf_info['events'] = []
-
-    with open(fname, 'rb') as fid:
-        assert(fid.tell() == 0)
-        fid.seek(168)  # Seek 8 + 80 bytes for Subject id + 80 bytes for rec id
-
-        day, month, year = [int(x) for x in re.findall('(\d+)',
-                                                       fid.read(8).decode())]
-        hour, minute, sec = [int(x) for x in re.findall('(\d+)',
-                                                        fid.read(8).decode())]
-        date = datetime.datetime(year + 2000, month, day, hour, minute, sec)
-
-        edf_info['data_offset'] = header_nbytes = int(fid.read(8).decode())
-        subtype = fid.read(44).strip().decode()[:5]
-        if len(subtype) > 0:
-            edf_info['subtype'] = subtype
-        else:
-            edf_info['subtype'] = os.path.splitext(fname)[1][1:].lower()
-
-        edf_info['n_records'] = n_records = int(fid.read(8).decode())
-        # record length in seconds
-        record_length = float(fid.read(8).decode())
-        if record_length == 0:
-            edf_info['record_length'] = record_length = 1.
-            warn('Header information is incorrect for record length. Default '
-                 'record length set to 1.')
-        else:
-            edf_info['record_length'] = record_length
-        nchan = int(fid.read(4).decode())
-        channels = list(range(nchan))
-        ch_names = [fid.read(16).strip().decode() for ch in channels]
-        for ch in channels:
-            fid.read(80)  # transducer
-        units = [fid.read(8).strip().decode() for ch in channels]
-        for i, unit in enumerate(units):
-            if unit == 'uV':
-                units[i] = 1e-6
-            else:
-                units[i] = 1
-        edf_info['units'] = units
-        physical_min = np.array([float(fid.read(8).decode())
-                                 for ch in channels])
-        edf_info['physical_min'] = physical_min
-        physical_max = np.array([float(fid.read(8).decode())
-                                 for ch in channels])
-        digital_min = np.array([float(fid.read(8).decode())
-                                for ch in channels])
-        edf_info['digital_min'] = digital_min
-        digital_max = np.array([float(fid.read(8).decode())
-                                for ch in channels])
-        prefiltering = [fid.read(80).strip().decode() for ch in channels][:-1]
-        highpass = np.ravel([re.findall('HP:\s+(\w+)', filt)
-                             for filt in prefiltering])
-        lowpass = np.ravel([re.findall('LP:\s+(\w+)', filt)
-                            for filt in prefiltering])
-
-        # number of samples per record
-        n_samps = np.array([int(fid.read(8).decode()) for ch in channels])
-        edf_info['n_samps'] = n_samps
-
-        fid.read(32 * nchan).decode()  # reserved
-        assert fid.tell() == header_nbytes
-
-    physical_ranges = physical_max - physical_min
-    cals = digital_max - digital_min
-
-    if edf_info['subtype'] in ('24BIT', 'bdf'):
-        edf_info['data_size'] = 3  # 24-bit (3 byte) integers
+    # Read header from file
+    ext = os.path.splitext(fname)[1][1:].lower()
+    logger.info('%s file detected' % ext.upper())
+    if ext in ('bdf', 'edf'):
+        edf_info = _read_edf_header(fname, annot, annotmap, exclude)
+    elif ext in ('gdf'):
+        if annot is not None:
+            warn('Annotations not yet supported for GDF files.')
+        edf_info = _read_gdf_header(fname, stim_channel, exclude)
+        if 'stim_data' not in edf_info and stim_channel == 'auto':
+            stim_channel = None  # Cannot construct stim channel.
     else:
-        edf_info['data_size'] = 2  # 16-bit (2 byte) integers
+        raise NotImplementedError(
+            'Only GDF, EDF, and BDF files are supported, got %s.' % ext)
 
-    # Creates a list of dicts of eeg channels for raw.info
-    logger.info('Setting channel info structure...')
-    chs = list()
+    include = edf_info['include']
+    ch_names = edf_info['ch_names']
+    n_samps = edf_info['n_samps'][include]
+    nchan = edf_info['nchan']
+    physical_ranges = edf_info['physical_max'] - edf_info['physical_min']
+    cals = edf_info['digital_max'] - edf_info['digital_min']
+    if np.any(~np.isfinite(cals)):
+        idx = np.where(~np.isfinite(cals))[0]
+        warn('Scaling factor is not defined in following channels:\n' +
+             ', '.join(ch_names[i] for i in idx))
+        cals[idx] = 1
+    if 'stim_data' in edf_info and stim_channel == 'auto':  # For GDF events.
+        cals = np.append(cals, 1)
+    # Check that stimulus channel exists in dataset
+    if not ('stim_data' in edf_info or
+            'EDF Annotations' in ch_names) and stim_channel == 'auto':
+        stim_channel = None
+    if stim_channel is not None:
+        stim_channel = _check_stim_channel(stim_channel, ch_names, include)
 
+    # Annotations
     tal_ch_name = 'EDF Annotations'
-    if tal_ch_name in ch_names:
-        tal_channel = ch_names.index(tal_ch_name)
+    tal_chs = np.where(np.array(ch_names) == tal_ch_name)[0]
+    if len(tal_chs) > 0:
+        if len(tal_chs) > 1:
+            warn('Channel names are not unique, found duplicates for: %s. '
+                 'Adding running numbers to duplicate channel names.'
+                 % tal_ch_name)
+        for idx, tal_ch in enumerate(tal_chs, 1):
+            ch_names[tal_ch] = ch_names[tal_ch] + '-%s' % idx
+        tal_channel = tal_chs
     else:
         tal_channel = None
     edf_info['tal_channel'] = tal_channel
+
     if tal_channel is not None and stim_channel is not None and not preload:
         raise RuntimeError('%s' % ('EDF+ Annotations (TAL) channel needs to be'
                                    ' parsed completely on loading.'
                                    ' You must set preload parameter to True.'))
-    if stim_channel == -1:
-        stim_channel = nchan - 1
+
+    # Creates a list of dicts of eeg channels for raw.info
+    logger.info('Setting channel info structure...')
+    chs = list()
     pick_mask = np.ones(len(ch_names))
     for idx, ch_info in enumerate(zip(ch_names, physical_ranges, cals)):
         ch_name, physical_range, cal = ch_info
@@ -405,10 +497,10 @@ def _get_edf_info(fname, stim_channel, annot, annotmap, eog, misc, preload):
             pick_mask[idx] = False
             chan_info['ch_name'] = 'STI 014'
             ch_names[idx] = chan_info['ch_name']
-            units[idx] = 1
+            edf_info['units'][idx] = 1
             if isinstance(stim_channel, str):
                 stim_channel = idx
-        if tal_channel == idx:
+        if tal_channel is not None and idx in tal_channel:
             chan_info['range'] = 1
             chan_info['cal'] = 1
             chan_info['coil_type'] = FIFF.FIFFV_COIL_NONE
@@ -423,13 +515,26 @@ def _get_edf_info(fname, stim_channel, annot, annotmap, eog, misc, preload):
         edf_info['max_samp'] = max_samp = n_samps[picks].max()
     else:
         edf_info['max_samp'] = max_samp = n_samps.max()
-    # sfreq defined as the max sampling rate of eeg
-    sfreq = n_samps.max() / record_length
+
+    # Info structure
+    # -------------------------------------------------------------------------
+
+    # sfreq defined as the max sampling rate of eeg (stim_ch not included)
+    if stim_channel is None:
+        data_samps = n_samps
+    else:
+        data_samps = np.delete(n_samps, slice(stim_channel, stim_channel + 1))
+    sfreq = data_samps.max() * \
+        edf_info['record_length'][1] / edf_info['record_length'][0]
+
     info = _empty_info(sfreq)
-    info['filename'] = fname
-    info['meas_date'] = calendar.timegm(date.utctimetuple())
+    info['meas_date'] = edf_info['meas_date']
     info['chs'] = chs
+    info['ch_names'] = ch_names
 
+    # Filter settings
+    highpass = edf_info['highpass']
+    lowpass = edf_info['lowpass']
     if highpass.size == 0:
         pass
     elif all(highpass):
@@ -443,9 +548,8 @@ def _get_edf_info(fname, stim_channel, annot, annotmap, eog, misc, preload):
         info['highpass'] = float(np.max(highpass))
         warn('Channels contain different highpass filters. Highest filter '
              'setting will be stored.')
-
     if lowpass.size == 0:
-        pass
+        pass  # Placeholder for future use. Lowpass set in _empty_info.
     elif all(lowpass):
         if lowpass[0] == 'NaN':
             pass  # Placeholder for future use. Lowpass set in _empty_info.
@@ -459,18 +563,534 @@ def _get_edf_info(fname, stim_channel, annot, annotmap, eog, misc, preload):
     # Some keys to be consistent with FIF measurement info
     info['description'] = None
     info['buffer_size_sec'] = 1.
-    edf_info['nsamples'] = int(n_records * max_samp)
+    edf_info['nsamples'] = int(edf_info['n_records'] * max_samp)
 
     # These are the conditions under which a stim channel will be interpolated
     if stim_channel is not None and not (annot and annotmap) and \
             tal_channel is None and n_samps[stim_channel] != int(max_samp):
         warn('Interpolating stim channel. Events may jitter.')
     info._update_redundant()
+
     return info, edf_info
 
 
+def _read_edf_header(fname, annot, annotmap, exclude):
+    """Read header information from EDF+ or BDF file."""
+    edf_info = dict()
+    edf_info.update(annot=annot, annotmap=annotmap, events=[])
+
+    with open(fname, 'rb') as fid:
+
+        fid.read(8)  # version (unused here)
+
+        # patient ID
+        pid = fid.read(80).decode()
+        pid = pid.split(' ', 2)
+        patient = {}
+        if len(pid) >= 2:
+            patient['id'] = pid[0]
+            patient['name'] = pid[1]
+
+        # Recording ID
+        meas_id = {}
+        meas_id['recording_id'] = fid.read(80).decode().strip(' \x00')
+
+        day, month, year = [int(x) for x in
+                            re.findall(r'(\d+)', fid.read(8).decode())]
+        hour, minute, sec = [int(x) for x in
+                             re.findall(r'(\d+)', fid.read(8).decode())]
+        century = 2000 if year < 50 else 1900
+        date = datetime.datetime(year + century, month, day, hour, minute, sec)
+
+        header_nbytes = int(fid.read(8).decode())
+
+        subtype = fid.read(44).strip().decode()[:5]
+        if len(subtype) == 0:
+            subtype = os.path.splitext(fname)[1][1:].lower()
+
+        n_records = int(fid.read(8).decode())
+        record_length = np.array([float(fid.read(8)), 1.])  # in seconds
+        if record_length[0] == 0:
+            record_length = record_length[0] = 1.
+            warn('Header information is incorrect for record length. Default '
+                 'record length set to 1.')
+
+        nchan = int(fid.read(4).decode())
+        channels = list(range(nchan))
+        ch_names = [fid.read(16).strip().decode() for ch in channels]
+        exclude = [ch_names.index(idx) for idx in exclude]
+        for ch in channels:
+            fid.read(80)  # transducer
+        units = [fid.read(8).strip().decode() for ch in channels]
+        edf_info['units'] = list()
+        include = list()
+        for i, unit in enumerate(units):
+            if i in exclude:
+                continue
+            if unit == 'uV':
+                edf_info['units'].append(1e-6)
+            else:
+                edf_info['units'].append(1)
+            include.append(i)
+        ch_names = [ch_names[idx] for idx in include]
+
+        physical_min = np.array([float(fid.read(8).decode())
+                                 for ch in channels])[include]
+        physical_max = np.array([float(fid.read(8).decode())
+                                 for ch in channels])[include]
+        digital_min = np.array([float(fid.read(8).decode())
+                                for ch in channels])[include]
+        digital_max = np.array([float(fid.read(8).decode())
+                                for ch in channels])[include]
+        prefiltering = [fid.read(80).decode().strip(' \x00')
+                        for ch in channels][:-1]
+        highpass = np.ravel([re.findall(r'HP:\s+(\w+)', filt)
+                             for filt in prefiltering])
+        lowpass = np.ravel([re.findall(r'LP:\s+(\w+)', filt)
+                            for filt in prefiltering])
+
+        # number of samples per record
+        n_samps = np.array([int(fid.read(8).decode()) for ch
+                            in channels])
+
+        # Populate edf_info
+        edf_info.update(
+            ch_names=ch_names, data_offset=header_nbytes,
+            digital_max=digital_max, digital_min=digital_min, exclude=exclude,
+            highpass=highpass, include=include, lowpass=lowpass,
+            meas_date=calendar.timegm(date.utctimetuple()),
+            n_records=n_records, n_samps=n_samps, nchan=nchan,
+            subject_info=patient, physical_max=physical_max,
+            physical_min=physical_min, record_length=record_length,
+            subtype=subtype)
+
+        fid.read(32 * nchan).decode()  # reserved
+        assert fid.tell() == header_nbytes
+
+        fid.seek(0, 2)
+        n_bytes = fid.tell()
+        n_data_bytes = n_bytes - header_nbytes
+        total_samps = (n_data_bytes // 3 if subtype == '24BIT'
+                       else n_data_bytes // 2)
+        read_records = total_samps // np.sum(n_samps)
+        if n_records != read_records:
+            warn('Number of records from the header does not match the file '
+                 'size (perhaps the recording was not stopped before exiting).'
+                 ' Inferring from the file size.')
+            edf_info['n_records'] = n_records = read_records
+
+        if subtype in ('24BIT', 'bdf'):
+            edf_info['dtype_byte'] = 3  # 24-bit (3 byte) integers
+            edf_info['dtype_np'] = np.uint8
+        else:
+            edf_info['dtype_byte'] = 2  # 16-bit (2 byte) integers
+            edf_info['dtype_np'] = np.int16
+
+    return edf_info
+
+
+def _read_gdf_header(fname, stim_channel, exclude):
+    """Read GDF 1.x and GDF 2.x header info."""
+    edf_info = dict()
+    events = []
+    edf_info['annot'] = None
+    edf_info['annotmap'] = None
+    with open(fname, 'rb') as fid:
+
+        version = fid.read(8).decode()
+
+        gdftype_np = (None, np.int8, np.uint8, np.int16, np.uint16, np.int32,
+                      np.uint32, np.int64, np.uint64, None, None, None, None,
+                      None, None, None, np.float32, np.float64)
+        gdftype_byte = [np.dtype(x).itemsize if x is not None else 0
+                        for x in gdftype_np]
+        assert sum(gdftype_byte) == 42
+
+        edf_info['type'] = edf_info['subtype'] = version[:3]
+        edf_info['number'] = float(version[4:])
+
+        # GDF 1.x
+        # ----------------------------------------------------------------------
+        if edf_info['number'] < 1.9:
+
+            # patient ID
+            pid = fid.read(80).decode()
+            pid = pid.split(' ', 2)
+            patient = {}
+            if len(pid) >= 2:
+                patient['id'] = pid[0]
+                patient['name'] = pid[1]
+
+            # Recording ID
+            meas_id = {}
+            meas_id['recording_id'] = fid.read(80).decode().strip(' \x00')
+
+            # date
+            tm = fid.read(16).decode().strip(' \x00')
+            try:
+                if tm[14:16] == '  ':
+                    tm = tm[:14] + '00' + tm[16:]
+                date = (datetime.datetime(int(tm[0:4]), int(tm[4:6]),
+                                          int(tm[6:8]), int(tm[8:10]),
+                                          int(tm[10:12]), int(tm[12:14]),
+                                          int(tm[14:16]) * pow(10, 4)))
+            except Exception:
+                date = datetime.datetime(2000, 1, 1)
+
+            header_nbytes = np.fromfile(fid, np.int64, 1)[0]
+            meas_id['equipment'] = np.fromfile(fid, np.uint8, 8)[0]
+            meas_id['hospital'] = np.fromfile(fid, np.uint8, 8)[0]
+            meas_id['technician'] = np.fromfile(fid, np.uint8, 8)[0]
+            fid.seek(20, 1)    # 20bytes reserved
+
+            n_records = np.fromfile(fid, np.int64, 1)[0]
+            # record length in seconds
+            record_length = np.fromfile(fid, np.uint32, 2)
+            if record_length[0] == 0:
+                record_length[0] = 1.
+                warn('Header information is incorrect for record length. '
+                     'Default record length set to 1.')
+            nchan = np.fromfile(fid, np.uint32, 1)[0]
+            channels = list(range(nchan))
+            ch_names = [fid.read(16).decode().strip(' \x00')
+                        for ch in channels]
+            fid.seek(80 * len(channels), 1)  # transducer
+            units = [fid.read(8).decode().strip(' \x00') for ch in channels]
+
+            exclude = [ch_names.index(idx) for idx in exclude]
+            include = list()
+            for i, unit in enumerate(units):
+                if unit[:2] == 'uV':
+                    units[i] = 1e-6
+                else:
+                    units[i] = 1
+                include.append(i)
+
+            ch_names = [ch_names[idx] for idx in include]
+            physical_min = np.fromfile(fid, np.float64, len(channels))
+            physical_max = np.fromfile(fid, np.float64, len(channels))
+            digital_min = np.fromfile(fid, np.int64, len(channels))
+            digital_max = np.fromfile(fid, np.int64, len(channels))
+            prefiltering = [fid.read(80).decode().strip(' \x00')
+                            for ch in channels][:-1]
+            highpass = np.ravel([re.findall(r'HP:\s+(\w+)', filt)
+                                 for filt in prefiltering])
+            lowpass = np.ravel([re.findall('LP:\\s+(\\w+)', filt)
+                                for filt in prefiltering])
+
+            # n samples per record
+            n_samps = np.fromfile(fid, np.int32, len(channels))
+
+            # channel data type
+            dtype = np.fromfile(fid, np.int32, len(channels))
+
+            # total number of bytes for data
+            bytes_tot = np.sum([gdftype_byte[t] * n_samps[i]
+                                for i, t in enumerate(dtype)])
+
+            # Populate edf_info
+            edf_info.update(
+                bytes_tot=bytes_tot, ch_names=ch_names,
+                data_offset=header_nbytes, digital_min=digital_min,
+                digital_max=digital_max,
+                dtype_byte=[gdftype_byte[t] for t in dtype],
+                dtype_np=[gdftype_np[t] for t in dtype], exclude=exclude,
+                highpass=highpass, include=include, lowpass=lowpass,
+                meas_date=calendar.timegm(date.utctimetuple()),
+                meas_id=meas_id, n_records=n_records, n_samps=n_samps,
+                nchan=nchan, subject_info=patient, physical_max=physical_max,
+                physical_min=physical_min, record_length=record_length,
+                units=units)
+
+            fid.seek(32 * edf_info['nchan'], 1)  # reserved
+            assert fid.tell() == header_nbytes
+
+            # Event table
+            # ------------------------------------------------------------------
+            etp = header_nbytes + n_records * edf_info['bytes_tot']
+            # skip data to go to event table
+            fid.seek(etp)
+            etmode = np.fromfile(fid, np.uint8, 1)[0]
+            if etmode in (1, 3):
+                sr = np.fromfile(fid, np.uint8, 3)
+                event_sr = sr[0]
+                for i in range(1, len(sr)):
+                    event_sr = event_sr + sr[i] * 2 ** (i * 8)
+                n_events = np.fromfile(fid, np.uint32, 1)[0]
+                pos = np.fromfile(fid, np.uint32, n_events) - 1  # 1-based inds
+                typ = np.fromfile(fid, np.uint16, n_events)
+
+                if etmode == 3:
+                    chn = np.fromfile(fid, np.uint16, n_events)
+                    dur = np.fromfile(fid, np.uint32, n_events)
+                else:
+                    chn = np.zeros(n_events, dtype=np.int32)
+                    dur = np.ones(n_events, dtype=np.uint32)
+                np.clip(dur, 1, np.inf, out=dur)
+                events = [n_events, pos, typ, chn, dur]
+
+        # GDF 2.x
+        # ----------------------------------------------------------------------
+        else:
+            # FIXED HEADER
+            handedness = ('Unknown', 'Right', 'Left', 'Equal')
+            gender = ('Unknown', 'Male', 'Female')
+            scale = ('Unknown', 'No', 'Yes', 'Corrected')
+
+            # date
+            pid = fid.read(66).decode()
+            pid = pid.split(' ', 2)
+            patient = {}
+            if len(pid) >= 2:
+                patient['id'] = pid[0]
+                patient['name'] = pid[1]
+            fid.seek(10, 1)  # 10bytes reserved
+
+            # Smoking / Alcohol abuse / drug abuse / medication
+            sadm = np.fromfile(fid, np.uint8, 1)[0]
+            patient['smoking'] = scale[sadm % 4]
+            patient['alcohol_abuse'] = scale[(sadm >> 2) % 4]
+            patient['drug_abuse'] = scale[(sadm >> 4) % 4]
+            patient['medication'] = scale[(sadm >> 6) % 4]
+            patient['weight'] = np.fromfile(fid, np.uint8, 1)[0]
+            if patient['weight'] == 0 or patient['weight'] == 255:
+                patient['weight'] = None
+            patient['height'] = np.fromfile(fid, np.uint8, 1)[0]
+            if patient['height'] == 0 or patient['height'] == 255:
+                patient['height'] = None
+
+            # Gender / Handedness / Visual Impairment
+            ghi = np.fromfile(fid, np.uint8, 1)[0]
+            patient['sex'] = gender[ghi % 4]
+            patient['handedness'] = handedness[(ghi >> 2) % 4]
+            patient['visual'] = scale[(ghi >> 4) % 4]
+
+            # Recording identification
+            meas_id = {}
+            meas_id['recording_id'] = fid.read(64).decode().strip(' \x00')
+            vhsv = np.fromfile(fid, np.uint8, 4)
+            loc = {}
+            if vhsv[3] == 0:
+                loc['vertpre'] = 10 * int(vhsv[0] >> 4) + int(vhsv[0] % 16)
+                loc['horzpre'] = 10 * int(vhsv[1] >> 4) + int(vhsv[1] % 16)
+                loc['size'] = 10 * int(vhsv[2] >> 4) + int(vhsv[2] % 16)
+            else:
+                loc['vertpre'] = 29
+                loc['horzpre'] = 29
+                loc['size'] = 29
+            loc['version'] = 0
+            loc['latitude'] = \
+                float(np.fromfile(fid, np.uint32, 1)[0]) / 3600000
+            loc['longitude'] = \
+                float(np.fromfile(fid, np.uint32, 1)[0]) / 3600000
+            loc['altitude'] = float(np.fromfile(fid, np.int32, 1)[0]) / 100
+            meas_id['loc'] = loc
+
+            date = np.fromfile(fid, np.uint64, 1)[0]
+            if date == 0:
+                date = datetime.datetime(1, 1, 1)
+            else:
+                date = datetime.datetime(1, 1, 1) + \
+                    datetime.timedelta(date * pow(2, -32) - 367)
+
+            birthday = np.fromfile(fid, np.uint64, 1).tolist()[0]
+            if birthday == 0:
+                birthday = datetime.datetime(1, 1, 1)
+            else:
+                birthday = (datetime.datetime(1, 1, 1) +
+                            datetime.timedelta(birthday * pow(2, -32) - 367))
+            patient['birthday'] = birthday
+            if patient['birthday'] != datetime.datetime(1, 1, 1, 0, 0):
+                today = datetime.datetime.today()
+                patient['age'] = today.year - patient['birthday'].year
+                today = today.replace(year=patient['birthday'].year)
+                if today < patient['birthday']:
+                    patient['age'] -= 1
+            else:
+                patient['age'] = None
+
+            header_nbytes = np.fromfile(fid, np.uint16, 1)[0] * 256
+
+            fid.seek(6, 1)  # 6 bytes reserved
+            meas_id['equipment'] = np.fromfile(fid, np.uint8, 8)
+            meas_id['ip'] = np.fromfile(fid, np.uint8, 6)
+            patient['headsize'] = np.fromfile(fid, np.uint16, 3)
+            patient['headsize'] = np.asarray(patient['headsize'], np.float32)
+            patient['headsize'] = np.ma.masked_array(
+                patient['headsize'],
+                np.equal(patient['headsize'], 0), None).filled()
+            ref = np.fromfile(fid, np.float32, 3)
+            gnd = np.fromfile(fid, np.float32, 3)
+            n_records = np.fromfile(fid, np.int64, 1)[0]
+
+            # record length in seconds
+            record_length = np.fromfile(fid, np.uint32, 2)
+            if record_length[0] == 0:
+                record_length[0] = 1.
+                warn('Header information is incorrect for record length. '
+                     'Default record length set to 1.')
+
+            nchan = np.fromfile(fid, np.uint16, 1)[0]
+            fid.seek(2, 1)  # 2bytes reserved
+
+            # Channels (variable header)
+            channels = list(range(nchan))
+            ch_names = [fid.read(16).decode().strip(' \x00')
+                        for ch in channels]
+            exclude = [ch_names.index(idx) for idx in exclude]
+
+            fid.seek(80 * len(channels), 1)  # reserved space
+            fid.seek(6 * len(channels), 1)  # phys_dim, obsolete
+
+            """The Physical Dimensions are encoded as int16, according to:
+            - Units codes :
+            https://sourceforge.net/p/biosig/svn/HEAD/tree/trunk/biosig/doc/units.csv
+            - Decimal factors codes:
+            https://sourceforge.net/p/biosig/svn/HEAD/tree/trunk/biosig/doc/DecimalFactors.txt
+            """  # noqa
+            units = np.fromfile(fid, np.uint16, len(channels)).tolist()
+            unitcodes = np.array(units[:])
+            include = list()
+            for i, unit in enumerate(units):
+                if unit == 4275:  # microvolts
+                    units[i] = 1e-6
+                elif unit == 512:  # dimensionless
+                    units[i] = 1
+                elif unit == 0:
+                    units[i] = 1  # unrecognized
+                else:
+                    warn('Unsupported physical dimension for channel %d '
+                         '(assuming dimensionless). Please contact the '
+                         'MNE-Python developers for support.' % i)
+                    units[i] = 1
+                include.append(i)
+
+            ch_names = [ch_names[idx] for idx in include]
+            physical_min = np.fromfile(fid, np.float64, len(channels))
+            physical_max = np.fromfile(fid, np.float64, len(channels))
+            digital_min = np.fromfile(fid, np.float64, len(channels))
+            digital_max = np.fromfile(fid, np.float64, len(channels))
+
+            fid.seek(68 * len(channels), 1)  # obsolete
+            lowpass = np.fromfile(fid, np.float32, len(channels))
+            highpass = np.fromfile(fid, np.float32, len(channels))
+            notch = np.fromfile(fid, np.float32, len(channels))
+
+            # number of samples per record
+            n_samps = np.fromfile(fid, np.int32, len(channels))
+
+            # data type
+            dtype = np.fromfile(fid, np.int32, len(channels))
+
+            channel = {}
+            channel['xyz'] = [np.fromfile(fid, np.float32, 3)[0]
+                              for ch in channels]
+
+            if edf_info['number'] < 2.19:
+                impedance = np.fromfile(fid, np.uint8,
+                                        len(channels)).astype(float)
+                impedance[impedance == 255] = np.nan
+                channel['impedance'] = pow(2, impedance / 8)
+                fid.seek(19 * len(channels), 1)  # reserved
+            else:
+                tmp = np.fromfile(fid, np.float32, 5 * len(channels))
+                tmp = tmp[::5]
+                fZ = tmp[:]
+                impedance = tmp[:]
+                # channels with no voltage (code 4256) data
+                ch = [unitcodes & 65504 != 4256][0]
+                impedance[np.where(ch)] = None
+                # channel with no impedance (code 4288) data
+                ch = [unitcodes & 65504 != 4288][0]
+                fZ[np.where(ch)[0]] = None
+
+            assert fid.tell() == header_nbytes
+
+            # total number of bytes for data
+            bytes_tot = np.sum([gdftype_byte[t] * n_samps[i]
+                                for i, t in enumerate(dtype)])
+
+            # Populate edf_info
+            edf_info.update(
+                bytes_tot=bytes_tot, ch_names=ch_names,
+                data_offset=header_nbytes,
+                dtype_byte=[gdftype_byte[t] for t in dtype],
+                dtype_np=[gdftype_np[t] for t in dtype],
+                digital_min=digital_min, digital_max=digital_max,
+                exclude=exclude, gnd=gnd, highpass=highpass, include=include,
+                impedance=impedance, lowpass=lowpass,
+                meas_date=calendar.timegm(date.utctimetuple()),
+                meas_id=meas_id, n_records=n_records, n_samps=n_samps,
+                nchan=nchan, notch=notch, subject_info=patient,
+                physical_max=physical_max, physical_min=physical_min,
+                record_length=record_length, ref=ref, units=units)
+
+            # EVENT TABLE
+            # ------------------------------------------------------------------
+            etp = edf_info['data_offset'] + edf_info['n_records'] * \
+                edf_info['bytes_tot']
+            fid.seek(etp)  # skip data to go to event table
+            etmode = fid.read(1).decode()
+            if etmode != '':
+                etmode = np.fromstring(etmode, np.uint8).tolist()[0]
+
+                if edf_info['number'] < 1.94:
+                    sr = np.fromfile(fid, np.uint8, 3)
+                    event_sr = sr[0]
+                    for i in range(1, len(sr)):
+                        event_sr = event_sr + sr[i] * 2**(i * 8)
+                    n_events = np.fromfile(fid, np.uint32, 1)[0]
+                else:
+                    ne = np.fromfile(fid, np.uint8, 3)
+                    n_events = ne[0]
+                    for i in range(1, len(ne)):
+                        n_events = n_events + ne[i] * 2**(i * 8)
+                    event_sr = np.fromfile(fid, np.float32, 1)[0]
+
+                pos = np.fromfile(fid, np.uint32, n_events) - 1  # 1-based inds
+                typ = np.fromfile(fid, np.uint16, n_events)
+
+                if etmode == 3:
+                    chn = np.fromfile(fid, np.uint16, n_events)
+                    dur = np.fromfile(fid, np.uint32, n_events)
+                else:
+                    chn = np.zeros(n_events, dtype=np.uint32)
+                    dur = np.ones(n_events, dtype=np.uint32)
+                np.clip(dur, 1, np.inf, out=dur)
+                events = [n_events, pos, typ, chn, dur]
+                edf_info['event_sfreq'] = event_sr
+
+    if stim_channel == 'auto' and edf_info['nchan'] not in exclude:
+        if len(events) == 0:
+            warn('No events found. Cannot construct a stimulus channel.')
+            edf_info['events'] = list()
+            return edf_info
+        edf_info['include'].append(edf_info['nchan'])
+        edf_info['n_samps'] = np.append(edf_info['n_samps'], 0)
+        edf_info['units'] = np.append(edf_info['units'], 1)
+        edf_info['ch_names'] += [u'STI 014']
+        edf_info['physical_min'] = np.append(edf_info['physical_min'], 0)
+        edf_info['digital_min'] = np.append(edf_info['digital_min'], 0)
+        vmax = np.max(events[2])
+        edf_info['physical_max'] = np.append(edf_info['physical_max'], vmax)
+        edf_info['digital_max'] = np.append(edf_info['digital_max'], vmax)
+
+        data = np.zeros(np.max(n_samps * n_records))
+        warn_overlap = False
+        for samp, id, dur in zip(events[1], events[2], events[4]):
+            if np.sum(data[samp:samp + dur]) > 0:
+                warn_overlap = True  # Warn only once.
+            data[samp:samp + dur] += id
+        if warn_overlap:
+            warn('Overlapping events detected. Use find_edf_events for the '
+                 'original events.')
+        edf_info['stim_data'] = data
+    edf_info['events'] = events
+    return edf_info
+
+
 def _read_annot(annot, annotmap, sfreq, data_length):
-    """Annotation File Reader
+    """Annotation File Reader.
 
     Parameters
     ----------
@@ -488,13 +1108,13 @@ def _read_annot(annot, annotmap, sfreq, data_length):
     stim_channel : ndarray
         An array containing stimulus trigger events.
     """
-    pat = '([+/-]\d+.\d+),(\w+)'
+    pat = '([+/-]\\d+.\\d+),(\\w+)'
     annot = open(annot).read()
     triggers = re.findall(pat, annot)
     times, values = zip(*triggers)
     times = [float(time) * sfreq for time in times]
 
-    pat = '(\w+):(\d+)'
+    pat = r'(\w+):(\d+)'
     annotmap = open(annotmap).read()
     mappings = re.findall(pat, annotmap)
     maps = {}
@@ -509,15 +1129,43 @@ def _read_annot(annot, annotmap, sfreq, data_length):
     return stim_channel
 
 
+def _check_stim_channel(stim_channel, ch_names, include):
+    """Check that the stimulus channel exists in the current datafile."""
+    if isinstance(stim_channel, str):
+        if stim_channel == 'auto':
+            if 'auto' in ch_names:
+                raise ValueError("'auto' exists as a channel name. Change "
+                                 "stim_channel parameter!")
+            stim_channel = len(include) - 1
+        elif stim_channel not in ch_names:
+            err = 'Could not find a channel named "{}" in datafile.' \
+                  .format(stim_channel)
+            casematch = [ch for ch in ch_names
+                         if stim_channel.lower().replace(' ', '') ==
+                         ch.lower().replace(' ', '')]
+            if casematch:
+                err += ' Closest match is "{}".'.format(casematch[0])
+            raise ValueError(err)
+    else:
+        if stim_channel == -1:
+            stim_channel = len(include) - 1
+        elif stim_channel > len(ch_names):
+            raise ValueError('Requested stim_channel index ({}) exceeds total '
+                             'number of channels in datafile ({})'
+                             .format(stim_channel, len(ch_names)))
+
+    return stim_channel
+
+
 def read_raw_edf(input_fname, montage=None, eog=None, misc=None,
-                 stim_channel=-1, annot=None, annotmap=None,
+                 stim_channel='auto', annot=None, annotmap=None, exclude=(),
                  preload=False, verbose=None):
-    """Reader function for EDF+, BDF conversion to FIF
+    """Reader function for EDF+, BDF, GDF conversion to FIF.
 
     Parameters
     ----------
     input_fname : str
-        Path to the EDF+,BDF file.
+        Path to the EDF+, BDF, or GDF file.
     montage : str | None | instance of Montage
         Path or instance of montage containing electrode positions.
         If None, sensor locations are (0,0,0). See the documentation of
@@ -530,10 +1178,12 @@ def read_raw_edf(input_fname, montage=None, eog=None, misc=None,
         Names of channels or list of indices that should be designated
         MISC channels. Values should correspond to the electrodes in the
         edf file. Default is None.
-    stim_channel : str | int | None
-        The channel name or channel index (starting at 0).
-        -1 corresponds to the last channel (default).
-        If None, there will be no stim channel added.
+    stim_channel : str | int | 'auto' | None
+        The channel name or channel index (starting at 0). -1 corresponds to
+        the last channel. If None, there will be no stim channel added. If
+        'auto' (default), the stim channel will be added as the last channel if
+        the header contains ``'EDF Annotations'`` or GDF events (otherwise stim
+        channel will not be added).
     annot : str | None
         Path to annotation file.
         If None, no derived stim channel will be added (for files requiring
@@ -541,6 +1191,9 @@ def read_raw_edf(input_fname, montage=None, eog=None, misc=None,
     annotmap : str | None
         Path to annotation map file containing mapping from label to trigger.
         Must be specified if annot is not None.
+    exclude : list of str
+        Channel names to exclude. This can help when reading data with
+        different sampling rates to avoid unnecessary resampling.
     preload : bool or str (default False)
         Preload data into memory for data manipulation and faster indexing.
         If True, the data will be preloaded into memory (fast, requires
@@ -548,17 +1201,37 @@ def read_raw_edf(input_fname, montage=None, eog=None, misc=None,
         file name of a memory-mapped file which is used to store the data
         on the hard drive (slower, requires less memory).
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
     raw : Instance of RawEDF
         A Raw object containing EDF data.
 
+    Notes
+    -----
+    Biosemi devices trigger codes are encoded in bits 1-16 of the status
+    channel, whereas system codes (CMS in/out-of range, battery low, etc.) are
+    coded in bits 16-23 (see http://www.biosemi.com/faq/trigger_signals.htm).
+    To retrieve correct event values (bits 1-16), one could do:
+
+        >>> events = mne.find_events(...)  # doctest:+SKIP
+        >>> events[:, 2] >>= 8  # doctest:+SKIP
+
+    It is also possible to retrieve system codes, but no particular effort has
+    been made to decode these in MNE.
+
+    For GDF files, the stimulus channel is constructed from the events in the
+    header. You should use keyword ``stim_channel=-1`` to add it at the end of
+    the channel list. The id numbers of overlapping events are simply combined
+    through addition. To get the original events from the header, use method
+    ``raw.find_edf_events``.
+
     See Also
     --------
     mne.io.Raw : Documentation of attribute and methods.
     """
     return RawEDF(input_fname=input_fname, montage=montage, eog=eog, misc=misc,
                   stim_channel=stim_channel, annot=annot, annotmap=annotmap,
-                  preload=preload, verbose=verbose)
+                  exclude=exclude, preload=preload, verbose=verbose)
diff --git a/mne/io/edf/tests/test_edf.py b/mne/io/edf/tests/test_edf.py
index 621a502..62bef44 100644
--- a/mne/io/edf/tests/test_edf.py
+++ b/mne/io/edf/tests/test_edf.py
@@ -21,10 +21,10 @@ import numpy as np
 from mne import pick_types
 from mne.datasets import testing
 from mne.externals.six import iterbytes
-from mne.utils import run_tests_if_main, requires_pandas
+from mne.utils import run_tests_if_main, requires_pandas, _TempDir
 from mne.io import read_raw_edf
 from mne.io.tests.test_raw import _test_raw_reader
-from mne.io.edf.edf import _parse_tal_channel
+from mne.io.edf.edf import _parse_tal_channel, find_edf_events
 from mne.event import find_events
 
 warnings.simplefilter('always')
@@ -45,6 +45,7 @@ data_path = testing.data_path(download=False)
 edf_stim_resamp_path = op.join(data_path, 'EDF', 'test_edf_stim_resamp.edf')
 edf_overlap_annot_path = op.join(data_path, 'EDF',
                                  'test_edf_overlapping_annotations.edf')
+edf_reduced = op.join(data_path, 'EDF', 'test_reduced.edf')
 
 
 eog = ['REOG', 'LEOG', 'IEOG']
@@ -54,7 +55,8 @@ misc = ['EXG1', 'EXG5', 'EXG8', 'M1', 'M2']
 def test_bdf_data():
     """Test reading raw bdf files."""
     raw_py = _test_raw_reader(read_raw_edf, input_fname=bdf_path,
-                              montage=montage_path, eog=eog, misc=misc)
+                              montage=montage_path, eog=eog, misc=misc,
+                              exclude=['M2', 'IEOG'], stim_channel=-1)
     assert_true('RawEDF' in repr(raw_py))
     picks = pick_types(raw_py.info, meg=False, eeg=True, exclude='bads')
     data_py, _ = raw_py[picks]
@@ -77,15 +79,26 @@ def test_edf_overlapping_annotations():
     """Test EDF with overlapping annotations."""
     n_warning = 2
     with warnings.catch_warnings(record=True) as w:
-        read_raw_edf(edf_overlap_annot_path, preload=True, verbose=True)
+        read_raw_edf(edf_overlap_annot_path, preload=True, stim_channel='auto',
+                     verbose=True)
         assert_equal(sum('overlapping' in str(ww.message) for ww in w),
                      n_warning)
 
 
+ at testing.requires_testing_data
+def test_edf_reduced():
+    """Test EDF with various sampling rates."""
+    _test_raw_reader(read_raw_edf, input_fname=edf_reduced, stim_channel=None,
+                     verbose='error')
+
+
 def test_edf_data():
     """Test edf files."""
-    _test_raw_reader(read_raw_edf, input_fname=edf_path, stim_channel=None)
-    raw_py = read_raw_edf(edf_path, preload=True)
+    raw = _test_raw_reader(read_raw_edf, input_fname=edf_path,
+                           stim_channel=None, exclude=['Ergo-Left', 'H10'],
+                           verbose='error')
+    raw_py = read_raw_edf(edf_path, stim_channel='auto', preload=True)
+    assert_equal(len(raw.ch_names) + 2, len(raw_py.ch_names))
     # Test saving and loading when annotations were parsed.
     edf_events = find_events(raw_py, output='step', shortest_event=0,
                              stim_channel='STI 014')
@@ -111,6 +124,23 @@ def test_edf_data():
 
     assert_array_equal(edf_events, events)
 
+    # Test with number of records not in header (-1).
+    tempdir = _TempDir()
+    broken_fname = op.join(tempdir, 'broken.edf')
+    with open(edf_path, 'rb') as fid_in:
+        fid_in.seek(0, 2)
+        n_bytes = fid_in.tell()
+        fid_in.seek(0, 0)
+        rbytes = fid_in.read(int(n_bytes * 0.4))
+    with open(broken_fname, 'wb') as fid_out:
+        fid_out.write(rbytes[:236])
+        fid_out.write(bytes('-1      '.encode()))
+        fid_out.write(rbytes[244:])
+    with warnings.catch_warnings(record=True):  # record mismatches
+        raw = read_raw_edf(broken_fname, preload=True, stim_channel='auto')
+        read_raw_edf(broken_fname, exclude=raw.ch_names[:132], preload=True,
+                     stim_channel='auto')
+
 
 @testing.requires_testing_data
 def test_stim_channel():
@@ -131,6 +161,8 @@ def test_stim_channel():
     data_eeglab = raw_eeglab[picks]
 
     assert_array_almost_equal(data_py, data_eeglab, 10)
+    events = find_edf_events(raw_py)
+    assert_true(len(events) - 1 == len(find_events(raw_py)))  # start not found
 
     # Test uneven sampling
     raw_py = read_raw_edf(edf_uneven_path, stim_channel=None)
@@ -145,18 +177,23 @@ def test_stim_channel():
     data_py = np.repeat(data_py, repeats=upsample)
     assert_array_equal(data_py, data_eeglab)
 
-    assert_raises(RuntimeError, read_raw_edf, edf_path, preload=False)
+    assert_raises(RuntimeError, read_raw_edf, edf_path, preload=False,
+                  stim_channel=-1)
 
     with warnings.catch_warnings(record=True) as w:
         warnings.simplefilter('always')
-        raw = read_raw_edf(edf_stim_resamp_path, verbose=True)
-    assert_equal(len(w), 1)
-    assert_true('Events may jitter' in str(w[0].message))
+        raw = read_raw_edf(edf_stim_resamp_path, verbose=True, stim_channel=-1)
+    assert_equal(len(w), 2)
+    assert_true(any('Events may jitter' in str(ww.message) for ww in w))
+    assert_true(any('truncated' in str(ww.message) for ww in w))
     with warnings.catch_warnings(record=True) as w:
         warnings.simplefilter('always')
         raw[:]
     assert_equal(len(w), 0)
 
+    events = raw_py.find_edf_events()
+    assert_true(len(events) == 0)
+
 
 def test_parse_annotation():
     """Test parsing the tal channel."""
@@ -170,7 +207,7 @@ def test_parse_annotation():
     annot = [a for a in iterbytes(annot)]
     annot[1::2] = [a * 256 for a in annot[1::2]]
     tal_channel = map(sum, zip(annot[0::2], annot[1::2]))
-    assert_equal(_parse_tal_channel(tal_channel),
+    assert_equal(_parse_tal_channel([tal_channel]),
                  [[180.0, 0, 'Lights off'], [180.0, 0, 'Close door'],
                   [180.0, 0, 'Lights off'], [180.0, 0, 'Close door'],
                   [3.14, 4.2, 'nothing'], [1800.2, 25.5, 'Apnea']])
@@ -179,7 +216,7 @@ def test_parse_annotation():
 def test_edf_annotations():
     """Test if events are detected correctly in a typical MNE workflow."""
     # test an actual file
-    raw = read_raw_edf(edf_path, preload=True)
+    raw = read_raw_edf(edf_path, preload=True, stim_channel='auto')
     edf_events = find_events(raw, output='step', shortest_event=0,
                              stim_channel='STI 014')
 
@@ -226,7 +263,8 @@ def test_edf_stim_channel():
 def test_to_data_frame():
     """Test edf Raw Pandas exporter."""
     for path in [edf_path, bdf_path]:
-        raw = read_raw_edf(path, stim_channel=None, preload=True)
+        raw = read_raw_edf(path, stim_channel=None, preload=True,
+                           verbose='error')
         _, times = raw[0, :10]
         df = raw.to_data_frame()
         assert_true((df.columns == raw.ch_names).all())
diff --git a/mne/io/edf/tests/test_gdf.py b/mne/io/edf/tests/test_gdf.py
new file mode 100644
index 0000000..55d064b
--- /dev/null
+++ b/mne/io/edf/tests/test_gdf.py
@@ -0,0 +1,92 @@
+"""Data Equivalence Tests"""
+from __future__ import print_function
+
+# Authors: Alexandre Barachant <alexandre.barachant at gmail.com>
+#          Nicolas Barascud <nicolas.barascud at ens.fr>
+#
+# License: BSD (3-clause)
+
+import os.path as op
+import warnings
+
+from nose.tools import assert_true
+from numpy.testing import (assert_array_almost_equal, assert_array_equal,
+                           assert_equal)
+import numpy as np
+import scipy.io as sio
+
+from mne.datasets import testing
+from mne.io import read_raw_edf
+from mne.utils import run_tests_if_main
+from mne import pick_types, find_events
+
+warnings.simplefilter('always')
+
+data_path = testing.data_path(download=False)
+gdf1_path = op.join(data_path, 'GDF', 'test_gdf_1.25')
+gdf2_path = op.join(data_path, 'GDF', 'test_gdf_2.20')
+
+
+ at testing.requires_testing_data
+def test_gdf_data():
+    """Test reading raw GDF 1.x files."""
+    with warnings.catch_warnings(record=True):  # interpolate / overlap events
+        raw = read_raw_edf(gdf1_path + '.gdf', eog=None,
+                           misc=None, preload=True, stim_channel='auto')
+    picks = pick_types(raw.info, meg=False, eeg=True, exclude='bads')
+    data, _ = raw[picks]
+
+    # this .npy was generated using the official biosig python package
+    raw_biosig = np.load(gdf1_path + '_biosig.npy')
+    raw_biosig = raw_biosig * 1e-6  # data are stored in microvolts
+    data_biosig = raw_biosig[picks]
+
+    # Assert data are almost equal
+    assert_array_almost_equal(data, data_biosig, 8)
+
+    # Test for stim channel
+    events = find_events(raw, shortest_event=1)
+    # The events are overlapping.
+    assert_array_equal(events[:, 0], raw._raw_extras[0]['events'][1][::2])
+
+    # Test events are encoded to stim channel.
+    events = find_events(raw)
+    evs = raw.find_edf_events()
+    assert_true(all([event in evs[1] for event in events[:, 0]]))
+
+
+ at testing.requires_testing_data
+def test_gdf2_data():
+    """Test reading raw GDF 2.x files."""
+    raw = read_raw_edf(gdf2_path + '.gdf', eog=None, misc=None, preload=True,
+                       stim_channel='STATUS')
+
+    nchan = raw.info['nchan']
+    ch_names = raw.ch_names  # Renamed STATUS -> STI 014.
+    picks = pick_types(raw.info, meg=False, eeg=True, exclude='bads')
+    data, _ = raw[picks]
+
+    # This .mat was generated using the official biosig matlab package
+    mat = sio.loadmat(gdf2_path + '_biosig.mat')
+    data_biosig = mat['dat'] * 1e-6  # data are stored in microvolts
+    data_biosig = data_biosig[picks]
+
+    # Assert data are almost equal
+    assert_array_almost_equal(data, data_biosig, 8)
+
+    # Find events
+    events = find_events(raw, verbose=1)
+    events[:, 2] >>= 8  # last 8 bits are system events in biosemi files
+    assert_equal(events.shape[0], 2)  # 2 events in file
+    assert_array_equal(events[:, 2], [20, 28])
+
+    with warnings.catch_warnings(record=True) as w:
+        # header contains no events
+        raw = read_raw_edf(gdf2_path + '.gdf', stim_channel='auto')
+        assert_equal(len(w), 1)
+        assert_true(str(w[0].message).startswith('No events found.'))
+    assert_equal(nchan, raw.info['nchan'])  # stim channel not constructed
+    assert_array_equal(ch_names[1:], raw.ch_names[1:])
+
+
+run_tests_if_main()
diff --git a/mne/io/eeglab/__init__.py b/mne/io/eeglab/__init__.py
index 871142f..238bd90 100644
--- a/mne/io/eeglab/__init__.py
+++ b/mne/io/eeglab/__init__.py
@@ -1,5 +1,5 @@
-"""EEGLAB module for conversion to FIF"""
+"""EEGLAB module for conversion to FIF."""
 
 # Author: Mainak Jas <mainak.jas at telecom-paristech.fr>
 
-from .eeglab import read_raw_eeglab, read_epochs_eeglab
+from .eeglab import read_raw_eeglab, read_epochs_eeglab, read_events_eeglab
diff --git a/mne/io/eeglab/eeglab.py b/mne/io/eeglab/eeglab.py
index ef45c71..2d5154c 100644
--- a/mne/io/eeglab/eeglab.py
+++ b/mne/io/eeglab/eeglab.py
@@ -11,10 +11,10 @@ from ..utils import (_read_segments_file, _find_channels,
                      _synthesize_stim_channel)
 from ..constants import FIFF
 from ..meas_info import _empty_info, create_info
-from ..base import _BaseRaw, _check_update_montage
+from ..base import BaseRaw, _check_update_montage
 from ...utils import logger, verbose, check_version, warn
 from ...channels.montage import Montage
-from ...epochs import _BaseEpochs
+from ...epochs import BaseEpochs
 from ...event import read_events
 from ...externals.six import string_types
 
@@ -55,12 +55,14 @@ def _to_loc(ll):
     if isinstance(ll, (int, float)) or len(ll) > 0:
         return ll
     else:
-        return 0.
+        return np.nan
 
 
 def _get_info(eeg, montage, eog=()):
     """Get measurement info."""
+    from scipy import io
     info = _empty_info(sfreq=eeg.srate)
+    update_ch_names = True
 
     # add the ch_names and info['chs'][idx]['loc']
     path = None
@@ -68,21 +70,33 @@ def _get_info(eeg, montage, eog=()):
             eeg.chanlocs = [eeg.chanlocs]
 
     if len(eeg.chanlocs) > 0:
-        ch_names, pos = list(), list()
+        pos_fields = ['X', 'Y', 'Z']
+        if (isinstance(eeg.chanlocs, np.ndarray) and not isinstance(
+                eeg.chanlocs[0], io.matlab.mio5_params.mat_struct)):
+            has_pos = all(fld in eeg.chanlocs[0].dtype.names
+                          for fld in pos_fields)
+        else:
+            has_pos = all(hasattr(eeg.chanlocs[0], fld)
+                          for fld in pos_fields)
+        get_pos = has_pos and montage is None
+        pos_ch_names, ch_names, pos = list(), list(), list()
         kind = 'user_defined'
-        selection = np.arange(len(eeg.chanlocs))
-        locs_available = True
+        update_ch_names = False
         for chanloc in eeg.chanlocs:
             ch_names.append(chanloc.labels)
-            loc_x = _to_loc(chanloc.X)
-            loc_y = _to_loc(chanloc.Y)
-            loc_z = _to_loc(chanloc.Z)
-            locs = np.r_[-loc_y, loc_x, loc_z]
-            if np.unique(locs).size == 1:
-                locs_available = False
-            pos.append(locs)
-        if locs_available:
-            montage = Montage(np.array(pos), ch_names, kind, selection)
+            if get_pos:
+                loc_x = _to_loc(chanloc.X)
+                loc_y = _to_loc(chanloc.Y)
+                loc_z = _to_loc(chanloc.Z)
+                locs = np.r_[-loc_y, loc_x, loc_z]
+                if not np.any(np.isnan(locs)):
+                    pos_ch_names.append(chanloc.labels)
+                    pos.append(locs)
+        n_channels_with_pos = len(pos_ch_names)
+        info = create_info(ch_names, eeg.srate, ch_types='eeg')
+        if n_channels_with_pos > 0:
+            selection = np.arange(n_channels_with_pos)
+            montage = Montage(np.array(pos), pos_ch_names, kind, selection)
     elif isinstance(montage, string_types):
         path = op.dirname(montage)
     else:  # if eeg.chanlocs is empty, we still need default chan names
@@ -92,7 +106,7 @@ def _get_info(eeg, montage, eog=()):
         info = create_info(ch_names, eeg.srate, ch_types='eeg')
     else:
         _check_update_montage(info, montage, path=path,
-                              update_ch_names=True)
+                              update_ch_names=update_ch_names)
 
     info['buffer_size_sec'] = 1.  # reasonable default
     # update the info dict
@@ -111,7 +125,7 @@ def _get_info(eeg, montage, eog=()):
 def read_raw_eeglab(input_fname, montage=None, eog=(), event_id=None,
                     event_id_func='strip_to_integer', preload=False,
                     verbose=None, uint16_codec=None):
-    """Read an EEGLAB .set file
+    r"""Read an EEGLAB .set file.
 
     Parameters
     ----------
@@ -153,7 +167,8 @@ def read_raw_eeglab(input_fname, montage=None, eog=(), event_id=None,
         preload=False will be effective only if the data is stored in a
         separate binary file.
     verbose : bool | str | int | None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
     uint16_codec : str | None
         If your \*.set file contains non-ascii characters, sometimes reading
         it may fail and give rise to error message stating that "buffer is
@@ -181,7 +196,7 @@ def read_raw_eeglab(input_fname, montage=None, eog=(), event_id=None,
 
 def read_epochs_eeglab(input_fname, events=None, event_id=None, montage=None,
                        eog=(), verbose=None, uint16_codec=None):
-    """Reader function for EEGLAB epochs files
+    r"""Reader function for EEGLAB epochs files.
 
     Parameters
     ----------
@@ -214,7 +229,8 @@ def read_epochs_eeglab(input_fname, events=None, event_id=None, montage=None,
         If 'auto', the channel names containing ``EOG`` or ``EYE`` are used.
         Defaults to empty tuple.
     verbose : bool | str | int | None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
     uint16_codec : str | None
         If your \*.set file contains non-ascii characters, sometimes reading
         it may fail and give rise to error message stating that "buffer is
@@ -242,8 +258,8 @@ def read_epochs_eeglab(input_fname, events=None, event_id=None, montage=None,
     return epochs
 
 
-class RawEEGLAB(_BaseRaw):
-    """Raw object from EEGLAB .set file.
+class RawEEGLAB(BaseRaw):
+    r"""Raw object from EEGLAB .set file.
 
     Parameters
     ----------
@@ -283,7 +299,8 @@ class RawEEGLAB(_BaseRaw):
         a memory-mapped file which is used to store the data on the hard
         drive (slower, requires less memory).
     verbose : bool | str | int | None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
     uint16_codec : str | None
         If your \*.set file contains non-ascii characters, sometimes reading
         it may fail and give rise to error message stating that "buffer is
@@ -304,12 +321,11 @@ class RawEEGLAB(_BaseRaw):
     --------
     mne.io.Raw : Documentation of attribute and methods.
     """
+
     @verbose
     def __init__(self, input_fname, montage, eog=(), event_id=None,
                  event_id_func='strip_to_integer', preload=False,
-                 verbose=None, uint16_codec=None):
-        """Read EEGLAB .set file.
-        """
+                 verbose=None, uint16_codec=None):  # noqa: D102
         from scipy import io
         basedir = op.dirname(input_fname)
         _check_mat_struct(input_fname)
@@ -331,8 +347,8 @@ class RawEEGLAB(_BaseRaw):
         info['chs'].append(stim_chan)
         info._update_redundant()
 
-        events = _read_eeglab_events(eeg, event_id=event_id,
-                                     event_id_func=event_id_func)
+        events = read_events_eeglab(eeg, event_id=event_id,
+                                    event_id_func=event_id_func)
         self._create_event_ch(events, n_samples=eeg.pnts)
 
         # read the data
@@ -364,7 +380,16 @@ class RawEEGLAB(_BaseRaw):
                 verbose=verbose)
 
     def _create_event_ch(self, events, n_samples=None):
-        """Create the event channel"""
+        """Create the event channel."""
+        n_dropped = len(events[:, 0]) - len(set(events[:, 0]))
+        if n_dropped > 0:
+            warn(str(n_dropped) + " events will be dropped because they "
+                 "occur on the same time sample as another event. "
+                 "`mne.io.Raw` objects store events on an event channel, "
+                 "which cannot represent two events on the same sample. You "
+                 "can extract the original event structure using "
+                 "`mne.io.eeglab.read_events_eeglab`. Then, you can e.g. "
+                 "subset the extracted events for constructing epochs.")
         if n_samples is None:
             n_samples = self.last_samp - self.first_samp + 1
         events = np.array(events, int)
@@ -374,14 +399,14 @@ class RawEEGLAB(_BaseRaw):
         self._event_ch = _synthesize_stim_channel(events, n_samples)
 
     def _read_segment_file(self, data, idx, fi, start, stop, cals, mult):
-        """Read a chunk of raw data"""
+        """Read a chunk of raw data."""
         _read_segments_file(self, data, idx, fi, start, stop, cals, mult,
                             dtype=np.float32, trigger_ch=self._event_ch,
                             n_channels=self.info['nchan'] - 1)
 
 
-class EpochsEEGLAB(_BaseEpochs):
-    """Epochs from EEGLAB .set file
+class EpochsEEGLAB(BaseEpochs):
+    r"""Epochs from EEGLAB .set file.
 
     Parameters
     ----------
@@ -443,7 +468,8 @@ class EpochsEEGLAB(_BaseEpochs):
         If 'auto', the channel names containing ``EOG`` or ``EYE`` are used.
         Defaults to empty tuple.
     verbose : bool | str | int | None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
     uint16_codec : str | None
         If your \*.set file contains non-ascii characters, sometimes reading
         it may fail and give rise to error message stating that "buffer is
@@ -459,11 +485,12 @@ class EpochsEEGLAB(_BaseEpochs):
     --------
     mne.Epochs : Documentation of attribute and methods.
     """
+
     @verbose
     def __init__(self, input_fname, events=None, event_id=None, tmin=0,
-                 baseline=None,  reject=None, flat=None, reject_tmin=None,
+                 baseline=None, reject=None, flat=None, reject_tmin=None,
                  reject_tmax=None, montage=None, eog=(), verbose=None,
-                 uint16_codec=None):
+                 uint16_codec=None):  # noqa: D102
         from scipy import io
         _check_mat_struct(input_fname)
         eeg = io.loadmat(input_fname, struct_as_record=False,
@@ -480,8 +507,11 @@ class EpochsEEGLAB(_BaseEpochs):
             ev_idx = 0
             warn_multiple_events = False
             for ep in eeg.epoch:
+                if isinstance(ep.eventtype, int):
+                    ep.eventtype = str(ep.eventtype)
                 if not isinstance(ep.eventtype, string_types):
-                    event_type = '/'.join(ep.eventtype.tolist())
+                    event_type = '/'.join([str(et) for et
+                                           in ep.eventtype.tolist()])
                     event_name.append(event_type)
                     # store latency of only first event
                     event_latencies.append(eeg.event[ev_idx].latency)
@@ -528,7 +558,6 @@ class EpochsEEGLAB(_BaseEpochs):
                 raise ValueError('No matching events found for %s '
                                  '(event id %i)' % (key, val))
 
-        self._filename = input_fname
         if isinstance(eeg.data, string_types):
             basedir = op.dirname(input_fname)
             data_fname = op.join(basedir, eeg.data)
@@ -550,22 +579,82 @@ class EpochsEEGLAB(_BaseEpochs):
         super(EpochsEEGLAB, self).__init__(
             info, data, events, event_id, tmin, tmax, baseline,
             reject=reject, flat=flat, reject_tmin=reject_tmin,
-            reject_tmax=reject_tmax, add_eeg_ref=False, verbose=verbose)
+            reject_tmax=reject_tmax, filename=input_fname, verbose=verbose)
+
+        # data are preloaded but _bad_dropped is not set so we do it here:
+        self._bad_dropped = True
         logger.info('Ready.')
 
 
-def _read_eeglab_events(eeg, event_id=None, event_id_func='strip_to_integer'):
-    """Create events array from EEGLAB structure
+def read_events_eeglab(eeg, event_id=None, event_id_func='strip_to_integer',
+                       uint16_codec=None):
+    r"""Create events array from EEGLAB structure.
 
     An event array is constructed by looking up events in the
     event_id, trying to reduce them to their integer part otherwise, and
     entirely dropping them (with a warning) if this is impossible.
-    Returns a 1x3 array of zeros if no events are found."""
+    Returns a 1x3 array of zeros if no events are found.
+
+    Usually, the EEGLAB readers will automatically construct event information
+    for you. However, the reader for continuous data stores event information
+    in the stimulus channel, which can only code one event per time sample.
+    Use this function if your EEGLAB file has events happening at the
+    same time (sample) point to manually create an events array.
+
+    Parameters
+    ----------
+    eeg : str | object
+        The EEGLAB object from which events are read in.
+        If str, path to the (EEGLAB) .set file.
+        Else, the "EEG" field of a MATLAB EEGLAB structure as read in by
+        scipy.io.loadmat.
+    event_id : dict | None
+        The ids of the events to consider. If None (default), an empty dict is
+        used and ``event_id_func`` (see below) is called on every event value.
+        If dict, the keys will be mapped to trigger values on the stimulus
+        channel and only keys not in ``event_id`` will be handled by
+        ``event_id_func``. Keys are case-sensitive.
+        Example::
+
+            {'SyncStatus': 1; 'Pulse Artifact': 3}
+
+    event_id_func : None | str | callable
+        What to do for events not found in ``event_id``. Must take one ``str``
+        argument and return an ``int``. If string, must be 'strip-to-integer',
+        in which case it defaults to stripping event codes such as "D128" or
+        "S  1" of their non-integer parts and returns the integer.
+        If the event is not in the ``event_id`` and calling ``event_id_func``
+        on it results in a ``TypeError`` (e.g. if ``event_id_func`` is
+        ``None``) or a ``ValueError``, the event is dropped.
+    uint16_codec : str | None
+        If your \*.set file contains non-ascii characters, sometimes reading
+        it may fail and give rise to error message stating that "buffer is
+        too small". ``uint16_codec`` allows to specify what codec (for example:
+        'latin1' or 'utf-8') should be used when reading character arrays and
+        can therefore help you solve this problem.
+
+    Returns
+    -------
+    events : array, shape = (n_events, 3)
+        All events that were found. The first column contains the event time
+        in samples and the third column contains the event id. The center
+        column is zero.
+
+    See Also
+    --------
+    mne.find_events : Extract events from a stim channel. Note that stim
+        channels can only code for one event per time point.
+    """
     if event_id_func is 'strip_to_integer':
         event_id_func = _strip_to_integer
     if event_id is None:
         event_id = dict()
 
+    if isinstance(eeg, string_types):
+        from scipy import io
+        eeg = io.loadmat(eeg, struct_as_record=False, squeeze_me=True,
+                         uint16_codec=uint16_codec)['EEG']
+
     if isinstance(eeg.event, np.ndarray):
         types = [str(event.type) for event in eeg.event]
         latencies = [event.latency for event in eeg.event]
diff --git a/mne/io/eeglab/tests/test_eeglab.py b/mne/io/eeglab/tests/test_eeglab.py
index 94555ce..fe071fe 100644
--- a/mne/io/eeglab/tests/test_eeglab.py
+++ b/mne/io/eeglab/tests/test_eeglab.py
@@ -1,4 +1,5 @@
 # Author: Mainak Jas <mainak.jas at telecom-paristech.fr>
+#         Mikolaj Magnuski <mmagnuski at swps.edu.pl>
 #
 # License: BSD (3-clause)
 
@@ -6,14 +7,14 @@ import os.path as op
 import shutil
 
 import warnings
-from nose.tools import assert_raises, assert_equal
+from nose.tools import assert_raises, assert_equal, assert_true
 import numpy as np
-from numpy.testing import assert_array_equal
+from numpy.testing import assert_array_equal, assert_array_almost_equal
 
 from mne import write_events, read_epochs_eeglab, Epochs, find_events
 from mne.io import read_raw_eeglab
 from mne.io.tests.test_raw import _test_raw_reader
-from mne.io.eeglab.eeglab import _read_eeglab_events
+from mne.io.eeglab.eeglab import read_events_eeglab
 from mne.datasets import testing
 from mne.utils import _TempDir, run_tests_if_main, requires_version
 
@@ -30,17 +31,18 @@ warnings.simplefilter('always')  # enable b/c these tests throw warnings
 @requires_version('scipy', '0.12')
 @testing.requires_testing_data
 def test_io_set():
-    """Test importing EEGLAB .set files"""
+    """Test importing EEGLAB .set files."""
     from scipy import io
-    with warnings.catch_warnings(record=True) as w1:
+    with warnings.catch_warnings(record=True) as w:
         warnings.simplefilter('always')
         # main tests, and test missing event_id
         _test_raw_reader(read_raw_eeglab, input_fname=raw_fname,
                          montage=montage)
         _test_raw_reader(read_raw_eeglab, input_fname=raw_fname_onefile,
                          montage=montage)
-        assert_equal(len(w1), 20)
-        # f3 or preload_false and a lot for dropping events
+    for want in ('Events like', 'consist entirely', 'could not be mapped',
+                 'string preload is not supported'):
+        assert_true(any(want in str(ww.message) for ww in w))
     with warnings.catch_warnings(record=True) as w:
         warnings.simplefilter('always')
         # test finding events in continuous data
@@ -54,8 +56,8 @@ def test_io_set():
         raw3 = read_raw_eeglab(input_fname=raw_fname, montage=montage,
                                event_id=event_id)
         raw4 = read_raw_eeglab(input_fname=raw_fname, montage=montage)
-        Epochs(raw0, find_events(raw0), event_id, add_eeg_ref=False)
-        epochs = Epochs(raw1, find_events(raw1), event_id, add_eeg_ref=False)
+        Epochs(raw0, find_events(raw0), event_id)
+        epochs = Epochs(raw1, find_events(raw1), event_id)
         assert_equal(len(find_events(raw4)), 0)  # no events without event_id
         assert_equal(epochs["square"].average().nave, 80)  # 80 with
         assert_array_equal(raw0[:][0], raw1[:][0], raw2[:][0], raw3[:][0])
@@ -65,7 +67,7 @@ def test_io_set():
         raw0.filter(1, None, l_trans_bandwidth='auto', filter_length='auto',
                     phase='zero')  # test that preloading works
 
-    # test that using uin16_codec does not break stuff
+    # test that using uint16_codec does not break stuff
     raw0 = read_raw_eeglab(input_fname=raw_fname, montage=montage,
                            event_id=event_id, preload=False,
                            uint16_codec='ascii')
@@ -75,9 +77,9 @@ def test_io_set():
                      squeeze_me=True)['EEG']
     for event in eeg.event:  # old version allows integer events
         event.type = 1
-    assert_equal(_read_eeglab_events(eeg)[-1, -1], 1)
+    assert_equal(read_events_eeglab(eeg)[-1, -1], 1)
     eeg.event = eeg.event[0]  # single event
-    assert_equal(_read_eeglab_events(eeg)[-1, -1], 1)
+    assert_equal(read_events_eeglab(eeg)[-1, -1], 1)
 
     with warnings.catch_warnings(record=True) as w:
         warnings.simplefilter('always')
@@ -96,6 +98,8 @@ def test_io_set():
 
     epochs = read_epochs_eeglab(epochs_fname, epochs.events, event_id)
     assert_equal(len(epochs.events), 4)
+    assert_true(epochs.preload)
+    assert_true(epochs._bad_dropped)
     epochs = read_epochs_eeglab(epochs_fname, out_fname, event_id)
     assert_raises(ValueError, read_epochs_eeglab, epochs_fname,
                   None, event_id)
@@ -117,6 +121,27 @@ def test_io_set():
     read_raw_eeglab(input_fname=one_event_fname, montage=montage,
                     event_id=event_id, preload=True)
 
+    # test overlapping events
+    overlap_fname = op.join(temp_dir, 'test_overlap_event.set')
+    io.savemat(overlap_fname, {'EEG':
+               {'trials': eeg.trials, 'srate': eeg.srate,
+                'nbchan': eeg.nbchan, 'data': 'test_overlap_event.fdt',
+                'epoch': eeg.epoch, 'event': [eeg.event[0], eeg.event[0]],
+                'chanlocs': eeg.chanlocs, 'pnts': eeg.pnts}})
+    shutil.copyfile(op.join(base_dir, 'test_raw.fdt'),
+                    op.join(temp_dir, 'test_overlap_event.fdt'))
+    event_id = {'rt': 1, 'square': 2}
+    with warnings.catch_warnings(record=True) as w:
+        warnings.simplefilter('always')
+        raw = read_raw_eeglab(input_fname=overlap_fname,
+                              montage=montage, event_id=event_id,
+                              preload=True)
+    assert_equal(len(w), 1)  # one warning for the dropped event
+    events_stimchan = find_events(raw)
+    events_read_events_eeglab = read_events_eeglab(overlap_fname, event_id)
+    assert_true(len(events_stimchan) == 1)
+    assert_true(len(events_read_events_eeglab) == 2)
+
     # test reading file with one channel
     one_chan_fname = op.join(temp_dir, 'test_one_channel.set')
     io.savemat(one_chan_fname, {'EEG':
@@ -132,6 +157,72 @@ def test_io_set():
     # no warning for 'no events found'
     assert_equal(len(w), 0)
 
+    # test reading file with 3 channels - one without position information
+    # first, create chanlocs structured array
+    ch_names = ['F3', 'unknown', 'FPz']
+    x, y, z = [1., 2., np.nan], [4., 5., np.nan], [7., 8., np.nan]
+    dt = [('labels', 'S10'), ('X', 'f8'), ('Y', 'f8'), ('Z', 'f8')]
+    chanlocs = np.zeros((3,), dtype=dt)
+    for ind, vals in enumerate(zip(ch_names, x, y, z)):
+        for fld in range(4):
+            chanlocs[ind][dt[fld][0]] = vals[fld]
+
+    # save set file
+    one_chanpos_fname = op.join(temp_dir, 'test_chanpos.set')
+    io.savemat(one_chanpos_fname, {'EEG':
+               {'trials': eeg.trials, 'srate': eeg.srate,
+                'nbchan': 3, 'data': np.random.random((3, 3)),
+                'epoch': eeg.epoch, 'event': eeg.epoch,
+                'chanlocs': chanlocs, 'times': eeg.times[:3], 'pnts': 3}})
+    # load it
+    with warnings.catch_warnings(record=True) as w:
+        warnings.simplefilter('always')
+        raw = read_raw_eeglab(input_fname=one_chanpos_fname, preload=True)
+    # one warning because some channels are not found in Montage
+    assert_equal(len(w), 1)
+    # position should be present for first two channels
+    for i in range(2):
+        assert_array_equal(raw.info['chs'][i]['loc'][:3],
+                           np.array([-chanlocs[i]['Y'],
+                                     chanlocs[i]['X'],
+                                     chanlocs[i]['Z']]))
+    # position of the last channel should be zero
+    assert_array_equal(raw.info['chs'][-1]['loc'][:3], np.array([0., 0., 0.]))
+
+    # test reading channel names from set and positions from montage
+    with warnings.catch_warnings(record=True) as w:
+        warnings.simplefilter('always')
+        raw = read_raw_eeglab(input_fname=one_chanpos_fname, preload=True,
+                              montage=montage)
+    # one warning because some channels are not found in Montage
+    assert_equal(len(w), 1)
+
+    # when montage was passed - channel positions should be taken from there
+    correct_pos = [[-0.56705965, 0.67706631, 0.46906776], [0., 0., 0.],
+                   [0., 0.99977915, -0.02101571]]
+    for ch_ind in range(3):
+        assert_array_almost_equal(raw.info['chs'][ch_ind]['loc'][:3],
+                                  np.array(correct_pos[ch_ind]))
+
+    # test reading channel names but not positions when there is no X (only Z)
+    # field in the EEG.chanlocs structure
+    nopos_chanlocs = chanlocs[['labels', 'Z']]
+    nopos_fname = op.join(temp_dir, 'test_no_chanpos.set')
+    io.savemat(nopos_fname, {'EEG':
+               {'trials': eeg.trials, 'srate': eeg.srate, 'nbchan': 3,
+                'data': np.random.random((3, 2)), 'epoch': eeg.epoch,
+                'event': eeg.epoch, 'chanlocs': nopos_chanlocs,
+                'times': eeg.times[:2], 'pnts': 2}})
+    # load the file
+    with warnings.catch_warnings(record=True) as w:
+        warnings.simplefilter('always')
+        raw = read_raw_eeglab(input_fname=nopos_fname, preload=True)
+    # test that channel names have been loaded but not channel positions
+    for i in range(3):
+        assert_equal(raw.info['chs'][i]['ch_name'], ch_names[i])
+        assert_array_equal(raw.info['chs'][i]['loc'][:3],
+                           np.array([0., 0., 0.]))
+
     # test if .dat file raises an error
     eeg = io.loadmat(epochs_fname, struct_as_record=False,
                      squeeze_me=True)['EEG']
diff --git a/mne/io/egi/__init__.py b/mne/io/egi/__init__.py
index 59f9db1..78e402f 100644
--- a/mne/io/egi/__init__.py
+++ b/mne/io/egi/__init__.py
@@ -1,5 +1,5 @@
-"""EGI module for conversion to FIF"""
+"""EGI module for conversion to FIF."""
 
 # Author: Denis A. Engemann <denis.engemann at gmail.com>
 
-from .egi import read_raw_egi, _combine_triggers
+from .egi import read_raw_egi
diff --git a/mne/io/egi/egi.py b/mne/io/egi/egi.py
index e270dce..e248ca2 100644
--- a/mne/io/egi/egi.py
+++ b/mne/io/egi/egi.py
@@ -8,7 +8,9 @@ import time
 
 import numpy as np
 
-from ..base import _BaseRaw, _check_update_montage
+from .egimff import _read_raw_egi_mff
+from .events import _combine_triggers
+from ..base import BaseRaw, _check_update_montage
 from ..utils import _read_segments_file, _create_chs
 from ..meas_info import _empty_info
 from ..constants import FIFF
@@ -16,8 +18,7 @@ from ...utils import verbose, logger, warn
 
 
 def _read_header(fid):
-    """Read EGI binary header"""
-
+    """Read EGI binary header."""
     version = np.fromfile(fid, np.int32, 1)[0]
 
     if version > 6 & ~np.bitwise_and(version, 6):
@@ -74,7 +75,7 @@ def _read_header(fid):
 
 
 def _read_events(fid, info):
-    """Read events"""
+    """Read events."""
     events = np.zeros([info['n_events'],
                        info['n_segments'] * info['n_samples']])
     fid.seek(36 + info['n_events'] * 4, 0)  # skip header
@@ -86,47 +87,17 @@ def _read_events(fid, info):
     return events
 
 
-def _combine_triggers(data, remapping=None):
-    """Combine binary triggers"""
-    new_trigger = np.zeros(data.shape[1])
-    if data.astype(bool).sum(axis=0).max() > 1:  # ensure no overlaps
-        logger.info('    Found multiple events at the same time '
-                    'sample. Cannot create trigger channel.')
-        return
-    if remapping is None:
-        remapping = np.arange(data) + 1
-    for d, event_id in zip(data, remapping):
-        idx = d.nonzero()
-        if np.any(idx):
-            new_trigger[idx] += event_id
-    return new_trigger
-
-
 @verbose
 def read_raw_egi(input_fname, montage=None, eog=None, misc=None,
-                 include=None, exclude=None, preload=False, verbose=None):
-    """Read EGI simple binary as raw object
-
-    .. note:: The trigger channel names are based on the
-              arbitrary user dependent event codes used. However this
-              function will attempt to generate a synthetic trigger channel
-              named ``STI 014`` in accordance with the general
-              Neuromag / MNE naming pattern.
-
-              The event_id assignment equals
-              ``np.arange(n_events - n_excluded) + 1``. The resulting
-              `event_id` mapping is stored as attribute to the resulting
-              raw object but will be ignored when saving to a fiff.
-              Note. The trigger channel is artificially constructed based
-              on timestamps received by the Netstation. As a consequence,
-              triggers have only short durations.
-
-              This step will fail if events are not mutually exclusive.
+                 include=None, exclude=None, preload=False,
+                 channel_naming='E%d', verbose=None):
+    """Read EGI simple binary as raw object.
 
     Parameters
     ----------
     input_fname : str
-        Path to the raw file.
+        Path to the raw file. Files with an extension .mff are automatically
+        considered to be EGI's native MFF format files.
     montage : str | None | instance of montage
         Path or instance of montage containing electrode positions.
         If None, sensor locations are (0,0,0). See the documentation of
@@ -155,28 +126,55 @@ def read_raw_egi(input_fname, montage=None, eog=None, misc=None,
 
         ..versionadded:: 0.11
 
+    channel_naming : str
+        Channel naming convention for the data channels. Defaults to 'E%d'
+        (resulting in channel names 'E1', 'E2', 'E3'...). The effective default
+        prior to 0.14.0 was 'EEG %03d'.
+
+         ..versionadded:: 0.14.0
+
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
     raw : Instance of RawEGI
         A Raw object containing EGI data.
 
+    Notes
+    -----
+    The trigger channel names are based on the arbitrary user dependent event
+    codes used. However this function will attempt to generate a synthetic
+    trigger channel named ``STI 014`` in accordance with the general
+    Neuromag / MNE naming pattern.
+
+    The event_id assignment equals ``np.arange(n_events) + 1``. The resulting
+    ``event_id`` mapping is stored as attribute to the resulting raw object but
+    will be ignored when saving to a fiff. Note. The trigger channel is
+    artificially constructed based on timestamps received by the Netstation.
+    As a consequence, triggers have only short durations.
+
+    This step will fail if events are not mutually exclusive.
+
     See Also
     --------
     mne.io.Raw : Documentation of attribute and methods.
     """
+    if input_fname.endswith('.mff'):
+        return _read_raw_egi_mff(input_fname, montage, eog, misc, include,
+                                 exclude, preload, channel_naming, verbose)
     return RawEGI(input_fname, montage, eog, misc, include, exclude, preload,
-                  verbose)
+                  channel_naming, verbose)
 
 
-class RawEGI(_BaseRaw):
-    """Raw object from EGI simple binary file
-    """
+class RawEGI(BaseRaw):
+    """Raw object from EGI simple binary file."""
+
     @verbose
     def __init__(self, input_fname, montage=None, eog=None, misc=None,
-                 include=None, exclude=None, preload=False, verbose=None):
+                 include=None, exclude=None, preload=False,
+                 channel_naming='E%d', verbose=None):  # noqa: D102
         if eog is None:
             eog = []
         if misc is None:
@@ -193,6 +191,7 @@ class RawEGI(_BaseRaw):
 
         logger.info('    Assembling measurement info ...')
 
+        event_codes = []
         if egi_info['n_events'] > 0:
             event_codes = list(egi_info['event_codes'])
             if include is None:
@@ -243,13 +242,12 @@ class RawEGI(_BaseRaw):
             self._new_trigger = None
         info = _empty_info(egi_info['samp_rate'])
         info['buffer_size_sec'] = 1.  # reasonable default
-        info['filename'] = input_fname
         my_time = datetime.datetime(
             egi_info['year'], egi_info['month'], egi_info['day'],
             egi_info['hour'], egi_info['minute'], egi_info['second'])
         my_timestamp = time.mktime(my_time.timetuple())
-        info['meas_date'] = np.array([my_timestamp], dtype=np.float32)
-        ch_names = ['EEG %03d' % (i + 1) for i in
+        info['meas_date'] = np.array([my_timestamp, 0], dtype=np.float32)
+        ch_names = [channel_naming % (i + 1) for i in
                     range(egi_info['n_channels'])]
         ch_names.extend(list(egi_info['event_codes']))
         if self._new_trigger is not None:
@@ -260,7 +258,7 @@ class RawEGI(_BaseRaw):
         ch_kind = FIFF.FIFFV_EEG_CH
         chs = _create_chs(ch_names, cals, ch_coil, ch_kind, eog, (), (), misc)
         sti_ch_idx = [i for i, name in enumerate(ch_names) if
-                      name.startswith('STI') or len(name) == 4]
+                      name.startswith('STI') or name in event_codes]
         for idx in sti_ch_idx:
             chs[idx].update({'unit_mul': 0, 'cal': 1,
                              'kind': FIFF.FIFFV_STIM_CH,
@@ -275,7 +273,7 @@ class RawEGI(_BaseRaw):
             raw_extras=[egi_info], verbose=verbose)
 
     def _read_segment_file(self, data, idx, fi, start, stop, cals, mult):
-        """Read a segment of data from a file"""
+        """Read a segment of data from a file."""
         egi_info = self._raw_extras[fi]
         dtype = egi_info['dtype']
         n_chan_read = egi_info['n_channels'] + egi_info['n_events']
diff --git a/mne/io/egi/egimff.py b/mne/io/egi/egimff.py
new file mode 100644
index 0000000..a5fe983
--- /dev/null
+++ b/mne/io/egi/egimff.py
@@ -0,0 +1,576 @@
+"""EGI NetStation Load Function."""
+
+import datetime
+import os.path as op
+import time
+from xml.dom.minidom import parse
+import dateutil.parser
+
+import numpy as np
+
+from .events import _read_events, _combine_triggers
+from .general import (_get_signalfname, _get_ep_info, _extract, _get_blocks,
+                      _get_gains, _block_r)
+from ..base import BaseRaw, _check_update_montage
+from ..constants import FIFF
+from ..meas_info import _empty_info
+from ..utils import _create_chs
+from ...utils import verbose, logger, warn
+
+
+def _read_mff_header(filepath):
+    """Read mff header.
+
+    Parameters
+    ----------
+    filepath : str
+        Path to the file.
+    """
+    all_files = _get_signalfname(filepath)
+    eeg_file = all_files['EEG']['signal']
+    eeg_info_file = all_files['EEG']['info']
+
+    fname = op.join(filepath, eeg_file)
+    signal_blocks = _get_blocks(fname)
+    samples_block = np.sum(signal_blocks['samples_block'])
+
+    epoch_info = _get_ep_info(filepath)
+    summaryinfo = dict(eeg_fname=eeg_file,
+                       info_fname=eeg_info_file,
+                       samples_block=samples_block)
+    summaryinfo.update(signal_blocks)
+
+    # Pull header info from the summary info.
+    categfile = op.join(filepath, 'categories.xml')
+    if op.isfile(categfile):  # epochtype = 'seg'
+        n_samples = epoch_info[0]['last_samp'] - epoch_info['first_samp']
+        n_trials = len(epoch_info)
+    else:  # 'cnt'
+        n_samples = np.sum(summaryinfo['samples_block'])
+        n_trials = 1
+
+    # Add the sensor info.
+    sensor_layout_file = op.join(filepath, 'sensorLayout.xml')
+    sensor_layout_obj = parse(sensor_layout_file)
+    sensors = sensor_layout_obj.getElementsByTagName('sensor')
+    chan_type = list()
+    chan_unit = list()
+    n_chans = 0
+    numbers = list()  # used for identification
+    for sensor in sensors:
+        sensortype = int(sensor.getElementsByTagName('type')[0]
+                         .firstChild.data)
+        if sensortype in [0, 1]:
+            sn = sensor.getElementsByTagName('number')[0].firstChild.data
+            sn = sn.encode()
+            numbers.append(sn)
+            chan_type.append('eeg')
+            chan_unit.append('uV')
+            n_chans = n_chans + 1
+    if n_chans != summaryinfo['n_channels']:
+        print("Error. Should never occur.")
+
+    # Check presence of PNS data
+    if 'PNS' in all_files:
+        pns_fpath = op.join(filepath, all_files['PNS']['signal'])
+        pns_blocks = _get_blocks(pns_fpath)
+
+        pns_file = op.join(filepath, 'pnsSet.xml')
+        pns_obj = parse(pns_file)
+        sensors = pns_obj.getElementsByTagName('sensor')
+        pns_names = []
+        pns_types = []
+        pns_units = []
+        for sensor in sensors:
+            sn = sensor.getElementsByTagName('number')[0].firstChild.data
+            name = sensor.getElementsByTagName('name')[0].firstChild.data
+            unit_elem = sensor.getElementsByTagName('unit')[0].firstChild
+            unit = ''
+            if unit_elem is not None:
+                unit = unit_elem.data
+
+            if name == 'ECG':
+                ch_type = 'ecg'
+            elif 'EMG' in name:
+                ch_type = 'emg'
+            else:
+                ch_type = 'bio'
+            pns_types.append(ch_type)
+            pns_units.append(unit)
+            pns_names.append(name)
+
+        summaryinfo.update(pns_types=pns_types, pns_units=pns_units,
+                           pns_names=pns_names, n_pns_channels=len(pns_names),
+                           pns_fname=all_files['PNS']['signal'],
+                           pns_sample_blocks=pns_blocks)
+
+    info_filepath = op.join(filepath, 'info.xml')  # add with filepath
+    tags = ['mffVersion', 'recordTime']
+    version_and_date = _extract(tags, filepath=info_filepath)
+    version = ""
+    if len(version_and_date['mffVersion']):
+        version = version_and_date['mffVersion'][0]
+    summaryinfo.update(version=version,
+                       date=version_and_date['recordTime'][0],
+                       n_samples=n_samples, n_trials=n_trials,
+                       chan_type=chan_type, chan_unit=chan_unit,
+                       numbers=numbers)
+    return summaryinfo
+
+
+def _read_header(input_fname):
+    """Obtain the headers from the file package mff.
+
+    Parameters
+    ----------
+    input_fname : str
+        Path for the file
+
+    Returns
+    -------
+    info : dict
+        Main headers set.
+    """
+    mff_hdr = _read_mff_header(input_fname)
+    with open(input_fname + '/signal1.bin', 'rb') as fid:
+        version = np.fromfile(fid, np.int32, 1)[0]
+    time_n = dateutil.parser.parse(mff_hdr['date'])
+    info = dict(
+        version=version,
+        year=int(time_n.strftime('%Y')),
+        month=int(time_n.strftime('%m')),
+        day=int(time_n.strftime('%d')),
+        hour=int(time_n.strftime('%H')),
+        minute=int(time_n.strftime('%M')),
+        second=int(time_n.strftime('%S')),
+        millisecond=int(time_n.strftime('%f')),
+        gain=0,
+        bits=0,
+        value_range=0)
+    unsegmented = 1 if mff_hdr['n_trials'] == 1 else 0
+    if unsegmented:
+        info.update(dict(n_categories=0,
+                         n_segments=1,
+                         n_events=0,
+                         event_codes=[],
+                         category_names=[],
+                         category_lengths=[],
+                         pre_baseline=0))
+    else:
+        raise NotImplementedError('Only continuos files are supported')
+    info['unsegmented'] = unsegmented
+    info.update(mff_hdr)
+    return info
+
+
+def _read_locs(filepath, chs, egi_info):
+    """Read channel locations."""
+    fname = op.join(filepath, 'coordinates.xml')
+    if not op.exists(fname):
+        return chs
+    numbers = np.array(egi_info['numbers'])
+    coordinates = parse(fname)
+    sensors = coordinates.getElementsByTagName('sensor')
+    for sensor in sensors:
+        nr = sensor.getElementsByTagName('number')[0].firstChild.data.encode()
+        id = np.where(numbers == nr)[0]
+        if len(id) == 0:
+            continue
+        loc = chs[id[0]]['loc']
+        loc[0] = sensor.getElementsByTagName('x')[0].firstChild.data
+        loc[1] = sensor.getElementsByTagName('y')[0].firstChild.data
+        loc[2] = sensor.getElementsByTagName('z')[0].firstChild.data
+        loc /= 100.  # cm -> m
+    return chs
+
+
+ at verbose
+def _read_raw_egi_mff(input_fname, montage=None, eog=None, misc=None,
+                      include=None, exclude=None, preload=False,
+                      channel_naming='E%d', verbose=None):
+    """Read EGI mff binary as raw object.
+
+    .. note:: This function attempts to create a synthetic trigger channel.
+              See notes below.
+
+    Parameters
+    ----------
+    input_fname : str
+        Path to the raw file.
+    montage : str | None | instance of montage
+        Path or instance of montage containing electrode positions.
+        If None, sensor locations are (0,0,0). See the documentation of
+        :func:`mne.channels.read_montage` for more information.
+    eog : list or tuple
+        Names of channels or list of indices that should be designated
+        EOG channels. Default is None.
+    misc : list or tuple
+        Names of channels or list of indices that should be designated
+        MISC channels. Default is None.
+    include : None | list
+       The event channels to be ignored when creating the synthetic
+       trigger. Defaults to None.
+       Note. Overrides `exclude` parameter.
+    exclude : None | list
+       The event channels to be ignored when creating the synthetic
+       trigger. Defaults to None. If None, channels that have more than
+       one event and the ``sync`` and ``TREV`` channels will be
+       ignored.
+    preload : bool or str (default False)
+        Preload data into memory for data manipulation and faster indexing.
+        If True, the data will be preloaded into memory (fast, requires
+        large amount of memory). If preload is a string, preload is the
+        file name of a memory-mapped file which is used to store the data
+        on the hard drive (slower, requires less memory).
+    channel_naming : str
+        Channel naming convention for the data channels. Defaults to 'E%d'
+        (resulting in channel names 'E1', 'E2', 'E3'...). The effective default
+        prior to 0.14.0 was 'EEG %03d'.
+    verbose : bool, str, int, or None
+        If not None, override default verbose level (see mne.verbose).
+
+    Returns
+    -------
+    raw : Instance of RawMff
+        A Raw object containing EGI mff data.
+
+    Notes
+    -----
+    The trigger channel names are based on the arbitrary user dependent event
+    codes used. However this function will attempt to generate a synthetic
+    trigger channel named ``STI 014`` in accordance with the general
+    Neuromag / MNE naming pattern.
+
+    The event_id assignment equals ``np.arange(n_events) + 1``. The resulting
+    ``event_id`` mapping is stored as attribute to the resulting raw object but
+    will be ignored when saving to a fiff. Note. The trigger channel is
+    artificially constructed based on timestamps received by the Netstation.
+    As a consequence, triggers have only short durations.
+
+    This step will fail if events are not mutually exclusive.
+
+    See Also
+    --------
+    mne.io.Raw : Documentation of attribute and methods.
+
+    ..versionadded:: 0.15.0
+    """
+    return RawMff(input_fname, montage, eog, misc, include, exclude,
+                  preload, channel_naming, verbose)
+
+
+class RawMff(BaseRaw):
+    """RawMff class."""
+
+    @verbose
+    def __init__(self, input_fname, montage=None, eog=None, misc=None,
+                 include=None, exclude=None, preload=False,
+                 channel_naming='E%d', verbose=None):
+        """Init the RawMff class."""
+        logger.info('Reading EGI MFF Header from %s...' % input_fname)
+        egi_info = _read_header(input_fname)
+        if eog is None:
+            eog = []
+        if misc is None:
+            misc = np.where(np.array(
+                egi_info['chan_type']) != 'eeg')[0].tolist()
+
+        logger.info('    Reading events ...')
+        egi_events, egi_info = _read_events(input_fname, egi_info)
+        gains = _get_gains(op.join(input_fname, egi_info['info_fname']))
+        if egi_info['value_range'] != 0 and egi_info['bits'] != 0:
+            cals = [egi_info['value_range'] / 2 ** egi_info['bits'] for i
+                    in range(len(egi_info['chan_type']))]
+        else:
+            cal_scales = {'uV': 1e-6, 'V': 1}
+            cals = [cal_scales[t] for t in egi_info['chan_unit']]
+        if 'gcal' in gains:
+            cals *= gains['gcal']
+        if 'ical' in gains:
+            pass  # XXX: currently not used
+        logger.info('    Assembling measurement info ...')
+        if egi_info['n_events'] > 0:
+            event_codes = list(egi_info['event_codes'])
+            if include is None:
+                exclude_list = ['sync', 'TREV'] if exclude is None else exclude
+                exclude_inds = [i for i, k in enumerate(event_codes) if k in
+                                exclude_list]
+                more_excludes = []
+                if exclude is None:
+                    for ii, event in enumerate(egi_events):
+                        if event.sum() <= 1 and event_codes[ii]:
+                            more_excludes.append(ii)
+                if len(exclude_inds) + len(more_excludes) == len(event_codes):
+                    warn('Did not find any event code with more than one '
+                         'event.', RuntimeWarning)
+                else:
+                    exclude_inds.extend(more_excludes)
+
+                exclude_inds.sort()
+                include_ = [i for i in np.arange(egi_info['n_events']) if
+                            i not in exclude_inds]
+                include_names = [k for i, k in enumerate(event_codes)
+                                 if i in include_]
+            else:
+                include_ = [i for i, k in enumerate(event_codes)
+                            if k in include]
+                include_names = include
+
+            for kk, v in [('include', include_names), ('exclude', exclude)]:
+                if isinstance(v, list):
+                    for k in v:
+                        if k not in event_codes:
+                            raise ValueError('Could find event named "%s"' % k)
+                elif v is not None:
+                    raise ValueError('`%s` must be None or of type list' % kk)
+            logger.info('    Synthesizing trigger channel "STI 014" ...')
+            logger.info('    Excluding events {%s} ...' %
+                        ", ".join([k for i, k in enumerate(event_codes)
+                                   if i not in include_]))
+            events_ids = np.arange(len(include_)) + 1
+            self._new_trigger = _combine_triggers(egi_events[include_],
+                                                  remapping=events_ids)
+            self.event_id = dict(zip([e for e in event_codes if e in
+                                      include_names], events_ids))
+            if self._new_trigger is not None:
+                egi_events = np.vstack([egi_events, self._new_trigger])
+        else:
+            # No events
+            self.event_id = None
+            event_codes = []
+        info = _empty_info(egi_info['sfreq'])
+        info['buffer_size_sec'] = 1.  # reasonable default
+        my_time = datetime.datetime(
+            egi_info['year'], egi_info['month'], egi_info['day'],
+            egi_info['hour'], egi_info['minute'], egi_info['second'])
+        my_timestamp = time.mktime(my_time.timetuple())
+        info['meas_date'] = np.array([my_timestamp, 0], dtype=np.float32)
+        ch_names = [channel_naming % (i + 1) for i in
+                    range(egi_info['n_channels'])]
+        ch_names.extend(list(egi_info['event_codes']))
+        if hasattr(self, '_new_trigger') and self._new_trigger is not None:
+            ch_names.append('STI 014')  # channel for combined events
+        ch_coil = FIFF.FIFFV_COIL_EEG
+        ch_kind = FIFF.FIFFV_EEG_CH
+        cals = np.concatenate(
+            [cals, np.repeat(1, len(event_codes) + 1 + len(misc) + len(eog))])
+        if 'pns_names' in egi_info:
+            ch_names.extend(egi_info['pns_names'])
+            cals = np.concatenate(
+                [cals, np.repeat(1, len(egi_info['pns_names']))])
+        chs = _create_chs(ch_names, cals, ch_coil, ch_kind, eog, (), (), misc)
+        chs = _read_locs(input_fname, chs, egi_info)
+        sti_ch_idx = [i for i, name in enumerate(ch_names) if
+                      name.startswith('STI') or name in event_codes]
+        for idx in sti_ch_idx:
+            chs[idx].update({'unit_mul': 0, 'cal': cals[idx],
+                             'kind': FIFF.FIFFV_STIM_CH,
+                             'coil_type': FIFF.FIFFV_COIL_NONE,
+                             'unit': FIFF.FIFF_UNIT_NONE})
+        if 'pns_names' in egi_info:
+            for i_ch, ch_name in enumerate(egi_info['pns_names']):
+                idx = ch_names.index(ch_name)
+                ch_type = egi_info['pns_types'][i_ch]
+                ch_kind = FIFF.FIFFV_BIO_CH
+                if ch_type == 'ecg':
+                    ch_kind = FIFF.FIFFV_ECG_CH
+                elif ch_type == 'emg':
+                    ch_kind = FIFF.FIFFV_EMG_CH
+                ch_unit = FIFF.FIFF_UNIT_V
+                ch_cal = 1e-6
+                if egi_info['pns_units'][i_ch] != 'uV':
+                    ch_unit = FIFF.FIFF_UNIT_NONE
+                    ch_cal = 1.0
+
+                chs[idx].update({'cal': ch_cal, 'kind': ch_kind,
+                                 'coil_type': FIFF.FIFFV_COIL_NONE,
+                                 'unit': ch_unit})
+
+        info['chs'] = chs
+        info._update_redundant()
+        _check_update_montage(info, montage)
+        file_bin = op.join(input_fname, egi_info['eeg_fname'])
+        egi_info['egi_events'] = egi_events
+
+        if 'pns_names' in egi_info:
+            egi_info['pns_filepath'] = op.join(
+                input_fname, egi_info['pns_fname'])
+
+        self._filenames = [file_bin]
+        self._raw_extras = [egi_info]
+
+        super(RawMff, self).__init__(
+            info, preload=preload, orig_format='float', filenames=[file_bin],
+            last_samps=[egi_info['n_samples'] - 1], raw_extras=[egi_info],
+            verbose=verbose)
+
+    def _read_segment_file(self, data, idx, fi, start, stop, cals, mult):
+        """Read a chunk of data."""
+        from ..utils import _mult_cal_one
+        dtype = '<f4'  # Data read in four byte floats.
+
+        egi_info = self._raw_extras[fi]
+
+        # info about the binary file structure
+        n_channels = egi_info['n_channels']
+        samples_block = egi_info['samples_block']
+
+        # Check how many channels to read are from EEG
+        if isinstance(idx, slice):
+            chs_to_read = self.info['chs'][idx]
+        else:
+            chs_to_read = [self.info['chs'][x] for x in idx]
+        eeg_chans = [i for i, x in enumerate(chs_to_read) if x['kind'] in
+                     (FIFF.FIFFV_EEG_CH, FIFF.FIFFV_STIM_CH)]
+        pns_chans = [i for i, x in enumerate(chs_to_read) if x['kind'] in
+                     (FIFF.FIFFV_ECG_CH, FIFF.FIFFV_EMG_CH, FIFF.FIFFV_BIO_CH)]
+
+        eeg_chans = np.array(eeg_chans)
+        pns_chans = np.array(pns_chans)
+
+        if len(pns_chans):
+            if not np.max(eeg_chans) < np.max(pns_chans):
+                raise ValueError('Currently interlacing EEG and PNS channels'
+                                 'is not supported')
+        # Number of channels to be read from EEG
+        n_data1_channels = len(eeg_chans)
+
+        # Number of channels expected in the EEG binay file
+        n_eeg_channels = n_channels
+
+        # Get starting/stopping block/samples
+        block_samples_offset = np.cumsum(samples_block)
+        offset_blocks = np.sum(block_samples_offset < start)
+        offset_samples = start - (block_samples_offset[offset_blocks - 1]
+                                  if offset_blocks > 0 else 0)
+
+        samples_to_read = stop - start
+
+        # Now account for events
+        egi_events = egi_info['egi_events']
+        if len(egi_events) > 0:
+            n_eeg_channels += egi_events.shape[0]
+
+        if len(pns_chans):
+            # Split idx slice into EEG and PNS
+            if isinstance(idx, slice):
+                if idx.start is not None or idx.stop is not None:
+                    eeg_idx = slice(idx.start, n_data1_channels)
+                    pns_idx = slice(0, idx.stop - n_eeg_channels)
+                else:
+                    eeg_idx = idx
+                    pns_idx = idx
+            else:
+                eeg_idx = idx[eeg_chans]
+                pns_idx = idx[pns_chans] - n_eeg_channels
+        else:
+            eeg_idx = idx
+
+        with open(self._filenames[fi], 'rb', buffering=0) as fid:
+            # Go to starting block
+            current_block = 0
+            current_block_info = None
+            current_data_sample = 0
+            while current_block < offset_blocks:
+                this_block_info = _block_r(fid)
+                if this_block_info is not None:
+                    current_block_info = this_block_info
+                fid.seek(current_block_info['block_size'], 1)
+                current_block = current_block + 1
+
+            # Start reading samples
+            while samples_to_read > 0:
+                this_block_info = _block_r(fid)
+                if this_block_info is not None:
+                    current_block_info = this_block_info
+
+                to_read = (current_block_info['nsamples'] *
+                           current_block_info['nc'])
+                block_data = np.fromfile(fid, dtype, to_read)
+                block_data = block_data.reshape(n_channels, -1, order='C')
+
+                # Compute indexes
+                samples_read = block_data.shape[1]
+                if offset_samples > 0:
+                    # First block read, skip to the offset:
+                    block_data = block_data[:, offset_samples:]
+                    samples_read = samples_read - offset_samples
+                if samples_to_read < samples_read:
+                    # Last block to read, skip the last samples
+                    block_data = block_data[:, :samples_to_read]
+                    samples_read = samples_to_read
+
+                s_start = current_data_sample
+                s_end = s_start + samples_read
+
+                # take into account events
+                if len(egi_events) > 0:
+                    e_chs = egi_events[:, s_start:s_end]
+                    block_data = np.vstack([block_data, e_chs])
+
+                data_view = data[:n_data1_channels, s_start:s_end]
+
+                _mult_cal_one(data_view, block_data, eeg_idx,
+                              cals[:n_data1_channels], mult)
+                samples_to_read = samples_to_read - samples_read
+                current_data_sample = current_data_sample + samples_read
+
+        if 'pns_names' in egi_info:
+            # PNS Data is present
+            pns_filepath = egi_info['pns_filepath']
+            n_pns_channels = egi_info['n_pns_channels']
+            pns_info = egi_info['pns_sample_blocks']
+            n_channels = pns_info['n_channels']
+            samples_block = pns_info['samples_block']
+
+            # Get starting/stopping block/samples
+            block_samples_offset = np.cumsum(samples_block)
+            offset_blocks = np.sum(block_samples_offset < start)
+            offset_samples = start - (block_samples_offset[offset_blocks - 1]
+                                      if offset_blocks > 0 else 0)
+
+            samples_to_read = stop - start
+            with open(pns_filepath, 'rb', buffering=0) as fid:
+                # Go to starting block
+                current_block = 0
+                current_block_info = None
+                current_data_sample = 0
+                while current_block < offset_blocks:
+                    this_block_info = _block_r(fid)
+                    if this_block_info is not None:
+                        current_block_info = this_block_info
+                    fid.seek(current_block_info['block_size'], 1)
+                    current_block = current_block + 1
+
+                # Start reading samples
+                while samples_to_read > 0:
+                    this_block_info = _block_r(fid)
+                    if this_block_info is not None:
+                        current_block_info = this_block_info
+
+                    to_read = (current_block_info['nsamples'] *
+                               current_block_info['nc'])
+                    block_data = np.fromfile(fid, dtype, to_read)
+                    block_data = block_data.reshape(n_channels, -1, order='C')
+
+                    # Compute indexes
+                    samples_read = block_data.shape[1]
+                    if offset_samples > 0:
+                        # First block read, skip to the offset:
+                        block_data = block_data[:, offset_samples:]
+                        samples_read = samples_read - offset_samples
+
+                    if samples_to_read < samples_read:
+                        # Last block to read, skip the last samples
+                        block_data = block_data[:, :samples_to_read]
+                        samples_read = samples_to_read
+
+                    s_start = current_data_sample
+                    s_end = s_start + samples_read
+
+                    data_view = data[n_data1_channels:, s_start:s_end]
+                    _mult_cal_one(data_view, block_data[:n_pns_channels],
+                                  pns_idx,
+                                  cals[n_data1_channels:], mult)
+                    samples_to_read = samples_to_read - samples_read
+                    current_data_sample = current_data_sample + samples_read
diff --git a/mne/io/egi/events.py b/mne/io/egi/events.py
new file mode 100644
index 0000000..8312a7b
--- /dev/null
+++ b/mne/io/egi/events.py
@@ -0,0 +1,165 @@
+# -*- coding: utf-8 -*-
+#
+# License: BSD (3-clause)
+
+from datetime import datetime
+from glob import glob
+from os.path import basename, join, splitext
+from xml.etree.ElementTree import parse
+
+import numpy as np
+
+from ...utils import logger
+
+
+def _read_events(input_fname, info):
+    """Read events for the record.
+
+    Parameters
+    ----------
+    input_fname : str
+        The file path.
+    info : dict
+        Header info array.
+    """
+    mff_events, event_codes = _read_mff_events(input_fname, info['sfreq'])
+    info['n_events'] = len(event_codes)
+    info['event_codes'] = np.asarray(event_codes).astype('<U4')
+    events = np.zeros([info['n_events'],
+                      info['n_segments'] * info['n_samples']])
+    for n, event in enumerate(event_codes):
+        for i in mff_events[event]:
+            events[n][i] = n + 1
+    return events, info
+
+
+def _read_mff_events(filename, sfreq):
+    """Extract the events.
+
+    Parameters
+    ----------
+    filename : str
+        File path.
+    sfreq : float
+        The sampling frequency
+    """
+    orig = {}
+    for xml_file in glob(join(filename, '*.xml')):
+        xml_type = splitext(basename(xml_file))[0]
+        orig[xml_type] = _parse_xml(xml_file)
+    xml_files = orig.keys()
+    xml_events = [x for x in xml_files if x[:7] == 'Events_']
+    for item in orig['info']:
+        if 'recordTime' in item:
+            start_time = _ns2py_time(item['recordTime'])
+            break
+    markers = []
+    code = []
+    for xml in xml_events:
+        for event in orig[xml][2:]:
+            event_start = _ns2py_time(event['beginTime'])
+            start = (event_start - start_time).total_seconds()
+            if event['code'] not in code:
+                code.append(event['code'])
+            marker = {'name': event['code'],
+                      'start': start,
+                      'start_sample': int(np.fix(start * sfreq)),
+                      'end': start + float(event['duration']) / 1e9,
+                      'chan': None,
+                      }
+            markers.append(marker)
+    events_tims = dict()
+    for ev in code:
+        trig_samp = list(c['start_sample'] for n,
+                         c in enumerate(markers) if c['name'] == ev)
+        events_tims.update({ev: trig_samp})
+    return events_tims, code
+
+
+def _parse_xml(xml_file):
+    """Parse XML file."""
+    xml = parse(xml_file)
+    root = xml.getroot()
+    return _xml2list(root)
+
+
+def _xml2list(root):
+    """Parse XML item."""
+    output = []
+    for element in root:
+
+        if len(element) > 0:
+            if element[0].tag != element[-1].tag:
+                output.append(_xml2dict(element))
+            else:
+                output.append(_xml2list(element))
+
+        elif element.text:
+            text = element.text.strip()
+            if text:
+                tag = _ns(element.tag)
+                output.append({tag: text})
+
+    return output
+
+
+def _ns(s):
+    """Remove namespace, but only if there is a namespace to begin with."""
+    if '}' in s:
+        return '}'.join(s.split('}')[1:])
+    else:
+        return s
+
+
+def _xml2dict(root):
+    """Use functions instead of Class.
+
+    remove namespace based on
+    http://stackoverflow.com/questions/2148119
+    """
+    output = {}
+    if root.items():
+        output.update(dict(root.items()))
+
+    for element in root:
+        if len(element) > 0:
+            if len(element) == 1 or element[0].tag != element[1].tag:
+                one_dict = _xml2dict(element)
+            else:
+                one_dict = {_ns(element[0].tag): _xml2list(element)}
+
+            if element.items():
+                one_dict.update(dict(element.items()))
+            output.update({_ns(element.tag): one_dict})
+
+        elif element.items():
+            output.update({_ns(element.tag): dict(element.items())})
+
+        else:
+            output.update({_ns(element.tag): element.text})
+    return output
+
+
+def _ns2py_time(nstime):
+    """Parse times."""
+    nsdate = nstime[0:10]
+    nstime0 = nstime[11:26]
+    nstime00 = nsdate + " " + nstime0
+    pytime = datetime.strptime(nstime00, '%Y-%m-%d %H:%M:%S.%f')
+    return pytime
+
+
+def _combine_triggers(data, remapping=None):
+    """Combine binary triggers."""
+    new_trigger = np.zeros(data.shape[1])
+    if data.astype(bool).sum(axis=0).max() > 1:  # ensure no overlaps
+        logger.info('    Found multiple events at the same time '
+                    'sample. Cannot create trigger channel.')
+        return
+    if remapping is None:
+        remapping = np.arange(data) + 1
+    for d, event_id in zip(data, remapping):
+        idx = d.nonzero()
+        if np.any(idx):
+            new_trigger[idx] += event_id
+    return new_trigger
diff --git a/mne/io/egi/general.py b/mne/io/egi/general.py
new file mode 100644
index 0000000..c23855c
--- /dev/null
+++ b/mne/io/egi/general.py
@@ -0,0 +1,162 @@
+# -*- coding: utf-8 -*-
+#
+# License: BSD (3-clause)
+
+import os
+from xml.dom.minidom import parse
+import re
+
+import numpy as np
+
+
+def _extract(tags, filepath=None, obj=None):
+    """Extract info from XML."""
+    if obj is not None:
+        fileobj = obj
+    elif filepath is not None:
+        fileobj = parse(filepath)
+    else:
+        raise ValueError('There is not object or file to extract data')
+    infoxml = dict()
+    for tag in tags:
+        value = fileobj.getElementsByTagName(tag)
+        infoxml[tag] = []
+        for i in range(len(value)):
+            infoxml[tag].append(value[i].firstChild.data)
+    return infoxml
+
+
+def _get_gains(filepath):
+    """Parse gains."""
+    file_obj = parse(filepath)
+    objects = file_obj.getElementsByTagName('calibration')
+    gains = dict()
+    for ob in objects:
+        value = ob.getElementsByTagName('type')
+        if value[0].firstChild.data == 'GCAL':
+            data_g = _extract(['ch'], obj=ob)['ch']
+            gains.update(gcal=np.asarray(data_g, dtype=np.float64))
+        elif value[0].firstChild.data == 'ICAL':
+            data_g = _extract(['ch'], obj=ob)['ch']
+            gains.update(ical=np.asarray(data_g, dtype=np.float64))
+    return gains
+
+
+def _get_ep_info(filepath):
+    """Get epoch info."""
+    epochfile = filepath + '/epochs.xml'
+    epochlist = parse(epochfile)
+    epochs = epochlist.getElementsByTagName('epoch')
+    epoch_info = list()
+    for epoch in epochs:
+        ep_begin = int(epoch.getElementsByTagName('beginTime')[0]
+                       .firstChild.data)
+        ep_end = int(epoch.getElementsByTagName('endTime')[0].firstChild.data)
+        first_block = int(epoch.getElementsByTagName('firstBlock')[0]
+                          .firstChild.data)
+        last_block = int(epoch.getElementsByTagName('lastBlock')[0]
+                         .firstChild.data)
+        epoch_info.append({'first_samp': ep_begin,
+                           'last_samp': ep_end,
+                           'first_block': first_block,
+                           'last_block': last_block})
+    return epoch_info
+
+
+def _get_blocks(filepath):
+    """Get info from meta data blocks."""
+    binfile = os.path.join(filepath)
+    n_blocks = 0
+    samples_block = []
+    header_sizes = []
+    n_channels = []
+    sfreq = []
+    # Meta data consists of:
+    # * 1 byte of flag (1 for meta data, 0 for data)
+    # * 1 byte of header size
+    # * 1 byte of block size
+    # * 1 byte of n_channels
+    # * n_channels bytes of offsets
+    # * n_channels bytes of sigfreqs?
+    with open(binfile, 'rb') as fid:
+        fid.seek(0, 2)  # go to end of file
+        file_length = fid.tell()
+        block_size = file_length
+        fid.seek(0)
+        position = 0
+        while position < file_length:
+            block = _block_r(fid)
+            if block is None:
+                samples_block.append(samples_block[n_blocks - 1])
+                n_blocks += 1
+                fid.seek(block_size, 1)
+                position = fid.tell()
+                continue
+            block_size = block['block_size']
+            header_size = block['header_size']
+            header_sizes.append(header_size)
+            samples_block.append(block['nsamples'])
+            n_blocks += 1
+            fid.seek(block_size, 1)
+            sfreq.append(block['sfreq'])
+            n_channels.append(block['nc'])
+            position = fid.tell()
+
+    if any([n != n_channels[0] for n in n_channels]):
+        raise RuntimeError("All the blocks don't have the same amount of "
+                           "channels.")
+    if any([f != sfreq[0] for f in sfreq]):
+        raise RuntimeError("All the blocks don't have the same sampling "
+                           "frequency.")
+    samples_block = np.array(samples_block)
+    signal_blocks = dict(n_channels=n_channels[0], sfreq=sfreq[0],
+                         n_blocks=n_blocks, samples_block=samples_block,
+                         header_sizes=header_sizes)
+    return signal_blocks
+
+
+def _get_signalfname(filepath):
+    """Get filenames."""
+    listfiles = os.listdir(filepath)
+    binfiles = list(f for f in listfiles if 'signal' in f and
+                    f[-4:] == '.bin' and f[0] != '.')
+    all_files = {}
+    for binfile in binfiles:
+        bin_num_str = re.search(r'\d+', binfile).group()
+        infofile = 'info' + bin_num_str + '.xml'
+        infobjfile = os.path.join(filepath, infofile)
+        infobj = parse(infobjfile)
+        if len(infobj.getElementsByTagName('EEG')):
+            signal_type = 'EEG'
+        elif len(infobj.getElementsByTagName('PNSData')):
+            signal_type = 'PNS'
+        all_files[signal_type] = {
+            'signal': 'signal{}.bin'.format(bin_num_str),
+            'info': infofile}
+    return all_files
+
+
+def _block_r(fid):
+    """Read meta data."""
+    if np.fromfile(fid, dtype=np.dtype('i4'), count=1)[0] != 1:  # not metadata
+        return None
+    header_size = np.fromfile(fid, dtype=np.dtype('i4'), count=1)[0]
+    block_size = np.fromfile(fid, dtype=np.dtype('i4'), count=1)[0]
+    hl = int(block_size / 4)
+    nc = np.fromfile(fid, dtype=np.dtype('i4'), count=1)[0]
+    nsamples = int(hl / nc)
+    np.fromfile(fid, dtype=np.dtype('i4'), count=nc)  # sigoffset
+    sigfreq = np.fromfile(fid, dtype=np.dtype('i4'), count=nc)
+    depth = sigfreq[0] & 0xFF
+    if depth != 32:
+        raise ValueError('I do not know how to read this MFF (depth != 32)')
+    sfreq = sigfreq[0] >> 8
+    count = int(header_size / 4 - (4 + 2 * nc))
+    np.fromfile(fid, dtype=np.dtype('i4'), count=count)  # sigoffset
+    block = dict(nc=nc,
+                 hl=hl,
+                 nsamples=nsamples,
+                 block_size=block_size,
+                 header_size=header_size,
+                 sfreq=sfreq)
+    return block
diff --git a/mne/io/egi/tests/test_egi.py b/mne/io/egi/tests/test_egi.py
index 2041280..b5b3896 100644
--- a/mne/io/egi/tests/test_egi.py
+++ b/mne/io/egi/tests/test_egi.py
@@ -5,26 +5,63 @@
 
 import os.path as op
 import warnings
+import inspect
 
 import numpy as np
 from numpy.testing import assert_array_equal, assert_allclose
 from nose.tools import assert_true, assert_raises, assert_equal
+from scipy import io as sio
+
 
 from mne import find_events, pick_types
 from mne.io import read_raw_egi
 from mne.io.tests.test_raw import _test_raw_reader
-from mne.io.egi import _combine_triggers
+from mne.io.egi.egi import _combine_triggers
 from mne.utils import run_tests_if_main
+from mne.datasets.testing import data_path, requires_testing_data
 
 warnings.simplefilter('always')  # enable b/c these tests throw warnings
 
-base_dir = op.join(op.dirname(op.realpath(__file__)), 'data')
+FILE = inspect.getfile(inspect.currentframe())
+base_dir = op.join(op.dirname(op.abspath(FILE)), 'data')
 egi_fname = op.join(base_dir, 'test_egi.raw')
 egi_txt_fname = op.join(base_dir, 'test_egi.txt')
 
 
+ at requires_testing_data
+def test_io_egi_mff():
+    """Test importing EGI MFF simple binary files"""
+    egi_fname_mff = op.join(data_path(), 'EGI', 'test_egi.mff')
+    raw = read_raw_egi(egi_fname_mff, include=None)
+    assert_true('RawMff' in repr(raw))
+    include = ['DIN1', 'DIN2', 'DIN3', 'DIN4', 'DIN5', 'DIN7']
+    raw = _test_raw_reader(read_raw_egi, input_fname=egi_fname_mff,
+                           include=include, channel_naming='EEG %03d')
+
+    assert_equal('eeg' in raw, True)
+    eeg_chan = [c for c in raw.ch_names if 'EEG' in c]
+    assert_equal(len(eeg_chan), 129)
+    picks = pick_types(raw.info, eeg=True)
+    assert_equal(len(picks), 129)
+    assert_equal('STI 014' in raw.ch_names, True)
+
+    events = find_events(raw, stim_channel='STI 014')
+    assert_equal(len(events), 8)
+    assert_equal(np.unique(events[:, 1])[0], 0)
+    assert_true(np.unique(events[:, 0])[0] != 0)
+    assert_true(np.unique(events[:, 2])[0] != 0)
+
+    assert_raises(ValueError, read_raw_egi, egi_fname_mff, include=['Foo'],
+                  preload=False)
+    assert_raises(ValueError, read_raw_egi, egi_fname_mff, exclude=['Bar'],
+                  preload=False)
+    for ii, k in enumerate(include, 1):
+        assert_true(k in raw.event_id)
+        assert_true(raw.event_id[k] == ii)
+
+
 def test_io_egi():
-    """Test importing EGI simple binary files"""
+    """Test importing EGI simple binary files."""
     # test default
     with open(egi_txt_fname) as fid:
         data = np.loadtxt(fid)
@@ -51,7 +88,7 @@ def test_io_egi():
 
     assert_equal('eeg' in raw, True)
 
-    eeg_chan = [c for c in raw.ch_names if 'EEG' in c]
+    eeg_chan = [c for c in raw.ch_names if c.startswith('E')]
     assert_equal(len(eeg_chan), 256)
     picks = pick_types(raw.info, eeg=True)
     assert_equal(len(picks), 256)
@@ -78,4 +115,46 @@ def test_io_egi():
         assert_true(k in raw.event_id)
         assert_true(raw.event_id[k] == ii)
 
+
+ at requires_testing_data
+def test_io_egi_pns_mff():
+    """Test importing EGI MFF with PNS data"""
+    egi_fname_mff = op.join(data_path(), 'EGI', 'test_egi_pns.mff')
+    raw = read_raw_egi(egi_fname_mff, include=None, preload=True,
+                       verbose='error')
+    assert_true('RawMff' in repr(raw))
+    pns_chans = pick_types(raw.info, ecg=True, bio=True, emg=True)
+    assert_equal(len(pns_chans), 7)
+    names = [raw.ch_names[x] for x in pns_chans]
+    pns_names = ['Resp. Temperature'[:15],
+                 'Resp. Pressure',
+                 'ECG',
+                 'Body Position',
+                 'Resp. Effort Chest'[:15],
+                 'Resp. Effort Abdomen'[:15],
+                 'EMG-Leg']
+    _test_raw_reader(read_raw_egi, input_fname=egi_fname_mff,
+                     channel_naming='EEG %03d', verbose='error')
+    assert_equal(names, pns_names)
+    mat_names = [
+        'Resp_Temperature'[:15],
+        'Resp_Pressure',
+        'ECG',
+        'Body_Position',
+        'Resp_Effort_Chest'[:15],
+        'Resp_Effort_Abdomen'[:15],
+        'EMGLeg'
+
+    ]
+    egi_fname_mat = op.join(data_path(), 'EGI', 'test_egi_pns.mat')
+    mc = sio.loadmat(egi_fname_mat)
+    for ch_name, ch_idx, mat_name in zip(pns_names, pns_chans, mat_names):
+        print('Testing {}'.format(ch_name))
+        mc_key = [x for x in mc.keys() if mat_name in x][0]
+        cal = raw.info['chs'][ch_idx]['cal']
+        mat_data = mc[mc_key] * cal
+        raw_data = raw[ch_idx][0]
+        assert_array_equal(mat_data, raw_data)
+
+
 run_tests_if_main()
diff --git a/mne/io/fiff/__init__.py b/mne/io/fiff/__init__.py
index a90a4af..0df2dc2 100644
--- a/mne/io/fiff/__init__.py
+++ b/mne/io/fiff/__init__.py
@@ -1,2 +1,4 @@
+"""FIF raw data reader."""
+
 from .raw import Raw
 from .raw import read_raw_fif
diff --git a/mne/io/fiff/raw.py b/mne/io/fiff/raw.py
index f917c6f..cc95516 100644
--- a/mne/io/fiff/raw.py
+++ b/mne/io/fiff/raw.py
@@ -17,17 +17,18 @@ from ..open import fiff_open, _fiff_get_fid, _get_next_fname
 from ..meas_info import read_meas_info
 from ..tree import dir_tree_find
 from ..tag import read_tag, read_tag_info
-from ..proj import make_eeg_average_ref_proj, _needs_eeg_average_ref_proj
-from ..base import (_BaseRaw, _RawShell, _check_raw_compatibility,
+from ..base import (BaseRaw, _RawShell, _check_raw_compatibility,
                     _check_maxshield)
 from ..utils import _mult_cal_one
 
-from ...annotations import Annotations, _combine_annotations
+from ...annotations import Annotations, _combine_annotations, _sync_onset
+
+from ...event import AcqParserFIF
 from ...utils import check_fname, logger, verbose, warn
 
 
-class Raw(_BaseRaw):
-    """Raw data in FIF format
+class Raw(BaseRaw):
+    """Raw data in FIF format.
 
     Parameters
     ----------
@@ -48,26 +49,14 @@ class Raw(_BaseRaw):
         large amount of memory). If preload is a string, preload is the
         file name of a memory-mapped file which is used to store the data
         on the hard drive (slower, requires less memory).
-    proj : bool
-        Deprecated. Use :meth:`raw.apply_proj() <mne.io.Raw.apply_proj>`
-        instead.
-    compensation : None | int
-        Deprecated. Use :meth:`mne.io.Raw.apply_gradient_compensation`
-        instead.
-    add_eeg_ref : bool
-        If True, an EEG average reference will be added (unless one
-        already exists). The default value of True in 0.13 will change to
-        False in 0.14, and the parameter will be removed in 0.15. Use
-        :func:`mne.set_eeg_reference` instead.
-    fnames : list or str
-        Deprecated. Use :func:`mne.concatenate_raws` instead.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Attributes
     ----------
     info : dict
-        Measurement info.
+        :class:`Measurement info <mne.Info>`.
     ch_names : list of string
         List of channels' names.
     n_times : int
@@ -77,29 +66,12 @@ class Raw(_BaseRaw):
     verbose : bool, str, int, or None
         See above.
     """
+
     @verbose
     def __init__(self, fname, allow_maxshield=False, preload=False,
-                 proj=None, compensation=None, add_eeg_ref=None,
-                 fnames=None, verbose=None):
-        if not proj:
-            proj = False
-        else:
-            warn('The proj parameter has been dprecated and will be removed '
-                 'in 0.14. Use raw.apply_proj() instead.', DeprecationWarning)
-        dep = ('Supplying a list of filenames with "fnames" to the Raw class '
-               'has been deprecated and will be removed in 0.13. Use multiple '
-               'calls to read_raw_fif with the "fname" argument followed by '
-               'concatenate_raws instead.')
-        if fnames is not None:
-            warn(dep, DeprecationWarning)
-        else:
-            fnames = fname
+                 verbose=None):  # noqa: D102
+        fnames = [op.realpath(fname)]
         del fname
-        if not isinstance(fnames, list):
-            fnames = [fnames]
-        else:
-            warn(dep, DeprecationWarning)
-        fnames = [op.realpath(f) for f in fnames]
         split_fnames = []
 
         raws = []
@@ -113,14 +85,6 @@ class Raw(_BaseRaw):
                     warn('Split raw file detected but next file %s does not '
                          'exist.' % next_fname)
                     continue
-                if next_fname in fnames:
-                    # the user manually specified the split files
-                    logger.info('Note: %s is part of a split raw file. It is '
-                                'not necessary to manually specify the parts '
-                                'in this case; simply construct Raw using '
-                                'the name of the first file.' % next_fname)
-                    continue
-
                 # process this file next
                 fnames.insert(ii + 1, next_fname)
                 split_fnames.append(next_fname)
@@ -132,47 +96,44 @@ class Raw(_BaseRaw):
             [r.first_samp for r in raws], [r.last_samp for r in raws],
             [r.filename for r in raws], [r._raw_extras for r in raws],
             raws[0].orig_format, None, verbose=verbose)
-        if 'eeg' in self:
-            from ...epochs import _dep_eeg_ref
-            add_eeg_ref = _dep_eeg_ref(add_eeg_ref, True)
-
-        # combine information from each raw file to construct self
-        if add_eeg_ref and _needs_eeg_average_ref_proj(self.info):
-            eeg_ref = make_eeg_average_ref_proj(self.info, activate=False)
-            self.add_proj(eeg_ref)
 
         # combine annotations
-        self.annotations = raws[0].annotations
+        BaseRaw.annotations.fset(self, raws[0].annotations, False)
         if any([r.annotations for r in raws[1:]]):
-            first_samps = list()
-            last_samps = list()
+            first_samps = self._first_samps
+            last_samps = self._last_samps
             for r in raws:
+                annotations = _combine_annotations((self.annotations,
+                                                    r.annotations),
+                                                   last_samps, first_samps,
+                                                   r.info['sfreq'],
+                                                   self.info['meas_date'])
+                BaseRaw.annotations.fset(self, annotations, False)
                 first_samps = np.r_[first_samps, r.first_samp]
                 last_samps = np.r_[last_samps, r.last_samp]
-                self.annotations = _combine_annotations((self.annotations,
-                                                         r.annotations),
-                                                        last_samps,
-                                                        first_samps,
-                                                        r.info['sfreq'])
-        if compensation is not None:
-            warn('The "compensation" argument has been deprecated '
-                 'in favor of the "raw.apply_gradient_compensation" '
-                 'method and will be removed in 0.14',
-                 DeprecationWarning)
-            self.apply_gradient_compensation(compensation)
+
+        # Add annotations for in-data skips
+        offsets = [0] + self._raw_lengths[:-1]
+        for extra, first_samp, offset in zip(self._raw_extras,
+                                             self._first_samps, offsets):
+            for skip in extra:
+                if skip['ent'] is None:  # these are skips
+                    if self.annotations is None:
+                        self.annotations = Annotations((), (), ())
+                    start = skip['first'] - first_samp + offset
+                    stop = skip['last'] - first_samp - 1 + offset
+                    self.annotations.append(
+                        _sync_onset(self, start / self.info['sfreq']),
+                        (stop - start) / self.info['sfreq'], 'BAD_ACQ_SKIP')
         if preload:
             self._preload_data(preload)
         else:
             self.preload = False
 
-        # setup the SSP projector
-        if proj:
-            self.apply_proj()
-
     @verbose
     def _read_raw_file(self, fname, allow_maxshield, preload,
                        do_check_fname=True, verbose=None):
-        """Read in header information from a raw file"""
+        """Read in header information from a raw file."""
         logger.info('Opening raw data file %s...' % fname)
 
         if do_check_fname:
@@ -200,6 +161,8 @@ class Raw(_BaseRaw):
                     tag = read_tag(fid, pos)
                     if kind == FIFF.FIFF_MNE_BASELINE_MIN:
                         onset = tag.data
+                        if onset is None:
+                            break  # bug in 0.14 wrote empty annotations
                     elif kind == FIFF.FIFF_MNE_BASELINE_MAX:
                         duration = tag.data - onset
                     elif kind == FIFF.FIFF_COMMENT:
@@ -208,8 +171,9 @@ class Raw(_BaseRaw):
                                        description]
                     elif kind == FIFF.FIFF_MEAS_DATE:
                         orig_time = float(tag.data)
-                annotations = Annotations(onset, duration, description,
-                                          orig_time)
+                if onset is not None:
+                    annotations = Annotations(onset, duration, description,
+                                              orig_time)
 
             #   Locate the data of interest
             raw_node = dir_tree_find(meas, FIFF.FIFFB_RAW_DATA)
@@ -225,9 +189,6 @@ class Raw(_BaseRaw):
             if len(raw_node) == 1:
                 raw_node = raw_node[0]
 
-            #   Set up the output structure
-            info['filename'] = fname
-
             #   Process the directory
             directory = raw_node['directory']
             nent = raw_node['nent']
@@ -263,6 +224,8 @@ class Raw(_BaseRaw):
 
             for k in range(first, nent):
                 ent = directory[k]
+                # There can be skips in the data (e.g., if the user unclicked)
+                # an re-clicked the button
                 if ent.kind == FIFF.FIFF_DATA_SKIP:
                     tag = read_tag(fid, ent.pos)
                     nskip = int(tag.data)
@@ -352,7 +315,7 @@ class Raw(_BaseRaw):
 
     @property
     def _dtype(self):
-        """Get the dtype to use to store data from disk"""
+        """Get the dtype to use to store data from disk."""
         if self._dtype_ is not None:
             return self._dtype_
         dtype = None
@@ -378,7 +341,7 @@ class Raw(_BaseRaw):
         return dtype
 
     def _read_segment_file(self, data, idx, fi, start, stop, cals, mult):
-        """Read a segment of data from a file"""
+        """Read a segment of data from a file."""
         stop -= 1
         offset = 0
         with _fiff_get_fid(self._filenames[fi]) as fid:
@@ -427,7 +390,7 @@ class Raw(_BaseRaw):
                     break
 
     def fix_mag_coil_types(self):
-        """Fix Elekta magnetometer coil types
+        """Fix Elekta magnetometer coil types.
 
         Returns
         -------
@@ -458,17 +421,27 @@ class Raw(_BaseRaw):
         fix_mag_coil_types(self.info)
         return self
 
+    @property
+    def acqparser(self):
+        """The AcqParserFIF for the measurement info.
+
+        See Also
+        --------
+        mne.AcqParserFIF
+        """
+        if getattr(self, '_acqparser', None) is None:
+            self._acqparser = AcqParserFIF(self.info)
+        return self._acqparser
+
 
 def _check_entry(first, nent):
-    """Helper to sanity check entries"""
+    """Sanity check entries."""
     if first >= nent:
         raise IOError('Could not read data, perhaps this is a corrupt file')
 
 
-def read_raw_fif(fname, allow_maxshield=False, preload=False,
-                 proj=False, compensation=None, add_eeg_ref=None,
-                 fnames=None, verbose=None):
-    """Reader function for Raw FIF data
+def read_raw_fif(fname, allow_maxshield=False, preload=False, verbose=None):
+    """Reader function for Raw FIF data.
 
     Parameters
     ----------
@@ -489,21 +462,9 @@ def read_raw_fif(fname, allow_maxshield=False, preload=False,
         large amount of memory). If preload is a string, preload is the
         file name of a memory-mapped file which is used to store the data
         on the hard drive (slower, requires less memory).
-    proj : bool
-        Deprecated. Use :meth:`raw.apply_proj() <mne.io.Raw.apply_proj>`
-        instead.
-    compensation : None | int
-        Deprecated. Use :meth:`mne.io.Raw.apply_gradient_compensation`
-        instead.
-    add_eeg_ref : bool
-        If True, an EEG average reference will be added (unless one
-        already exists). The default value of True in 0.13 will change to
-        False in 0.14, and the parameter will be removed in 0.15. Use
-        :func:`mne.set_eeg_reference` instead.
-    fnames : list or str
-        Deprecated. Use :func:`mne.concatenate_raws` instead.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -515,5 +476,4 @@ def read_raw_fif(fname, allow_maxshield=False, preload=False,
     .. versionadded:: 0.9.0
     """
     return Raw(fname=fname, allow_maxshield=allow_maxshield,
-               preload=preload, proj=proj, compensation=compensation,
-               add_eeg_ref=add_eeg_ref, fnames=fnames, verbose=verbose)
+               preload=preload, verbose=verbose)
diff --git a/mne/io/fiff/tests/test_raw_fiff.py b/mne/io/fiff/tests/test_raw_fiff.py
index 6129266..4d88b3d 100644
--- a/mne/io/fiff/tests/test_raw_fiff.py
+++ b/mne/io/fiff/tests/test_raw_fiff.py
@@ -5,7 +5,6 @@
 
 from copy import deepcopy
 from functools import partial
-import glob
 import itertools as itt
 import os
 import os.path as op
@@ -15,6 +14,7 @@ import numpy as np
 from numpy.testing import (assert_array_almost_equal, assert_array_equal,
                            assert_allclose, assert_equal)
 from nose.tools import assert_true, assert_raises, assert_not_equal
+import pytest
 
 from mne.datasets import testing
 from mne.io.constants import FIFF
@@ -22,10 +22,10 @@ from mne.io import RawArray, concatenate_raws, read_raw_fif
 from mne.io.tests.test_raw import _test_concat, _test_raw_reader
 from mne import (concatenate_events, find_events, equalize_channels,
                  compute_proj_raw, pick_types, pick_channels, create_info)
-from mne.utils import (_TempDir, requires_pandas, slow_test,
+from mne.utils import (_TempDir, requires_pandas, object_diff,
                        requires_mne, run_subprocess, run_tests_if_main)
 from mne.externals.six.moves import zip, cPickle as pickle
-from mne.io.proc_history import _get_sss_rank
+from mne.io.proc_history import _get_rank_sss
 from mne.io.pick import _picks_by_type
 from mne.annotations import Annotations
 from mne.tests.common import assert_naming
@@ -36,6 +36,7 @@ testing_path = testing.data_path(download=False)
 data_dir = op.join(testing_path, 'MEG', 'sample')
 fif_fname = op.join(data_dir, 'sample_audvis_trunc_raw.fif')
 ms_fname = op.join(testing_path, 'SSS', 'test_move_anon_raw.fif')
+skip_fname = op.join(testing_path, 'misc', 'intervalrecording_raw.fif')
 
 base_dir = op.join(op.dirname(__file__), '..', '..', 'tests', 'data')
 test_fif_fname = op.join(base_dir, 'test_raw.fif')
@@ -49,11 +50,19 @@ hp_fname = op.join(base_dir, 'test_chpi_raw_hp.txt')
 hp_fif_fname = op.join(base_dir, 'test_chpi_raw_sss.fif')
 
 
+ at testing.requires_testing_data
+def test_acq_skip():
+    """Test treatment of acquisition skips."""
+    raw = read_raw_fif(skip_fname)
+    assert_equal(len(raw.times), 17000)
+    assert_equal(len(raw.annotations), 3)  # there are 3 skips
+
+
 def test_fix_types():
     """Test fixing of channel types."""
     for fname, change in ((hp_fif_fname, True), (test_fif_fname, False),
                           (ctf_fname, False)):
-        raw = read_raw_fif(fname, add_eeg_ref=False)
+        raw = read_raw_fif(fname)
         mag_picks = pick_types(raw.info, meg='mag')
         other_picks = np.setdiff1d(np.arange(len(raw.ch_names)), mag_picks)
         # we don't actually have any files suffering from this problem, so
@@ -77,25 +86,25 @@ def test_concat():
     """Test RawFIF concatenation."""
     # we trim the file to save lots of memory and some time
     tempdir = _TempDir()
-    raw = read_raw_fif(test_fif_fname, add_eeg_ref=False)
-    raw.crop(0, 2., copy=False)
+    raw = read_raw_fif(test_fif_fname)
+    raw.crop(0, 2.)
     test_name = op.join(tempdir, 'test_raw.fif')
     raw.save(test_name)
     # now run the standard test
-    _test_concat(partial(read_raw_fif, add_eeg_ref=False), test_name)
+    _test_concat(partial(read_raw_fif), test_name)
 
 
 @testing.requires_testing_data
 def test_hash_raw():
     """Test hashing raw objects."""
-    raw = read_raw_fif(fif_fname, add_eeg_ref=False)
+    raw = read_raw_fif(fif_fname)
     assert_raises(RuntimeError, raw.__hash__)
-    raw = read_raw_fif(fif_fname, add_eeg_ref=False).crop(0, 0.5, copy=False)
+    raw = read_raw_fif(fif_fname).crop(0, 0.5)
     raw_size = raw._size
     raw.load_data()
     raw_load_size = raw._size
     assert_true(raw_size < raw_load_size)
-    raw_2 = read_raw_fif(fif_fname, add_eeg_ref=False).crop(0, 0.5, copy=False)
+    raw_2 = read_raw_fif(fif_fname).crop(0, 0.5)
     raw_2.load_data()
     assert_equal(hash(raw), hash(raw_2))
     # do NOT use assert_equal here, failing output is terrible
@@ -110,7 +119,7 @@ def test_maxshield():
     """Test maxshield warning."""
     with warnings.catch_warnings(record=True) as w:
         warnings.simplefilter('always')
-        read_raw_fif(ms_fname, allow_maxshield=True, add_eeg_ref=False)
+        read_raw_fif(ms_fname, allow_maxshield=True)
     assert_equal(len(w), 1)
     assert_true('test_raw_fiff.py' in w[0].filename)
 
@@ -119,7 +128,7 @@ def test_maxshield():
 def test_subject_info():
     """Test reading subject information."""
     tempdir = _TempDir()
-    raw = read_raw_fif(fif_fname, add_eeg_ref=False).crop(0, 1, copy=False)
+    raw = read_raw_fif(fif_fname).crop(0, 1)
     assert_true(raw.info['subject_info'] is None)
     # fake some subject data
     keys = ['id', 'his_id', 'last_name', 'first_name', 'birthday', 'sex',
@@ -131,7 +140,7 @@ def test_subject_info():
     raw.info['subject_info'] = subject_info
     out_fname = op.join(tempdir, 'test_subj_info_raw.fif')
     raw.save(out_fname, overwrite=True)
-    raw_read = read_raw_fif(out_fname, add_eeg_ref=False)
+    raw_read = read_raw_fif(out_fname)
     for key in keys:
         assert_equal(subject_info[key], raw_read.info['subject_info'][key])
     assert_equal(raw.info['meas_date'], raw_read.info['meas_date'])
@@ -140,14 +149,14 @@ def test_subject_info():
 @testing.requires_testing_data
 def test_copy_append():
     """Test raw copying and appending combinations."""
-    raw = read_raw_fif(fif_fname, preload=True, add_eeg_ref=False).copy()
-    raw_full = read_raw_fif(fif_fname, add_eeg_ref=False)
+    raw = read_raw_fif(fif_fname, preload=True).copy()
+    raw_full = read_raw_fif(fif_fname)
     raw_full.append(raw)
     data = raw_full[:, :][0]
     assert_equal(data.shape[1], 2 * raw._data.shape[1])
 
 
- at slow_test
+ at pytest.mark.slowtest
 @testing.requires_testing_data
 def test_rank_estimation():
     """Test raw rank estimation."""
@@ -156,36 +165,24 @@ def test_rank_estimation():
         ['norm', dict(mag=1e11, grad=1e9, eeg=1e5)]
     )
     for fname, scalings in iter_tests:
-        raw = read_raw_fif(fname, add_eeg_ref=False)
+        raw = read_raw_fif(fname).crop(0, 4.).load_data()
         (_, picks_meg), (_, picks_eeg) = _picks_by_type(raw.info,
                                                         meg_combined=True)
         n_meg = len(picks_meg)
         n_eeg = len(picks_eeg)
 
-        raw = read_raw_fif(fname, preload=True, add_eeg_ref=False)
-        if 'proc_history' not in raw.info:
+        if len(raw.info['proc_history']) == 0:
             expected_rank = n_meg + n_eeg
         else:
-            mf = raw.info['proc_history'][0]['max_info']
-            expected_rank = _get_sss_rank(mf) + n_eeg
+            expected_rank = _get_rank_sss(raw.info) + n_eeg
         assert_array_equal(raw.estimate_rank(scalings=scalings), expected_rank)
-
         assert_array_equal(raw.estimate_rank(picks=picks_eeg,
-                                             scalings=scalings),
-                           n_eeg)
-
-        raw = read_raw_fif(fname, preload=False, add_eeg_ref=False)
+                                             scalings=scalings), n_eeg)
         if 'sss' in fname:
-            tstart, tstop = 0., 30.
             raw.add_proj(compute_proj_raw(raw))
-            raw.apply_proj()
-        else:
-            tstart, tstop = 10., 20.
-
         raw.apply_proj()
         n_proj = len(raw.info['projs'])
-
-        assert_array_equal(raw.estimate_rank(tstart=tstart, tstop=tstop,
+        assert_array_equal(raw.estimate_rank(tstart=0, tstop=3.,
                                              scalings=scalings),
                            expected_rank - (1 if 'sss' in fname else n_proj))
 
@@ -198,8 +195,7 @@ def test_output_formats():
     tols = [1e-4, 1e-7, 1e-7, 1e-15]
 
     # let's fake a raw file with different formats
-    raw = read_raw_fif(test_fif_fname,
-                       add_eeg_ref=False).crop(0, 1, copy=False)
+    raw = read_raw_fif(test_fif_fname).crop(0, 1)
 
     temp_file = op.join(tempdir, 'raw.fif')
     for ii, (fmt, tol) in enumerate(zip(formats, tols)):
@@ -207,7 +203,7 @@ def test_output_formats():
         if ii > 0:
             assert_raises(IOError, raw.save, temp_file, fmt=fmt)
         raw.save(temp_file, fmt=fmt, overwrite=True)
-        raw2 = read_raw_fif(temp_file, add_eeg_ref=False)
+        raw2 = read_raw_fif(temp_file)
         raw2_data = raw2[:, :][0]
         assert_allclose(raw2_data, raw[:, :][0], rtol=tol, atol=1e-25)
         assert_equal(raw2.orig_format, fmt)
@@ -221,13 +217,13 @@ def _compare_combo(raw, new, times, n_times):
         assert_allclose(orig, new[:, ti][0])
 
 
- at slow_test
+ at pytest.mark.slowtest
 @testing.requires_testing_data
 def test_multiple_files():
     """Test loading multiple files simultaneously."""
     # split file
     tempdir = _TempDir()
-    raw = read_raw_fif(fif_fname, add_eeg_ref=False).crop(0, 10, copy=False)
+    raw = read_raw_fif(fif_fname).crop(0, 10)
     raw.load_data()
     raw.load_data()  # test no operation
     split_size = 3.  # in seconds
@@ -244,7 +240,7 @@ def test_multiple_files():
     for ri in range(len(tmins) - 1, -1, -1):
         fname = op.join(tempdir, 'test_raw_split-%d_raw.fif' % ri)
         raw.save(fname, tmin=tmins[ri], tmax=tmaxs[ri])
-        raws[ri] = read_raw_fif(fname, add_eeg_ref=False)
+        raws[ri] = read_raw_fif(fname)
         assert_equal(len(raws[ri].times),
                      int(round((tmaxs[ri] - tmins[ri]) *
                                raw.info['sfreq'])) + 1)  # + 1 b/c inclusive
@@ -260,7 +256,7 @@ def test_multiple_files():
     assert_equal(raw.first_samp, all_raw_1.first_samp)
     assert_equal(raw.last_samp, all_raw_1.last_samp)
     assert_allclose(raw[:, :][0], all_raw_1[:, :][0])
-    raws[0] = read_raw_fif(fname, add_eeg_ref=False)
+    raws[0] = read_raw_fif(fname)
     all_raw_2 = concatenate_raws(raws, preload=True)
     assert_allclose(raw[:, :][0], all_raw_2[:, :][0])
 
@@ -271,81 +267,69 @@ def test_multiple_files():
     assert_array_equal(events1, events3)
 
     # test various methods of combining files
-    raw = read_raw_fif(fif_fname, preload=True, add_eeg_ref=False)
+    raw = read_raw_fif(fif_fname, preload=True)
     n_times = raw.n_times
     # make sure that all our data match
     times = list(range(0, 2 * n_times, 999))
     # add potentially problematic points
     times.extend([n_times - 1, n_times, 2 * n_times - 1])
 
-    raw_combo0 = concatenate_raws([read_raw_fif(f, add_eeg_ref=False)
+    raw_combo0 = concatenate_raws([read_raw_fif(f)
                                    for f in [fif_fname, fif_fname]],
                                   preload=True)
     _compare_combo(raw, raw_combo0, times, n_times)
-    raw_combo = concatenate_raws([read_raw_fif(f, add_eeg_ref=False)
+    raw_combo = concatenate_raws([read_raw_fif(f)
                                   for f in [fif_fname, fif_fname]],
                                  preload=False)
     _compare_combo(raw, raw_combo, times, n_times)
-    raw_combo = concatenate_raws([read_raw_fif(f, add_eeg_ref=False)
+    raw_combo = concatenate_raws([read_raw_fif(f)
                                   for f in [fif_fname, fif_fname]],
                                  preload='memmap8.dat')
     _compare_combo(raw, raw_combo, times, n_times)
-    with warnings.catch_warnings(record=True):  # deprecated
-        assert_raises(ValueError, read_raw_fif, [fif_fname, ctf_fname])
-        assert_raises(ValueError, read_raw_fif,
-                      [fif_fname, fif_bad_marked_fname])
     assert_equal(raw[:, :][0].shape[1] * 2, raw_combo0[:, :][0].shape[1])
     assert_equal(raw_combo0[:, :][0].shape[1], raw_combo0.n_times)
 
     # with all data preloaded, result should be preloaded
-    raw_combo = read_raw_fif(fif_fname, preload=True, add_eeg_ref=False)
-    raw_combo.append(read_raw_fif(fif_fname, preload=True, add_eeg_ref=False))
+    raw_combo = read_raw_fif(fif_fname, preload=True)
+    raw_combo.append(read_raw_fif(fif_fname, preload=True))
     assert_true(raw_combo.preload is True)
     assert_equal(raw_combo.n_times, raw_combo._data.shape[1])
     _compare_combo(raw, raw_combo, times, n_times)
 
     # with any data not preloaded, don't set result as preloaded
-    raw_combo = concatenate_raws([read_raw_fif(fif_fname, preload=True,
-                                               add_eeg_ref=False),
-                                  read_raw_fif(fif_fname, preload=False,
-                                               add_eeg_ref=False)])
+    raw_combo = concatenate_raws([read_raw_fif(fif_fname, preload=True),
+                                  read_raw_fif(fif_fname, preload=False)])
     assert_true(raw_combo.preload is False)
     assert_array_equal(find_events(raw_combo, stim_channel='STI 014'),
                        find_events(raw_combo0, stim_channel='STI 014'))
     _compare_combo(raw, raw_combo, times, n_times)
 
     # user should be able to force data to be preloaded upon concat
-    raw_combo = concatenate_raws([read_raw_fif(fif_fname, preload=False,
-                                               add_eeg_ref=False),
-                                  read_raw_fif(fif_fname, preload=True,
-                                               add_eeg_ref=False)],
+    raw_combo = concatenate_raws([read_raw_fif(fif_fname, preload=False),
+                                  read_raw_fif(fif_fname, preload=True)],
                                  preload=True)
     assert_true(raw_combo.preload is True)
     _compare_combo(raw, raw_combo, times, n_times)
 
-    raw_combo = concatenate_raws([read_raw_fif(fif_fname, preload=False,
-                                               add_eeg_ref=False),
-                                  read_raw_fif(fif_fname, preload=True,
-                                               add_eeg_ref=False)],
+    raw_combo = concatenate_raws([read_raw_fif(fif_fname, preload=False),
+                                  read_raw_fif(fif_fname, preload=True)],
                                  preload='memmap3.dat')
     _compare_combo(raw, raw_combo, times, n_times)
 
     raw_combo = concatenate_raws([
-        read_raw_fif(fif_fname, preload=True, add_eeg_ref=False),
-        read_raw_fif(fif_fname, preload=True, add_eeg_ref=False)],
-        preload='memmap4.dat')
+        read_raw_fif(fif_fname, preload=True),
+        read_raw_fif(fif_fname, preload=True)], preload='memmap4.dat')
     _compare_combo(raw, raw_combo, times, n_times)
 
     raw_combo = concatenate_raws([
-        read_raw_fif(fif_fname, preload=False, add_eeg_ref=False),
-        read_raw_fif(fif_fname, preload=False, add_eeg_ref=False)],
-        preload='memmap5.dat')
+        read_raw_fif(fif_fname, preload=False),
+        read_raw_fif(fif_fname, preload=False)], preload='memmap5.dat')
     _compare_combo(raw, raw_combo, times, n_times)
 
     # verify that combining raws with different projectors throws an exception
     raw.add_proj([], remove_existing=True)
     assert_raises(ValueError, raw.append,
-                  read_raw_fif(fif_fname, preload=True, add_eeg_ref=False))
+                  read_raw_fif(fif_fname, preload=True))
 
     # now test event treatment for concatenated raw files
     events = [find_events(raw, stim_channel='STI 014'),
@@ -365,37 +349,32 @@ def test_multiple_files():
 def test_split_files():
     """Test writing and reading of split raw files."""
     tempdir = _TempDir()
-    raw_1 = read_raw_fif(fif_fname, preload=True, add_eeg_ref=False)
+    raw_1 = read_raw_fif(fif_fname, preload=True)
     # Test a very close corner case
-    raw_crop = raw_1.copy().crop(0, 1., copy=False)
+    raw_crop = raw_1.copy().crop(0, 1.)
 
     assert_allclose(raw_1.info['buffer_size_sec'], 10., atol=1e-2)  # samp rate
     split_fname = op.join(tempdir, 'split_raw.fif')
+    raw_1.annotations = Annotations([2.], [5.5], 'test')
     raw_1.save(split_fname, buffer_size_sec=1.0, split_size='10MB')
 
-    raw_2 = read_raw_fif(split_fname, add_eeg_ref=False)
+    raw_2 = read_raw_fif(split_fname)
     assert_allclose(raw_2.info['buffer_size_sec'], 1., atol=1e-2)  # samp rate
+    assert_array_equal(raw_1.annotations.onset, raw_2.annotations.onset)
+    assert_array_equal(raw_1.annotations.duration, raw_2.annotations.duration)
+    assert_array_equal(raw_1.annotations.description,
+                       raw_2.annotations.description)
     data_1, times_1 = raw_1[:, :]
     data_2, times_2 = raw_2[:, :]
     assert_array_equal(data_1, data_2)
     assert_array_equal(times_1, times_2)
 
-    # test the case where the silly user specifies the split files
-    fnames = [split_fname]
-    fnames.extend(sorted(glob.glob(op.join(tempdir, 'split_raw-*.fif'))))
-    with warnings.catch_warnings(record=True):
-        warnings.simplefilter('always')
-        raw_2 = read_raw_fif(fnames, add_eeg_ref=False)  # deprecated list
-    data_2, times_2 = raw_2[:, :]
-    assert_array_equal(data_1, data_2)
-    assert_array_equal(times_1, times_2)
-
     # test the case where we only end up with one buffer to write
     # (GH#3210). These tests rely on writing meas info and annotations
     # taking up a certain number of bytes, so if we change those functions
     # somehow, the numbers below for e.g. split_size might need to be
     # adjusted.
-    raw_crop = raw_1.copy().crop(0, 5, copy=False)
+    raw_crop = raw_1.copy().crop(0, 5)
     try:
         raw_crop.save(split_fname, split_size='1MB',  # too small a size
                       buffer_size_sec=1., overwrite=True)
@@ -411,21 +390,21 @@ def test_split_files():
     # at a time so we hit GH#3210 if we aren't careful
     raw_crop.save(split_fname, split_size='4.5MB',
                   buffer_size_sec=1., overwrite=True)
-    raw_read = read_raw_fif(split_fname, add_eeg_ref=False)
+    raw_read = read_raw_fif(split_fname)
     assert_allclose(raw_crop[:][0], raw_read[:][0], atol=1e-20)
 
     # Check our buffer arithmetic
 
     # 1 buffer required
-    raw_crop = raw_1.copy().crop(0, 1, copy=False)
+    raw_crop = raw_1.copy().crop(0, 1)
     raw_crop.save(split_fname, buffer_size_sec=1., overwrite=True)
-    raw_read = read_raw_fif(split_fname, add_eeg_ref=False)
+    raw_read = read_raw_fif(split_fname)
     assert_equal(len(raw_read._raw_extras[0]), 1)
     assert_equal(raw_read._raw_extras[0][0]['nsamp'], 301)
     assert_allclose(raw_crop[:][0], raw_read[:][0])
     # 2 buffers required
     raw_crop.save(split_fname, buffer_size_sec=0.5, overwrite=True)
-    raw_read = read_raw_fif(split_fname, add_eeg_ref=False)
+    raw_read = read_raw_fif(split_fname)
     assert_equal(len(raw_read._raw_extras[0]), 2)
     assert_equal(raw_read._raw_extras[0][0]['nsamp'], 151)
     assert_equal(raw_read._raw_extras[0][1]['nsamp'], 150)
@@ -434,7 +413,7 @@ def test_split_files():
     raw_crop.save(split_fname,
                   buffer_size_sec=1. - 1.01 / raw_crop.info['sfreq'],
                   overwrite=True)
-    raw_read = read_raw_fif(split_fname, add_eeg_ref=False)
+    raw_read = read_raw_fif(split_fname)
     assert_equal(len(raw_read._raw_extras[0]), 2)
     assert_equal(raw_read._raw_extras[0][0]['nsamp'], 300)
     assert_equal(raw_read._raw_extras[0][1]['nsamp'], 1)
@@ -442,7 +421,7 @@ def test_split_files():
     raw_crop.save(split_fname,
                   buffer_size_sec=1. - 2.01 / raw_crop.info['sfreq'],
                   overwrite=True)
-    raw_read = read_raw_fif(split_fname, add_eeg_ref=False)
+    raw_read = read_raw_fif(split_fname)
     assert_equal(len(raw_read._raw_extras[0]), 2)
     assert_equal(raw_read._raw_extras[0][0]['nsamp'], 299)
     assert_equal(raw_read._raw_extras[0][1]['nsamp'], 2)
@@ -453,9 +432,9 @@ def test_load_bad_channels():
     """Test reading/writing of bad channels."""
     tempdir = _TempDir()
     # Load correctly marked file (manually done in mne_process_raw)
-    raw_marked = read_raw_fif(fif_bad_marked_fname, add_eeg_ref=False)
+    raw_marked = read_raw_fif(fif_bad_marked_fname)
     correct_bads = raw_marked.info['bads']
-    raw = read_raw_fif(test_fif_fname, add_eeg_ref=False)
+    raw = read_raw_fif(test_fif_fname)
     # Make sure it starts clean
     assert_array_equal(raw.info['bads'], [])
 
@@ -463,7 +442,7 @@ def test_load_bad_channels():
     raw.load_bad_channels(bad_file_works)
     # Write it out, read it in, and check
     raw.save(op.join(tempdir, 'foo_raw.fif'))
-    raw_new = read_raw_fif(op.join(tempdir, 'foo_raw.fif'), add_eeg_ref=False)
+    raw_new = read_raw_fif(op.join(tempdir, 'foo_raw.fif'))
     assert_equal(correct_bads, raw_new.info['bads'])
     # Reset it
     raw.info['bads'] = []
@@ -479,18 +458,17 @@ def test_load_bad_channels():
         assert_equal(n_found, 1)  # there could be other irrelevant errors
         # write it out, read it in, and check
         raw.save(op.join(tempdir, 'foo_raw.fif'), overwrite=True)
-        raw_new = read_raw_fif(op.join(tempdir, 'foo_raw.fif'),
-                               add_eeg_ref=False)
+        raw_new = read_raw_fif(op.join(tempdir, 'foo_raw.fif'))
         assert_equal(correct_bads, raw_new.info['bads'])
 
     # Check that bad channels are cleared
     raw.load_bad_channels(None)
     raw.save(op.join(tempdir, 'foo_raw.fif'), overwrite=True)
-    raw_new = read_raw_fif(op.join(tempdir, 'foo_raw.fif'), add_eeg_ref=False)
+    raw_new = read_raw_fif(op.join(tempdir, 'foo_raw.fif'))
     assert_equal([], raw_new.info['bads'])
 
 
- at slow_test
+ at pytest.mark.slowtest
 @testing.requires_testing_data
 def test_io_raw():
     """Test IO for raw data (Neuromag + CTF + gz)."""
@@ -498,18 +476,18 @@ def test_io_raw():
     tempdir = _TempDir()
     # test unicode io
     for chars in [b'\xc3\xa4\xc3\xb6\xc3\xa9', b'a']:
-        with read_raw_fif(fif_fname, add_eeg_ref=False) as r:
+        with read_raw_fif(fif_fname) as r:
             assert_true('Raw' in repr(r))
             assert_true(op.basename(fif_fname) in repr(r))
             desc1 = r.info['description'] = chars.decode('utf-8')
             temp_file = op.join(tempdir, 'raw.fif')
             r.save(temp_file, overwrite=True)
-            with read_raw_fif(temp_file, add_eeg_ref=False) as r2:
+            with read_raw_fif(temp_file) as r2:
                 desc2 = r2.info['description']
             assert_equal(desc1, desc2)
 
     # Let's construct a simple test for IO first
-    raw = read_raw_fif(fif_fname, add_eeg_ref=False).crop(0, 3.5, copy=False)
+    raw = read_raw_fif(fif_fname).crop(0, 3.5)
     raw.load_data()
     # put in some data that we know the values of
     data = rng.randn(raw._data.shape[0], raw._data.shape[1])
@@ -518,7 +496,7 @@ def test_io_raw():
     fname = op.join(tempdir, 'test_copy_raw.fif')
     raw.save(fname, buffer_size_sec=1.0)
     # read it in, make sure the whole thing matches
-    raw = read_raw_fif(fname, add_eeg_ref=False)
+    raw = read_raw_fif(fname)
     assert_allclose(data, raw[:, :][0], rtol=1e-6, atol=1e-20)
     # let's read portions across the 1-sec tag boundary, too
     inds = raw.time_as_index([1.75, 2.25])
@@ -530,7 +508,7 @@ def test_io_raw():
     fnames_out = ['raw.fif', 'raw.fif.gz', 'raw.fif']
     for fname_in, fname_out in zip(fnames_in, fnames_out):
         fname_out = op.join(tempdir, fname_out)
-        raw = read_raw_fif(fname_in, add_eeg_ref=False)
+        raw = read_raw_fif(fname_in)
 
         nchan = raw.info['nchan']
         ch_names = raw.info['ch_names']
@@ -552,7 +530,7 @@ def test_io_raw():
         # Writing with drop_small_buffer True
         raw.save(fname_out, picks, tmin=0, tmax=4, buffer_size_sec=3,
                  drop_small_buffer=True, overwrite=True)
-        raw2 = read_raw_fif(fname_out, add_eeg_ref=False)
+        raw2 = read_raw_fif(fname_out)
 
         sel = pick_channels(raw2.ch_names, meg_ch_names)
         data2, times2 = raw2[sel, :]
@@ -564,7 +542,7 @@ def test_io_raw():
         if fname_in == fif_fname or fname_in == fif_fname + '.gz':
             assert_equal(len(raw.info['dig']), 146)
 
-        raw2 = read_raw_fif(fname_out, add_eeg_ref=False)
+        raw2 = read_raw_fif(fname_out)
 
         sel = pick_channels(raw2.ch_names, meg_ch_names)
         data2, times2 = raw2[sel, :]
@@ -602,7 +580,7 @@ def test_io_raw():
         warnings.simplefilter("always")
         raw_badname = op.join(tempdir, 'test-bad-name.fif.gz')
         raw.save(raw_badname)
-        read_raw_fif(raw_badname, add_eeg_ref=False)
+        read_raw_fif(raw_badname)
     assert_naming(w, 'test_raw_fiff.py', 2)
 
 
@@ -613,7 +591,7 @@ def test_io_complex():
     tempdir = _TempDir()
     dtypes = [np.complex64, np.complex128]
 
-    raw = _test_raw_reader(partial(read_raw_fif, add_eeg_ref=False),
+    raw = _test_raw_reader(partial(read_raw_fif),
                            fname=fif_fname)
     picks = np.arange(5)
     start, stop = raw.time_as_index([0, 5])
@@ -635,13 +613,12 @@ def test_io_complex():
             # warning gets thrown on every instance b/c simplifilter('always')
             assert_equal(len(w), 1)
 
-        raw2 = read_raw_fif(op.join(tempdir, 'raw.fif'), add_eeg_ref=False)
+        raw2 = read_raw_fif(op.join(tempdir, 'raw.fif'))
         raw2_data, _ = raw2[picks, :]
         n_samp = raw2_data.shape[1]
         assert_allclose(raw2_data[:, :n_samp], raw_cp._data[picks, :n_samp])
         # with preloading
-        raw2 = read_raw_fif(op.join(tempdir, 'raw.fif'), preload=True,
-                            add_eeg_ref=False)
+        raw2 = read_raw_fif(op.join(tempdir, 'raw.fif'), preload=True)
         raw2_data, _ = raw2[picks, :]
         n_samp = raw2_data.shape[1]
         assert_allclose(raw2_data[:, :n_samp], raw_cp._data[picks, :n_samp])
@@ -651,7 +628,7 @@ def test_io_complex():
 def test_getitem():
     """Test getitem/indexing of Raw."""
     for preload in [False, True, 'memmap.dat']:
-        raw = read_raw_fif(fif_fname, preload=preload, add_eeg_ref=False)
+        raw = read_raw_fif(fif_fname, preload=preload)
         data, times = raw[0, :]
         data1, times1 = raw[0]
         assert_array_equal(data, data1)
@@ -674,7 +651,7 @@ def test_proj():
     """Test SSP proj operations."""
     tempdir = _TempDir()
     for proj in [True, False]:
-        raw = read_raw_fif(fif_fname, preload=False, add_eeg_ref=False)
+        raw = read_raw_fif(fif_fname, preload=False)
         if proj:
             raw.apply_proj()
         assert_true(all(p['active'] == proj for p in raw.info['projs']))
@@ -702,26 +679,23 @@ def test_proj():
 
     # test apply_proj() with and without preload
     for preload in [True, False]:
-        raw = read_raw_fif(fif_fname, preload=preload, proj=False,
-                           add_eeg_ref=False)
+        raw = read_raw_fif(fif_fname, preload=preload)
         data, times = raw[:, 0:2]
         raw.apply_proj()
         data_proj_1 = np.dot(raw._projector, data)
 
         # load the file again without proj
-        raw = read_raw_fif(fif_fname, preload=preload, proj=False,
-                           add_eeg_ref=False)
+        raw = read_raw_fif(fif_fname, preload=preload)
 
         # write the file with proj. activated, make sure proj has been applied
         raw.save(op.join(tempdir, 'raw.fif'), proj=True, overwrite=True)
-        raw2 = read_raw_fif(op.join(tempdir, 'raw.fif'), proj=False,
-                            add_eeg_ref=False)
+        raw2 = read_raw_fif(op.join(tempdir, 'raw.fif'))
         data_proj_2, _ = raw2[:, 0:2]
         assert_allclose(data_proj_1, data_proj_2)
         assert_true(all(p['active'] for p in raw2.info['projs']))
 
         # read orig file with proj. active
-        raw2 = read_raw_fif(fif_fname, preload=preload, add_eeg_ref=False)
+        raw2 = read_raw_fif(fif_fname, preload=preload)
         raw2.apply_proj()
         data_proj_2, _ = raw2[:, 0:2]
         assert_allclose(data_proj_1, data_proj_2)
@@ -735,14 +709,13 @@ def test_proj():
 
     tempdir = _TempDir()
     out_fname = op.join(tempdir, 'test_raw.fif')
-    raw = read_raw_fif(test_fif_fname, preload=True,
-                       add_eeg_ref=False).crop(0, 0.002, copy=False)
+    raw = read_raw_fif(test_fif_fname, preload=True).crop(0, 0.002)
     raw.pick_types(meg=False, eeg=True)
     raw.info['projs'] = [raw.info['projs'][-1]]
     raw._data.fill(0)
     raw._data[-1] = 1.
     raw.save(out_fname)
-    raw = read_raw_fif(out_fname, preload=False, add_eeg_ref=False)
+    raw = read_raw_fif(out_fname, preload=False)
     raw.apply_proj()
     assert_allclose(raw[:, :][0][:1], raw[0, :][0])
 
@@ -753,7 +726,7 @@ def test_preload_modify():
     tempdir = _TempDir()
     rng = np.random.RandomState(0)
     for preload in [False, True, 'memmap.dat']:
-        raw = read_raw_fif(fif_fname, preload=preload, add_eeg_ref=False)
+        raw = read_raw_fif(fif_fname, preload=preload)
 
         nsamp = raw.last_samp - raw.first_samp + 1
         picks = pick_types(raw.info, meg='grad', exclude='bads')
@@ -762,26 +735,26 @@ def test_preload_modify():
 
         try:
             raw[picks, :nsamp // 2] = data
-        except RuntimeError as err:
+        except RuntimeError:
             if not preload:
                 continue
             else:
-                raise err
+                raise
 
         tmp_fname = op.join(tempdir, 'raw.fif')
         raw.save(tmp_fname, overwrite=True)
 
-        raw_new = read_raw_fif(tmp_fname, add_eeg_ref=False)
-        data_new, _ = raw_new[picks, :nsamp / 2]
+        raw_new = read_raw_fif(tmp_fname)
+        data_new, _ = raw_new[picks, :nsamp // 2]
 
         assert_allclose(data, data_new)
 
 
- at slow_test
+ at pytest.mark.slowtest
 @testing.requires_testing_data
 def test_filter():
     """Test filtering (FIR and IIR) and Raw.apply_function interface."""
-    raw = read_raw_fif(fif_fname, add_eeg_ref=False).crop(0, 7, copy=False)
+    raw = read_raw_fif(fif_fname).crop(0, 7)
     raw.load_data()
     sig_dec_notch = 12
     sig_dec_notch_fit = 12
@@ -791,7 +764,7 @@ def test_filter():
     trans = 2.0
     filter_params = dict(picks=picks, filter_length='auto',
                          h_trans_bandwidth=trans, l_trans_bandwidth=trans,
-                         phase='zero', fir_window='hamming')
+                         fir_design='firwin')
     raw_lp = raw.copy().filter(None, 8.0, **filter_params)
     raw_hp = raw.copy().filter(16.0, None, **filter_params)
     raw_bp = raw.copy().filter(8.0 + trans, 16.0 - trans, **filter_params)
@@ -841,7 +814,7 @@ def test_filter():
         raw_bs = raw.copy().filter(60.0 + trans, 60.0 - trans, **filter_params)
         data_bs, _ = raw_bs[picks, :]
         raw_notch = raw.copy().notch_filter(
-            60.0, picks=picks, n_jobs=2, method='fir', filter_length='auto',
+            60.0, picks=picks, n_jobs=2, method='fir',
             trans_bandwidth=2 * trans)
     data_notch, _ = raw_notch[picks, :]
     assert_array_almost_equal(data_bs, data_notch, sig_dec_notch)
@@ -869,7 +842,7 @@ def test_filter():
         assert_true(raw.info['lowpass'] is None)
         assert_true(raw.info['highpass'] is None)
         kwargs = dict(l_trans_bandwidth=20, h_trans_bandwidth=20,
-                      filter_length='auto', phase='zero', fir_window='hann')
+                      filter_length='auto', phase='zero', fir_design='firwin')
         raw_filt = raw.copy().filter(l_freq, h_freq, picks=np.arange(1),
                                      **kwargs)
         assert_true(raw.info['lowpass'] is None)
@@ -902,9 +875,7 @@ def test_filter_picks():
         picks['meg'] = ch_type if ch_type in ('mag', 'grad') else False
         picks['fnirs'] = ch_type if ch_type in ('hbo', 'hbr') else False
         raw_ = raw.copy().pick_types(**picks)
-        raw_.filter(10, 30, l_trans_bandwidth='auto',
-                    h_trans_bandwidth='auto', filter_length='auto',
-                    phase='zero', fir_window='hamming')
+        raw_.filter(10, 30, fir_design='firwin')
 
     # -- Error if no data channel
     for ch_type in ('misc', 'stim'):
@@ -917,7 +888,7 @@ def test_filter_picks():
 def test_crop():
     """Test cropping raw files."""
     # split a concatenated file to test a difficult case
-    raw = concatenate_raws([read_raw_fif(f, add_eeg_ref=False)
+    raw = concatenate_raws([read_raw_fif(f)
                             for f in [fif_fname, fif_fname]])
     split_size = 10.  # in seconds
     sfreq = raw.info['sfreq']
@@ -931,7 +902,7 @@ def test_crop():
     tmins /= sfreq
     raws = [None] * len(tmins)
     for ri, (tmin, tmax) in enumerate(zip(tmins, tmaxs)):
-        raws[ri] = raw.copy().crop(tmin, tmax, copy=False)
+        raws[ri] = raw.copy().crop(tmin, tmax)
     all_raw_2 = concatenate_raws(raws, preload=False)
     assert_equal(raw.first_samp, all_raw_2.first_samp)
     assert_equal(raw.last_samp, all_raw_2.last_samp)
@@ -945,11 +916,11 @@ def test_crop():
     # going in revere order so the last fname is the first file (need it later)
     raws = [None] * len(tmins)
     for ri, (tmin, tmax) in enumerate(zip(tmins, tmaxs)):
-        raws[ri] = raw.copy().crop(tmin, tmax, copy=False)
+        raws[ri] = raw.copy().crop(tmin, tmax)
     # test concatenation of split file
     all_raw_1 = concatenate_raws(raws, preload=False)
 
-    all_raw_2 = raw.copy().crop(0, None, copy=False)
+    all_raw_2 = raw.copy().crop(0, None)
     for ar in [all_raw_1, all_raw_2]:
         assert_equal(raw.first_samp, ar.first_samp)
         assert_equal(raw.last_samp, ar.last_samp)
@@ -960,7 +931,7 @@ def test_crop():
     info = create_info(1, 1000)
     raw = RawArray(data, info)
     for tmin in range(0, 1001, 100):
-        raw1 = raw.copy().crop(tmin=tmin, tmax=tmin + 2, copy=False)
+        raw1 = raw.copy().crop(tmin=tmin, tmax=tmin + 2)
         assert_equal(raw1[:][0].shape, (1, 2001))
 
 
@@ -968,7 +939,7 @@ def test_crop():
 def test_resample():
     """Test resample (with I/O and multiple files)."""
     tempdir = _TempDir()
-    raw = read_raw_fif(fif_fname, add_eeg_ref=False).crop(0, 3, copy=False)
+    raw = read_raw_fif(fif_fname).crop(0, 3)
     raw.load_data()
     raw_resamp = raw.copy()
     sfreq = raw.info['sfreq']
@@ -977,7 +948,7 @@ def test_resample():
     assert_equal(raw_resamp.n_times, len(raw_resamp.times))
     raw_resamp.save(op.join(tempdir, 'raw_resamp-raw.fif'))
     raw_resamp = read_raw_fif(op.join(tempdir, 'raw_resamp-raw.fif'),
-                              preload=True, add_eeg_ref=False)
+                              preload=True)
     assert_equal(sfreq, raw_resamp.info['sfreq'] / 2)
     assert_equal(raw.n_times, raw_resamp.n_times / 2)
     assert_equal(raw_resamp._data.shape[1], raw_resamp.n_times)
@@ -1056,11 +1027,22 @@ def test_resample():
         assert_true(len(w) == 1)
 
     # test resampling events: this should no longer give a warning
-    stim = [0, 1, 1, 0, 0, 1, 1, 0]
-    raw = RawArray([stim], create_info(1, len(stim), ['stim']))
+    # we often have first_samp != 0, include it here too
+    stim = [0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0]
+    # test is on half the sfreq, but should work with trickier ones too
+    o_sfreq, sfreq_ratio = len(stim), 0.5
+    n_sfreq = o_sfreq * sfreq_ratio
+    first_samp = len(stim) // 2
+    raw = RawArray([stim], create_info(1, o_sfreq, ['stim']),
+                   first_samp=first_samp)
     events = find_events(raw)
-    raw, events = raw.resample(4., events=events, npad='auto')
-    assert_equal(events, np.array([[0, 0, 1], [2, 0, 1]]))
+    raw, events = raw.resample(n_sfreq, events=events, npad='auto')
+    n_fsamp = int(first_samp * sfreq_ratio)  # how it's calc'd in base.py
+    # NB np.round used for rounding event times, which has 0.5 as corner case:
+    # https://docs.scipy.org/doc/numpy/reference/generated/numpy.around.html
+    assert_equal(events,
+                 np.array([[np.round(1 * sfreq_ratio) + n_fsamp, 0, 1],
+                           [np.round(10 * sfreq_ratio) + n_fsamp, 0, 1]]))
 
     # test copy flag
     stim = [1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0]
@@ -1081,14 +1063,14 @@ def test_resample():
 @testing.requires_testing_data
 def test_hilbert():
     """Test computation of analytic signal using hilbert."""
-    raw = read_raw_fif(fif_fname, preload=True, add_eeg_ref=False)
+    raw = read_raw_fif(fif_fname, preload=True)
     picks_meg = pick_types(raw.info, meg=True, exclude='bads')
     picks = picks_meg[:4]
 
     raw_filt = raw.copy()
     raw_filt.filter(10, 20, picks=picks, l_trans_bandwidth='auto',
                     h_trans_bandwidth='auto', filter_length='auto',
-                    phase='zero', fir_window='blackman')
+                    phase='zero', fir_window='blackman', fir_design='firwin')
     raw_filt_2 = raw_filt.copy()
 
     raw2 = raw.copy()
@@ -1113,7 +1095,7 @@ def test_hilbert():
 @testing.requires_testing_data
 def test_raw_copy():
     """Test Raw copy."""
-    raw = read_raw_fif(fif_fname, preload=True, add_eeg_ref=False)
+    raw = read_raw_fif(fif_fname, preload=True)
     data, _ = raw[:, :]
     copied = raw.copy()
     copied_data, _ = copied[:, :]
@@ -1121,7 +1103,7 @@ def test_raw_copy():
     assert_equal(sorted(raw.__dict__.keys()),
                  sorted(copied.__dict__.keys()))
 
-    raw = read_raw_fif(fif_fname, preload=False, add_eeg_ref=False)
+    raw = read_raw_fif(fif_fname, preload=False)
     data, _ = raw[:, :]
     copied = raw.copy()
     copied_data, _ = copied[:, :]
@@ -1133,7 +1115,7 @@ def test_raw_copy():
 @requires_pandas
 def test_to_data_frame():
     """Test raw Pandas exporter."""
-    raw = read_raw_fif(test_fif_fname, preload=True, add_eeg_ref=False)
+    raw = read_raw_fif(test_fif_fname, preload=True)
     _, times = raw[0, :10]
     df = raw.to_data_frame()
     assert_true((df.columns == raw.ch_names).all())
@@ -1147,9 +1129,8 @@ def test_to_data_frame():
 def test_add_channels():
     """Test raw splitting / re-appending channel types."""
     rng = np.random.RandomState(0)
-    raw = read_raw_fif(test_fif_fname,
-                       add_eeg_ref=False).crop(0, 1, copy=False).load_data()
-    raw_nopre = read_raw_fif(test_fif_fname, preload=False, add_eeg_ref=False)
+    raw = read_raw_fif(test_fif_fname).crop(0, 1).load_data()
+    raw_nopre = read_raw_fif(test_fif_fname, preload=False)
     raw_eeg_meg = raw.copy().pick_types(meg=True, eeg=True)
     raw_eeg = raw.copy().pick_types(meg=False, eeg=True)
     raw_meg = raw.copy().pick_types(meg=True, eeg=False)
@@ -1175,12 +1156,12 @@ def test_add_channels():
     assert_raises(ValueError, raw_meg.copy().add_channels, [raw_arr])
     raw_meg.copy().add_channels([raw_arr], force_update_info=True)
     # Make sure that values didn't get overwritten
-    assert_true(raw_arr.info['dev_head_t'] is orig_head_t)
+    assert_equal(object_diff(raw_arr.info['dev_head_t'], orig_head_t), '')
 
     # Now test errors
     raw_badsf = raw_eeg.copy()
     raw_badsf.info['sfreq'] = 3.1415927
-    raw_eeg.crop(.5, copy=False)
+    raw_eeg.crop(.5)
 
     assert_raises(AssertionError, raw_meg.add_channels, [raw_nopre])
     assert_raises(RuntimeError, raw_meg.add_channels, [raw_badsf])
@@ -1193,10 +1174,10 @@ def test_add_channels():
 def test_save():
     """Test saving raw."""
     tempdir = _TempDir()
-    raw = read_raw_fif(fif_fname, preload=False, add_eeg_ref=False)
+    raw = read_raw_fif(fif_fname, preload=False)
     # can't write over file being read
     assert_raises(ValueError, raw.save, fif_fname)
-    raw = read_raw_fif(fif_fname, preload=True, add_eeg_ref=False)
+    raw = read_raw_fif(fif_fname, preload=True)
     # can't overwrite file without overwrite=True
     assert_raises(IOError, raw.save, fif_fname)
 
@@ -1207,8 +1188,7 @@ def test_save():
     raw.annotations = annot
     new_fname = op.join(op.abspath(op.curdir), 'break-raw.fif')
     raw.save(op.join(tempdir, new_fname), overwrite=True)
-    new_raw = read_raw_fif(op.join(tempdir, new_fname), preload=False,
-                           add_eeg_ref=False)
+    new_raw = read_raw_fif(op.join(tempdir, new_fname), preload=False)
     assert_raises(ValueError, new_raw.save, new_fname)
     assert_array_equal(annot.onset, new_raw.annotations.onset)
     assert_array_equal(annot.duration, new_raw.annotations.duration)
@@ -1227,8 +1207,8 @@ def test_save():
     onsets = raw.annotations.onset
     durations = raw.annotations.duration
     # 2*5s clips combined with annotations at 2.5s + 2s clip, annotation at 1s
-    assert_array_almost_equal([2.5, 7.5, 11.], onsets, decimal=2)
-    assert_array_almost_equal([2., 2.5, 1.], durations, decimal=2)
+    assert_array_almost_equal([2.5, 7.5, 11.], onsets[:3], decimal=2)
+    assert_array_almost_equal([2., 2.5, 1.], durations[:3], decimal=2)
 
     # test annotation clipping
     annot = Annotations([0., raw.times[-1]], [2., 2.], 'test',
@@ -1239,8 +1219,7 @@ def test_save():
     assert_array_almost_equal(raw.annotations.duration, [1., 1.], decimal=3)
 
     # make sure we can overwrite the file we loaded when preload=True
-    new_raw = read_raw_fif(op.join(tempdir, new_fname), preload=True,
-                           add_eeg_ref=False)
+    new_raw = read_raw_fif(op.join(tempdir, new_fname), preload=True)
     new_raw.save(op.join(tempdir, new_fname), overwrite=True)
     os.remove(new_fname)
 
@@ -1249,15 +1228,14 @@ def test_save():
 def test_with_statement():
     """Test with statement."""
     for preload in [True, False]:
-        with read_raw_fif(fif_fname, preload=preload,
-                          add_eeg_ref=False) as raw_:
+        with read_raw_fif(fif_fname, preload=preload) as raw_:
             print(raw_)
 
 
 def test_compensation_raw():
     """Test Raw compensation."""
     tempdir = _TempDir()
-    raw_3 = read_raw_fif(ctf_comp_fname, add_eeg_ref=False)
+    raw_3 = read_raw_fif(ctf_comp_fname)
     assert_equal(raw_3.compensation_grade, 3)
     data_3, times = raw_3[:, :]
 
@@ -1271,15 +1249,6 @@ def test_compensation_raw():
         data_new, times_new = raw_3_new[:, :]
         assert_array_equal(times, times_new)
         assert_array_equal(data_3, data_new)
-        # deprecated way
-        preload = True if ii == 0 else False
-        raw_3_new = read_raw_fif(ctf_comp_fname, compensation=3,
-                                 preload=preload, verbose='error',
-                                 add_eeg_ref=False)
-        assert_equal(raw_3_new.compensation_grade, 3)
-        data_new, times_new = raw_3_new[:, :]
-        assert_array_equal(times, times_new)
-        assert_array_equal(data_3, data_new)
 
     # change to grade 0
     raw_0 = raw_3.copy().apply_gradient_compensation(0)
@@ -1293,8 +1262,7 @@ def test_compensation_raw():
     data_1, times_new = raw_1[:, :]
     assert_array_equal(times, times_new)
     assert_true(np.mean(np.abs(data_1 - data_3)) > 1e-12)
-    assert_raises(ValueError, read_raw_fif, ctf_comp_fname, compensation=33,
-                  verbose='error', add_eeg_ref=False)
+    assert_raises(ValueError, raw_1.apply_gradient_compensation, 33)
     raw_bad = raw_0.copy()
     raw_bad.add_proj(compute_proj_raw(raw_0, duration=0.5, verbose='error'))
     raw_bad.apply_proj()
@@ -1307,16 +1275,6 @@ def test_compensation_raw():
     assert_array_equal(times, times_new)
     assert_true(np.mean(np.abs(data_1_new - data_3)) > 1e-12)
     assert_allclose(data_1, data_1_new, **tols)
-    # deprecated way
-    for preload in (True, False):
-        raw_1_new = read_raw_fif(ctf_comp_fname, compensation=1,
-                                 verbose='error', preload=preload,
-                                 add_eeg_ref=False)
-        assert_equal(raw_1_new.compensation_grade, 1)
-        data_1_new, times_new = raw_1_new[:, :]
-        assert_array_equal(times, times_new)
-        assert_true(np.mean(np.abs(data_1_new - data_3)) > 1e-12)
-        assert_allclose(data_1, data_1_new, **tols)
     # change back
     raw_3_new = raw_1.copy().apply_gradient_compensation(3)
     data_3_new, times_new = raw_3_new[:, :]
@@ -1341,7 +1299,7 @@ def test_compensation_raw():
     temp_file = op.join(tempdir, 'raw.fif')
     raw_3.save(temp_file, overwrite=True)
     for preload in (True, False):
-        raw_read = read_raw_fif(temp_file, preload=preload, add_eeg_ref=False)
+        raw_read = read_raw_fif(temp_file, preload=preload)
         assert_equal(raw_read.compensation_grade, 3)
         data_read, times_new = raw_read[:, :]
         assert_array_equal(times, times_new)
@@ -1362,8 +1320,7 @@ def test_compensation_raw():
     looser_tols = dict(rtol=1e-6, atol=1e-18)
     raw_1.save(temp_file, overwrite=True)
     for preload in (True, False):
-        raw_read = read_raw_fif(temp_file, preload=preload, verbose=True,
-                                add_eeg_ref=False)
+        raw_read = read_raw_fif(temp_file, preload=preload, verbose=True)
         assert_equal(raw_read.compensation_grade, 1)
         data_read, times_new = raw_read[:, :]
         assert_array_equal(times, times_new)
@@ -1384,12 +1341,11 @@ def test_compensation_raw_mne():
         cmd = ['mne_process_raw', '--raw', fname, '--save', tmp_fname,
                '--grad', str(grad), '--projoff', '--filteroff']
         run_subprocess(cmd)
-        return read_raw_fif(tmp_fname, preload=True, add_eeg_ref=False)
+        return read_raw_fif(tmp_fname, preload=True)
 
     for grad in [0, 2, 3]:
-        with warnings.catch_warnings(record=True):  # deprecated param
-            raw_py = read_raw_fif(ctf_comp_fname, preload=True,
-                                  compensation=grad, add_eeg_ref=False)
+        raw_py = read_raw_fif(ctf_comp_fname, preload=True)
+        raw_py.apply_gradient_compensation(grad)
         raw_c = compensate_mne(ctf_comp_fname, grad)
         assert_allclose(raw_py._data, raw_c._data, rtol=1e-6, atol=1e-17)
         assert_equal(raw_py.info['nchan'], raw_c.info['nchan'])
@@ -1404,7 +1360,7 @@ def test_compensation_raw_mne():
 @testing.requires_testing_data
 def test_drop_channels_mixin():
     """Test channels-dropping functionality."""
-    raw = read_raw_fif(fif_fname, preload=True, add_eeg_ref=False)
+    raw = read_raw_fif(fif_fname, preload=True)
     drop_ch = raw.ch_names[:3]
     ch_names = raw.ch_names[3:]
 
@@ -1425,7 +1381,7 @@ def test_pick_channels_mixin():
     """Test channel-picking functionality."""
     # preload is True
 
-    raw = read_raw_fif(fif_fname, preload=True, add_eeg_ref=False)
+    raw = read_raw_fif(fif_fname, preload=True)
     ch_names = raw.ch_names[:3]
 
     ch_names_orig = raw.ch_names
@@ -1440,7 +1396,7 @@ def test_pick_channels_mixin():
     assert_equal(len(ch_names), raw._data.shape[0])
     assert_raises(ValueError, raw.pick_channels, ch_names[0])
 
-    raw = read_raw_fif(fif_fname, preload=False, add_eeg_ref=False)
+    raw = read_raw_fif(fif_fname, preload=False)
     assert_raises(RuntimeError, raw.pick_channels, ch_names)
     assert_raises(RuntimeError, raw.drop_channels, ch_names)
 
@@ -1448,7 +1404,7 @@ def test_pick_channels_mixin():
 @testing.requires_testing_data
 def test_equalize_channels():
     """Test equalization of channels."""
-    raw1 = read_raw_fif(fif_fname, preload=True, add_eeg_ref=False)
+    raw1 = read_raw_fif(fif_fname, preload=True)
 
     raw2 = raw1.copy()
     ch_names = raw1.ch_names[2:]
diff --git a/mne/io/kit/__init__.py b/mne/io/kit/__init__.py
index a3d74cc..b0626e3 100644
--- a/mne/io/kit/__init__.py
+++ b/mne/io/kit/__init__.py
@@ -1,4 +1,4 @@
-"""KIT module for conversion to FIF"""
+"""KIT module for reading raw data."""
 
 # Author: Teon Brooks <teon.brooks at gmail.com>
 #
diff --git a/mne/io/kit/constants.py b/mne/io/kit/constants.py
index 168d28e..90ff60d 100644
--- a/mne/io/kit/constants.py
+++ b/mne/io/kit/constants.py
@@ -1,10 +1,11 @@
-"""KIT constants"""
+"""KIT constants."""
 
-# Author: Teon Brooks <teon.brooks at gmail.com>
+# Authors: Teon Brooks <teon.brooks at gmail.com>
+#          Christian Brodbeck <christianbrodbeck at nyu.edu>
 #
 # License: BSD (3-clause)
 
-from ..constants import Bunch
+from ..constants import Bunch, FIFF
 
 
 KIT = Bunch()
@@ -13,101 +14,127 @@ KIT = Bunch()
 KIT.SHORT = 2
 KIT.INT = 4
 KIT.DOUBLE = 8
-KIT.STRING = 128
 
-# pointer locations
-KIT.AMPLIFIER_INFO = 112
-KIT.BASIC_INFO = 16
-KIT.CHAN_SENS = 80
-KIT.RAW_OFFSET = 144
-KIT.AVE_OFFSET = 160
-KIT.SAMPLE_INFO = 128
-KIT.MRK_INFO = 192
-KIT.CHAN_LOC_OFFSET = 64
-
-# parameters
-KIT.VOLTAGE_RANGE = 5.
+# channel parameters
 KIT.CALIB_FACTOR = 1.0  # mne_manual p.272
 KIT.RANGE = 1.  # mne_manual p.272
 KIT.UNIT_MUL = 0  # default is 0 mne_manual p.273
-
-# gain: 0:x1, 1:x2, 2:x5, 3:x10, 4:x20, 5:x50, 6:x100, 7:x200
 KIT.GAINS = [1, 2, 5, 10, 20, 50, 100, 200]
-# BEF options: 0:THROUGH, 1:50Hz, 2:60Hz, 3:50Hz
-KIT.BEFS = [0, 50, 60, 50]
+
+KIT.HPFS = {
+    1: (0, 1, 3, 3),
+    2: (0, 1, 3, 0.3),
+    3: (0, 0.03, 0.1, 0.3, 1, 3, 10, 30),
+    4: (0, 1, 3, 10, 30, 100, 200, 500),
+}
+KIT.LPFS = {
+    1: (10, 20, 50, 100, 200, 500, 1000, 2000),
+    3: (10, 20, 50, 100, 200, 500, 1000, 10000),
+    4: (10, 30, 100, 300, 1000, 2000, 5000, 10000),
+}
+KIT.BEFS = {
+    1: (0, 50, 60, 60),
+    3: (0, 60, 50, 50),
+}
+
+# Map FLL-Type to filter options (high, low, band)
+KIT.FLL_SETTINGS = {
+    0: (1, 1, 1),  # Hanger Type #1
+    10: (1, 1, 1),  # Hanger Type #2
+    20: (1, 1, 1),  # Hanger Type #2
+    50: (2, 1, 1),  # Hanger Type #3
+    60: (2, 1, 1),  # Hanger Type #3
+    100: (3, 3, 3),  # Low Band Kapper Type
+    200: (4, 4, 3),  # High Band Kapper Type
+}
+
+# channel types
+KIT.CHANNEL_MAGNETOMETER = 1
+KIT.CHANNEL_MAGNETOMETER_REFERENCE = 0x101
+KIT.CHANNEL_AXIAL_GRADIOMETER = 2
+KIT.CHANNEL_AXIAL_GRADIOMETER_REFERENCE = 0x102
+KIT.CHANNEL_PLANAR_GRADIOMETER = 3
+KIT.CHANNEL_PLANAR_GRADIOMETER_REFERENCE = 0x103
+KIT.CHANNEL_2ND_ORDER_AXIAL_GRADIOMETER = 4
+KIT.CHANNEL_2ND_ORDER_AXIAL_GRADIOMETER_REFERENCE = 0x104
+KIT.CHANNEL_TRIGGER = -1
+KIT.CHANNEL_EEG = -2
+KIT.CHANNEL_ECG = -3
+KIT.CHANNEL_ETC = -4
+KIT.CHANNEL_NULL = 0
+KIT.CHANNELS_MEG = (
+    KIT.CHANNEL_MAGNETOMETER,
+    KIT.CHANNEL_MAGNETOMETER_REFERENCE,
+    KIT.CHANNEL_AXIAL_GRADIOMETER,
+    KIT.CHANNEL_AXIAL_GRADIOMETER_REFERENCE,
+    KIT.CHANNEL_PLANAR_GRADIOMETER,
+    KIT.CHANNEL_PLANAR_GRADIOMETER_REFERENCE,
+    KIT.CHANNEL_2ND_ORDER_AXIAL_GRADIOMETER,
+    KIT.CHANNEL_2ND_ORDER_AXIAL_GRADIOMETER_REFERENCE,
+)
+KIT.CHANNELS_REFERENCE = (
+    KIT.CHANNEL_MAGNETOMETER_REFERENCE,
+    KIT.CHANNEL_AXIAL_GRADIOMETER_REFERENCE,
+    KIT.CHANNEL_PLANAR_GRADIOMETER_REFERENCE,
+    KIT.CHANNEL_2ND_ORDER_AXIAL_GRADIOMETER_REFERENCE,
+)
+KIT.CHANNELS_MISC = (
+    KIT.CHANNEL_TRIGGER,
+    KIT.CHANNEL_EEG,
+    KIT.CHANNEL_ECG,
+    KIT.CHANNEL_ETC,
+)
+KIT.CH_TO_FIFF_COIL = {
+    # KIT.CHANNEL_MAGNETOMETER: FIFF.???,
+    KIT.CHANNEL_MAGNETOMETER_REFERENCE: FIFF.FIFFV_COIL_KIT_REF_MAG,
+    KIT.CHANNEL_AXIAL_GRADIOMETER: FIFF.FIFFV_COIL_KIT_GRAD,
+    # KIT.CHANNEL_AXIAL_GRADIOMETER_REFERENCE: FIFF.???,
+    # KIT.CHANNEL_PLANAR_GRADIOMETER: FIFF.???,
+    # KIT.CHANNEL_PLANAR_GRADIOMETER_REFERENCE: FIFF.???,
+    # KIT.CHANNEL_2ND_ORDER_AXIAL_GRADIOMETER: FIFF.???,
+    # KIT.CHANNEL_2ND_ORDER_AXIAL_GRADIOMETER_REFERENCE: FIFF.???,
+    KIT.CHANNEL_TRIGGER: FIFF.FIFFV_COIL_NONE, 
+    KIT.CHANNEL_EEG: FIFF.FIFFV_COIL_EEG,
+    KIT.CHANNEL_ECG: FIFF.FIFFV_COIL_NONE, 
+    KIT.CHANNEL_ETC: FIFF.FIFFV_COIL_NONE,
+    KIT.CHANNEL_NULL: FIFF.FIFFV_COIL_NONE,
+}
+KIT.CH_TO_FIFF_KIND = {
+    KIT.CHANNEL_MAGNETOMETER: FIFF.FIFFV_MEG_CH,
+    KIT.CHANNEL_MAGNETOMETER_REFERENCE: FIFF.FIFFV_REF_MEG_CH,
+    KIT.CHANNEL_AXIAL_GRADIOMETER: FIFF.FIFFV_MEG_CH,
+    KIT.CHANNEL_AXIAL_GRADIOMETER_REFERENCE: FIFF.FIFFV_REF_MEG_CH,
+    KIT.CHANNEL_PLANAR_GRADIOMETER: FIFF.FIFFV_MEG_CH,
+    KIT.CHANNEL_PLANAR_GRADIOMETER_REFERENCE: FIFF.FIFFV_REF_MEG_CH,
+    KIT.CHANNEL_2ND_ORDER_AXIAL_GRADIOMETER: FIFF.FIFFV_MEG_CH,
+    KIT.CHANNEL_2ND_ORDER_AXIAL_GRADIOMETER_REFERENCE: FIFF.FIFFV_REF_MEG_CH,
+    KIT.CHANNEL_TRIGGER: FIFF.FIFFV_MISC_CH,
+    KIT.CHANNEL_EEG: FIFF.FIFFV_EEG_CH,
+    KIT.CHANNEL_ECG: FIFF.FIFFV_ECG_CH,
+    KIT.CHANNEL_ETC: FIFF.FIFFV_MISC_CH,
+    KIT.CHANNEL_NULL: FIFF.FIFFV_MISC_CH,
+}
+KIT.CH_LABEL = {
+    KIT.CHANNEL_TRIGGER: 'TRIGGER',
+    KIT.CHANNEL_EEG: 'EEG',
+    KIT.CHANNEL_ECG: 'ECG',
+    KIT.CHANNEL_ETC: 'MISC',
+    KIT.CHANNEL_NULL: 'MISC',
+}
+
+# Acquisition modes
+KIT.CONTINUOUS = 1
+KIT.EVOKED = 2
+KIT.EPOCHS = 3
 
 # coreg constants
 KIT.DIG_POINTS = 10000
 
-# create system specific dicts
-KIT_NY = Bunch(**KIT)
-KIT_AD = Bunch(**KIT)
-
-# NY-system channel information
-KIT_NY.NCHAN = 192
-KIT_NY.NMEGCHAN = 157
-KIT_NY.NREFCHAN = 3
-KIT_NY.NMISCCHAN = 32
-KIT_NY.N_SENS = KIT_NY.NMEGCHAN + KIT_NY.NREFCHAN
-# 12-bit A-to-D converter, one bit for signed integer. range +/- 2048
-KIT_NY.DYNAMIC_RANGE = 2 ** 11
-# amplifier information
-KIT_NY.GAIN1_BIT = 11  # stored in Bit 11-12
-KIT_NY.GAIN1_MASK = 2 ** 11 + 2 ** 12
-KIT_NY.GAIN2_BIT = 0  # stored in Bit 0-2
-KIT_NY.GAIN2_MASK = 2 ** 0 + 2 ** 1 + 2 ** 2  # (0x0007)
-KIT_NY.GAIN3_BIT = None
-KIT_NY.GAIN3_MASK = None
-KIT_NY.HPF_BIT = 4  # stored in Bit 4-5
-KIT_NY.HPF_MASK = 2 ** 4 + 2 ** 5
-KIT_NY.LPF_BIT = 8  # stored in Bit 8-10
-KIT_NY.LPF_MASK = 2 ** 8 + 2 ** 9 + 2 ** 10
-KIT_NY.BEF_BIT = 14  # stored in Bit 14-15
-KIT_NY.BEF_MASK = 2 ** 14 + 2 ** 15
-# HPF options: 0:0, 1:1, 2:3
-KIT_NY.HPFS = [0, 1, 3]
-# LPF options: 0:10Hz, 1:20Hz, 2:50Hz, 3:100Hz, 4:200Hz, 5:500Hz,
-#              6:1,000Hz, 7:2,000Hz
-KIT_NY.LPFS = [10, 20, 50, 100, 200, 500, 1000, 2000]
-
-
-# University of Maryland - system channel information
-# Virtually the same as the NY-system except new ADC in July 2014
-# 16-bit A-to-D converter, one bit for signed integer. range +/- 32768
-KIT_UMD = KIT_NY
-KIT_UMD_2014 = Bunch(**KIT_UMD)
-KIT_UMD_2014.DYNAMIC_RANGE = 2 ** 15
-
-
-# AD-system channel information
-KIT_AD.NCHAN = 256
-KIT_AD.NMEGCHAN = 208
-KIT_AD.NREFCHAN = 16
-KIT_AD.NMISCCHAN = 32
-KIT_AD.N_SENS = KIT_AD.NMEGCHAN + KIT_AD.NREFCHAN
-# 16-bit A-to-D converter, one bit for signed integer. range +/- 32768
-KIT_AD.DYNAMIC_RANGE = 2 ** 15
-# amplifier information
-KIT_AD.GAIN1_BIT = 12  # stored in Bit 12-14
-KIT_AD.GAIN1_MASK = 2 ** 12 + 2 ** 13 + 2 ** 14
-KIT_AD.GAIN2_BIT = 28  # stored in Bit 28-30
-KIT_AD.GAIN2_MASK = 2 ** 28 + 2 ** 29 + 2 ** 30
-KIT_AD.GAIN3_BIT = 24  # stored in Bit 24-26
-KIT_AD.GAIN3_MASK = 2 ** 24 + 2 ** 25 + 2 ** 26
-KIT_AD.HPF_BIT = 8  # stored in Bit 8-10
-KIT_AD.HPF_MASK = 2 ** 8 + 2 ** 9 + 2 ** 10
-KIT_AD.LPF_BIT = 16  # stored in Bit 16-18
-KIT_AD.LPF_MASK = 2 ** 16 + 2 ** 17 + 2 ** 18
-KIT_AD.BEF_BIT = 0  # stored in Bit 0-1
-KIT_AD.BEF_MASK = 2 ** 0 + 2 ** 1
-# HPF options: 0:0Hz, 1:0.03Hz, 2:0.1Hz, 3:0.3Hz, 4:1Hz, 5:3Hz, 6:10Hz, 7:30Hz
-KIT_AD.HPFS = [0, 0.03, 0.1, 0.3, 1, 3, 10, 30]
-# LPF options: 0:10Hz, 1:20Hz, 2:50Hz, 3:100Hz, 4:200Hz, 5:500Hz,
-#              6:1,000Hz, 7:10,000Hz
-KIT_AD.LPFS = [10, 20, 50, 100, 200, 500, 1000, 10000]
-
-
+# Known KIT systems
+# -----------------
 # KIT recording system is encoded in the SQD file as integer:
+KIT.SYSTEM_AS = 260  # Academia Sinica at Taiwan
+KIT.SYSTEM_AS_2008 = 261  # Academia Sinica, 2008 or 2009 -
 KIT.SYSTEM_NYU_2008 = 32  # NYU-NY, July 7, 2008 -
 KIT.SYSTEM_NYU_2009 = 33  # NYU-NY, January 24, 2009 -
 KIT.SYSTEM_NYU_2010 = 34  # NYU-NY, January 22, 2010 -
@@ -117,32 +144,38 @@ KIT.SYSTEM_NYUAD_2014 = 442  # NYU-AD move to NYUAD campus Nov 20, 2014 -
 KIT.SYSTEM_UMD_2004 = 51  # UMD Marie Mount Hall, October 1, 2004 -
 KIT.SYSTEM_UMD_2014_07 = 52  # UMD update to 16 bit ADC, July 4, 2014 -
 KIT.SYSTEM_UMD_2014_12 = 53  # UMD December 4, 2014 -
-
-KIT_CONSTANTS = {KIT.SYSTEM_NYU_2008: KIT_NY,
-                 KIT.SYSTEM_NYU_2009: KIT_NY,
-                 KIT.SYSTEM_NYU_2010: KIT_NY,
-                 KIT.SYSTEM_NYUAD_2011: KIT_AD,
-                 KIT.SYSTEM_NYUAD_2012: KIT_AD,
-                 KIT.SYSTEM_NYUAD_2014: KIT_AD,
-                 KIT.SYSTEM_UMD_2004: KIT_UMD,
-                 KIT.SYSTEM_UMD_2014_07: KIT_UMD_2014,
-                 KIT.SYSTEM_UMD_2014_12: KIT_UMD_2014}
-
-KIT_LAYOUT = {KIT.SYSTEM_NYU_2008: 'KIT-157',
-              KIT.SYSTEM_NYU_2009: 'KIT-157',
-              KIT.SYSTEM_NYU_2010: 'KIT-157',
-              KIT.SYSTEM_NYUAD_2011: 'KIT-AD',
-              KIT.SYSTEM_NYUAD_2012: 'KIT-AD',
-              KIT.SYSTEM_NYUAD_2014: 'KIT-AD',
-              KIT.SYSTEM_UMD_2004: None,
-              KIT.SYSTEM_UMD_2014_07: None,
-              KIT.SYSTEM_UMD_2014_12: 'KIT-UMD-3'}
-
-# Names stored along with ID in SQD files
-SYSNAMES = {KIT.SYSTEM_NYU_2009: 'NYU 160ch System since Jan24 2009',
-            KIT.SYSTEM_NYU_2010: 'NYU 160ch System since Jan24 2009',
-            KIT.SYSTEM_NYUAD_2012: "New York University Abu Dhabi",
-            KIT.SYSTEM_NYUAD_2014: "New York University Abu Dhabi",
-            KIT.SYSTEM_UMD_2004: "University of Maryland",
-            KIT.SYSTEM_UMD_2014_07: "University of Maryland",
-            KIT.SYSTEM_UMD_2014_12: "University of Maryland"}
+# Sensor layouts, used for plotting and connectivity
+KIT_LAYOUT = {
+    KIT.SYSTEM_AS: None,
+    KIT.SYSTEM_AS_2008: None,
+    KIT.SYSTEM_NYU_2008: 'KIT-157',
+    KIT.SYSTEM_NYU_2009: 'KIT-157',
+    KIT.SYSTEM_NYU_2010: 'KIT-157',
+    KIT.SYSTEM_NYUAD_2011: 'KIT-AD',
+    KIT.SYSTEM_NYUAD_2012: 'KIT-AD',
+    KIT.SYSTEM_NYUAD_2014: 'KIT-AD',
+    KIT.SYSTEM_UMD_2004: None,
+    KIT.SYSTEM_UMD_2014_07: None,
+    KIT.SYSTEM_UMD_2014_12: 'KIT-UMD-3',
+}
+# Names displayed in the info dict description
+KIT_SYSNAMES = {
+    KIT.SYSTEM_AS: 'Academia Sinica, -2008',
+    KIT.SYSTEM_AS_2008: 'Academia Sinica, 2008-',
+    KIT.SYSTEM_NYU_2008: 'NYU New York, 2008-9',
+    KIT.SYSTEM_NYU_2009: 'NYU New York, 2009-10',
+    KIT.SYSTEM_NYU_2010: 'NYU New York, 2010-',
+    KIT.SYSTEM_NYUAD_2011: 'New York University Abu Dhabi, 2011-12',
+    KIT.SYSTEM_NYUAD_2012: 'New York University Abu Dhabi, 2012-14',
+    KIT.SYSTEM_NYUAD_2014: 'New York University Abu Dhabi, 2014-',
+    KIT.SYSTEM_UMD_2004: 'University of Maryland, 2004-14',
+    KIT.SYSTEM_UMD_2014_07: 'University of Maryland, 2014',
+    KIT.SYSTEM_UMD_2014_12: 'University of Maryland, 2014-',
+}
+
+LEGACY_AMP_PARAMS = {
+    KIT.SYSTEM_NYU_2008: (5., 11.),
+    KIT.SYSTEM_NYU_2009: (5., 11.),
+    KIT.SYSTEM_NYU_2010: (5., 11.),
+    KIT.SYSTEM_UMD_2004: (5., 11.),
+}
diff --git a/mne/io/kit/coreg.py b/mne/io/kit/coreg.py
index 9634b0f..7c19f02 100644
--- a/mne/io/kit/coreg.py
+++ b/mne/io/kit/coreg.py
@@ -1,4 +1,4 @@
-"""Coordinate Point Extractor for KIT system"""
+"""Coordinate Point Extractor for KIT system."""
 
 # Author: Teon Brooks <teon.brooks at gmail.com>
 #
@@ -14,7 +14,7 @@ from ...externals.six.moves import cPickle as pickle
 
 
 def read_mrk(fname):
-    """Marker Point Extraction in MEG space directly from sqd
+    r"""Marker Point Extraction in MEG space directly from sqd.
 
     Parameters
     ----------
@@ -30,7 +30,7 @@ def read_mrk(fname):
     ext = op.splitext(fname)[-1]
     if ext in ('.sqd', '.mrk'):
         with open(fname, 'rb', buffering=0) as fid:
-            fid.seek(KIT.MRK_INFO)
+            fid.seek(192)
             mrk_offset = unpack('i', fid.read(KIT.INT))[0]
             fid.seek(mrk_offset)
             # skips match_done, meg_to_mri and mri_to_meg
@@ -66,7 +66,7 @@ def read_mrk(fname):
 
 
 def read_sns(fname):
-    """Sensor coordinate extraction in MEG space
+    """Sensor coordinate extraction in MEG space.
 
     Parameters
     ----------
diff --git a/mne/io/kit/kit.py b/mne/io/kit/kit.py
index 01651af..53d3d59 100644
--- a/mne/io/kit/kit.py
+++ b/mne/io/kit/kit.py
@@ -1,16 +1,17 @@
-"""Conversion tool from SQD to FIF
-
-RawKIT class is adapted from Denis Engemann et al.'s mne_bti2fiff.py
+"""Conversion tool from SQD to FIF.
 
+RawKIT class is adapted from Denis Engemann et al.'s mne_bti2fiff.py.
 """
 
-# Author: Teon Brooks <teon.brooks at gmail.com>
+# Authors: Teon Brooks <teon.brooks at gmail.com>
+#          Christian Brodbeck <christianbrodbeck at nyu.edu>
 #
 # License: BSD (3-clause)
 
+from collections import defaultdict
+from math import sin, cos
 from os import SEEK_CUR, path as op
 from struct import unpack
-import time
 
 import numpy as np
 from scipy import linalg
@@ -20,19 +21,27 @@ from ...coreg import fit_matched_points, _decimate_points
 from ...utils import verbose, logger, warn
 from ...transforms import (apply_trans, als_ras_trans,
                            get_ras_to_neuromag_trans, Transform)
-from ..base import _BaseRaw
+from ..base import BaseRaw
 from ..utils import _mult_cal_one
-from ...epochs import _BaseEpochs
+from ...epochs import BaseEpochs
 from ..constants import FIFF
 from ..meas_info import _empty_info, _read_dig_points, _make_dig_points
-from .constants import KIT, KIT_CONSTANTS, SYSNAMES
+from .constants import KIT, LEGACY_AMP_PARAMS
 from .coreg import read_mrk
 from ...externals.six import string_types
 from ...event import read_events
 
 
-class RawKIT(_BaseRaw):
-    """Raw object from KIT SQD file
+class UnsupportedKITFormat(ValueError):
+    """Our reader is not guaranteed to work with old files."""
+
+    def __init__(self, sqd_version, *args, **kwargs):  # noqa: D102
+        self.sqd_version = sqd_version
+        ValueError.__init__(self, *args, **kwargs)
+
+
+class RawKIT(BaseRaw):
+    """Raw object from KIT SQD file.
 
     Parameters
     ----------
@@ -72,8 +81,12 @@ class RawKIT(_BaseRaw):
     stim_code : 'binary' | 'channel'
         How to decode trigger values from stim channels. 'binary' read stim
         channel events as binary code, 'channel' encodes channel number.
+    allow_unknown_format : bool
+        Force reading old data that is not officially supported. Alternatively,
+        read and re-save the data with the KIT MEG Laboratory application.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Notes
     -----
@@ -86,20 +99,21 @@ class RawKIT(_BaseRaw):
     --------
     mne.io.Raw : Documentation of attribute and methods.
     """
+
     @verbose
     def __init__(self, input_fname, mrk=None, elp=None, hsp=None, stim='>',
                  slope='-', stimthresh=1, preload=False, stim_code='binary',
-                 verbose=None):
+                 allow_unknown_format=False, verbose=None):  # noqa: D102
         logger.info('Extracting SQD Parameters from %s...' % input_fname)
         input_fname = op.abspath(input_fname)
         self.preload = False
         logger.info('Creating Raw.info structure...')
-        info, kit_info = get_kit_info(input_fname)
+        info, kit_info = get_kit_info(input_fname, allow_unknown_format)
         kit_info['slope'] = slope
         kit_info['stimthresh'] = stimthresh
-        if kit_info['acq_type'] != 1:
-            err = 'SQD file contains epochs, not raw data. Wrong reader.'
-            raise TypeError(err)
+        if kit_info['acq_type'] != KIT.CONTINUOUS:
+            raise TypeError('SQD file contains epochs, not raw data. Wrong '
+                            'reader.')
         logger.info('Creating Info structure...')
 
         last_samps = [kit_info['n_samples'] - 1]
@@ -124,7 +138,7 @@ class RawKIT(_BaseRaw):
         logger.info('Ready.')
 
     def read_stim_ch(self, buffer_size=1e5):
-        """Read events from data
+        """Read events from data.
 
         Parameter
         ---------
@@ -173,9 +187,12 @@ class RawKIT(_BaseRaw):
             How to decode trigger values from stim channels. 'binary' read stim
             channel events as binary code, 'channel' encodes channel number.
         """
-        if stim_code not in ('binary', 'channel'):
+        if self.preload:
+            raise NotImplementedError("Can't change stim channel after "
+                                      "loading data")
+        elif stim_code not in ('binary', 'channel'):
             raise ValueError("stim_code=%r, needs to be 'binary' or 'channel'"
-                             % stim_code)
+                             % (stim_code,))
 
         if stim is not None:
             if isinstance(stim, str):
@@ -196,47 +213,29 @@ class RawKIT(_BaseRaw):
 
             # modify info
             nchan = self._raw_extras[0]['nchan'] + 1
-            ch_name = 'STI 014'
-            chan_info = {}
-            chan_info['cal'] = KIT.CALIB_FACTOR
-            chan_info['logno'] = nchan
-            chan_info['scanno'] = nchan
-            chan_info['range'] = 1.0
-            chan_info['unit'] = FIFF.FIFF_UNIT_NONE
-            chan_info['unit_mul'] = 0
-            chan_info['ch_name'] = ch_name
-            chan_info['coil_type'] = FIFF.FIFFV_COIL_NONE
-            chan_info['loc'] = np.zeros(12)
-            chan_info['kind'] = FIFF.FIFFV_STIM_CH
-            info['chs'].append(chan_info)
+            info['chs'].append(dict(
+                cal=KIT.CALIB_FACTOR, logno=nchan, scanno=nchan, range=1.0,
+                unit=FIFF.FIFF_UNIT_NONE, unit_mul=0, ch_name='STI 014',
+                coil_type=FIFF.FIFFV_COIL_NONE, loc=np.zeros(12),
+                kind=FIFF.FIFFV_STIM_CH))
             info._update_redundant()
-        if self.preload:
-            err = "Can't change stim channel after preloading data"
-            raise NotImplementedError(err)
 
         self._raw_extras[0]['stim'] = stim
         self._raw_extras[0]['stim_code'] = stim_code
 
     @verbose
     def _read_segment_file(self, data, idx, fi, start, stop, cals, mult):
-        """Read a chunk of raw data"""
+        """Read a chunk of raw data."""
         nchan = self._raw_extras[fi]['nchan']
         data_left = (stop - start) * nchan
-        # amplifier applies only to the sensor channels
-        n_sens = self._raw_extras[fi]['n_sens']
-        sensor_gain = self._raw_extras[fi]['sensor_gain'].copy()
-        sensor_gain[:n_sens] = (sensor_gain[:n_sens] /
-                                self._raw_extras[fi]['amp_gain'])
-        conv_factor = np.array((KIT.VOLTAGE_RANGE /
-                                self._raw_extras[fi]['DYNAMIC_RANGE']) *
-                               sensor_gain)
+        conv_factor = self._raw_extras[fi]['conv_factor']
+
         n_bytes = 2
         # Read up to 100 MB of data at a time.
         blk_size = min(data_left, (100000000 // n_bytes // nchan) * nchan)
         with open(self._filenames[fi], 'rb', buffering=0) as fid:
             # extract data
-            data_offset = KIT.RAW_OFFSET
-            fid.seek(data_offset)
+            fid.seek(144)
             # data offset info
             data_offset = unpack('i', fid.read(KIT.INT))[0]
             pointer = start * nchan * KIT.SHORT
@@ -248,7 +247,7 @@ class RawKIT(_BaseRaw):
                 block = block.reshape(nchan, -1, order='F').astype(float)
                 blk_stop = blk_start + block.shape[1]
                 data_view = data[:, blk_start:blk_stop]
-                block *= conv_factor[:, np.newaxis]
+                block *= conv_factor
 
                 # Create a synthetic stim channel
                 if stim is not None:
@@ -264,14 +263,14 @@ class RawKIT(_BaseRaw):
 
 
 def _default_stim_chs(info):
-    """Default stim channels for SQD files"""
+    """Return default stim channels for SQD files."""
     return pick_types(info, meg=False, ref_meg=False, misc=True,
                       exclude=[])[:8]
 
 
 def _make_stim_channel(trigger_chs, slope, threshold, stim_code,
                        trigger_values):
-    """Create synthetic stim channel from multiple trigger channels"""
+    """Create synthetic stim channel from multiple trigger channels."""
     if slope == '+':
         trig_chs_bin = trigger_chs > threshold
     elif slope == '-':
@@ -288,8 +287,8 @@ def _make_stim_channel(trigger_chs, slope, threshold, stim_code,
     return np.array(trig_chs.sum(axis=0), ndmin=2)
 
 
-class EpochsKIT(_BaseEpochs):
-    """Epochs Array object from KIT SQD file
+class EpochsKIT(BaseEpochs):
+    """Epochs Array object from KIT SQD file.
 
     Parameters
     ----------
@@ -352,8 +351,12 @@ class EpochsKIT(_BaseEpochs):
     hsp : None | str | array, shape = (n_points, 3)
         Digitizer head shape points, or path to head shape file. If more than
         10`000 points are in the head shape, they are automatically decimated.
+    allow_unknown_format : bool
+        Force reading old data that is not officially supported. Alternatively,
+        read and re-save the data with the KIT MEG Laboratory application.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Notes
     -----
@@ -366,10 +369,12 @@ class EpochsKIT(_BaseEpochs):
     --------
     mne.Epochs : Documentation of attribute and methods.
     """
+
     @verbose
     def __init__(self, input_fname, events, event_id=None, tmin=0,
                  baseline=None,  reject=None, flat=None, reject_tmin=None,
-                 reject_tmax=None, mrk=None, elp=None, hsp=None, verbose=None):
+                 reject_tmax=None, mrk=None, elp=None, hsp=None,
+                 allow_unknown_format=False, verbose=None):  # noqa: D102
 
         if isinstance(events, string_types):
             events = read_events(events)
@@ -389,19 +394,19 @@ class EpochsKIT(_BaseEpochs):
 
         logger.info('Extracting KIT Parameters from %s...' % input_fname)
         input_fname = op.abspath(input_fname)
-        self.info, kit_info = get_kit_info(input_fname)
+        self.info, kit_info = get_kit_info(input_fname, allow_unknown_format)
+        kit_info.update(filename=input_fname)
         self._raw_extras = [kit_info]
+        self._filenames = []
         if len(events) != self._raw_extras[0]['n_epochs']:
             raise ValueError('Event list does not match number of epochs.')
 
-        if self._raw_extras[0]['acq_type'] == 3:
-            self._raw_extras[0]['data_offset'] = KIT.RAW_OFFSET
+        if self._raw_extras[0]['acq_type'] == KIT.EPOCHS:
             self._raw_extras[0]['data_length'] = KIT.INT
             self._raw_extras[0]['dtype'] = 'h'
         else:
-            err = ('SQD file contains raw data, not epochs or average. '
-                   'Wrong reader.')
-            raise TypeError(err)
+            raise TypeError('SQD file contains raw data, not epochs or '
+                            'average. Wrong reader.')
 
         if event_id is None:  # convert to int to make typing-checks happy
             event_id = dict((str(e), int(e)) for e in np.unique(events[:, 2]))
@@ -411,22 +416,19 @@ class EpochsKIT(_BaseEpochs):
                 raise ValueError('No matching events found for %s '
                                  '(event id %i)' % (key, val))
 
-        self._filename = input_fname
         data = self._read_kit_data()
         assert data.shape == (self._raw_extras[0]['n_epochs'],
                               self.info['nchan'],
                               self._raw_extras[0]['frame_length'])
         tmax = ((data.shape[2] - 1) / self.info['sfreq']) + tmin
-        super(EpochsKIT, self).__init__(self.info, data, events, event_id,
-                                        tmin, tmax, baseline,
-                                        reject=reject, flat=flat,
-                                        reject_tmin=reject_tmin,
-                                        reject_tmax=reject_tmax,
-                                        verbose=verbose)
+        super(EpochsKIT, self).__init__(
+            self.info, data, events, event_id, tmin, tmax, baseline,
+            reject=reject, flat=flat, reject_tmin=reject_tmin,
+            reject_tmax=reject_tmax, filename=input_fname, verbose=verbose)
         logger.info('Ready.')
 
     def _read_kit_data(self):
-        """Read epochs data
+        """Read epochs data.
 
         Returns
         -------
@@ -435,34 +437,23 @@ class EpochsKIT(_BaseEpochs):
         times : array, [samples]
             returns the time values corresponding to the samples.
         """
-        #  Initial checks
-        epoch_length = self._raw_extras[0]['frame_length']
-        n_epochs = self._raw_extras[0]['n_epochs']
-        n_samples = self._raw_extras[0]['n_samples']
-
-        with open(self._filename, 'rb', buffering=0) as fid:
-            # extract data
-            data_offset = self._raw_extras[0]['data_offset']
-            dtype = self._raw_extras[0]['dtype']
-            fid.seek(data_offset)
+        info = self._raw_extras[0]
+        epoch_length = info['frame_length']
+        n_epochs = info['n_epochs']
+        n_samples = info['n_samples']
+        filename = info['filename']
+        dtype = info['dtype']
+        nchan = info['nchan']
+
+        with open(filename, 'rb', buffering=0) as fid:
+            fid.seek(144)
             # data offset info
             data_offset = unpack('i', fid.read(KIT.INT))[0]
-            nchan = self._raw_extras[0]['nchan']
             count = n_samples * nchan
             fid.seek(data_offset)
             data = np.fromfile(fid, dtype=dtype, count=count)
-            data = data.reshape((n_samples, nchan))
-        # amplifier applies only to the sensor channels
-        n_sens = self._raw_extras[0]['n_sens']
-        sensor_gain = np.copy(self._raw_extras[0]['sensor_gain'])
-        sensor_gain[:n_sens] = (sensor_gain[:n_sens] /
-                                self._raw_extras[0]['amp_gain'])
-        conv_factor = np.array((KIT.VOLTAGE_RANGE /
-                                self._raw_extras[0]['DYNAMIC_RANGE']) *
-                               sensor_gain, ndmin=2)
-        data = conv_factor * data
-        # reshape
-        data = data.T
+        data = data.reshape((n_samples, nchan)).T
+        data = data * info['conv_factor']
         data = data.reshape((nchan, n_epochs, epoch_length))
         data = data.transpose((1, 0, 2))
 
@@ -470,10 +461,10 @@ class EpochsKIT(_BaseEpochs):
 
 
 def _set_dig_kit(mrk, elp, hsp):
-    """Add landmark points and head shape data to the KIT instance
+    """Add landmark points and head shape data to the KIT instance.
 
     Digitizer data (elp and hsp) are represented in [mm] in the Polhemus
-    ALS coordinate system.
+    ALS coordinate system. This is converted to [m].
 
     Parameters
     ----------
@@ -500,7 +491,7 @@ def _set_dig_kit(mrk, elp, hsp):
         hsp = _read_dig_points(hsp)
     n_pts = len(hsp)
     if n_pts > KIT.DIG_POINTS:
-        hsp = _decimate_points(hsp, res=5)
+        hsp = _decimate_points(hsp, res=0.005)
         n_new = len(hsp)
         warn("The selected head shape contained {n_in} points, which is "
              "more than recommended ({n_rec}), and was automatically "
@@ -541,13 +532,16 @@ def _set_dig_kit(mrk, elp, hsp):
     return dig_points, dev_head_t
 
 
-def get_kit_info(rawfile):
-    """Extracts all the information from the sqd file.
+def get_kit_info(rawfile, allow_unknown_format):
+    """Extract all the information from the sqd file.
 
     Parameters
     ----------
     rawfile : str
         KIT file to be read.
+    allow_unknown_format : bool
+        Force reading old data that is not officially supported. Alternatively,
+        read and re-save the data with the KIT MEG Laboratory application.
 
     Returns
     -------
@@ -558,161 +552,201 @@ def get_kit_info(rawfile):
     """
     sqd = dict()
     sqd['rawfile'] = rawfile
+    unsupported_format = False
     with open(rawfile, 'rb', buffering=0) as fid:  # buffering=0 for np bug
-        fid.seek(KIT.BASIC_INFO)
+        fid.seek(16)
         basic_offset = unpack('i', fid.read(KIT.INT))[0]
         fid.seek(basic_offset)
-        # skips version, revision
-        fid.seek(KIT.INT * 2, SEEK_CUR)
+        # check file format version
+        version, revision = unpack('2i', fid.read(2 * KIT.INT))
+        if version < 2 or (version == 2 and revision < 3):
+            version_string = "V%iR%03i" % (version, revision)
+            if allow_unknown_format:
+                unsupported_format = True
+                logger.warning("Force loading KIT format %s", version_string)
+            else:
+                raise UnsupportedKITFormat(
+                    version_string,
+                    "SQD file format %s is not officially supported. "
+                    "Set allow_unknown_format=True to load it anyways." %
+                    (version_string,))
+
         sysid = unpack('i', fid.read(KIT.INT))[0]
         # basic info
-        sysname = unpack('128s', fid.read(KIT.STRING))
-        sysname = sysname[0].decode().split('\n')[0]
-        if sysid not in KIT_CONSTANTS:
-            raise NotImplementedError("Data from the KIT system %s (ID %s) "
-                                      "can not currently be read, please "
-                                      "contact the MNE-Python developers."
-                                      % (sysname, sysid))
-        KIT_SYS = KIT_CONSTANTS[sysid]
-        logger.info("KIT-System ID %i: %s" % (sysid, sysname))
-        if sysid in SYSNAMES:
-            if sysname != SYSNAMES[sysid]:
-                warn("KIT file %s has system-name %r, expected %r"
-                     % (rawfile, sysname, SYSNAMES[sysid]))
-
+        system_name = unpack('128s', fid.read(128))[0].decode()
+        # model name
+        model_name = unpack('128s', fid.read(128))[0].decode()
         # channels
-        fid.seek(KIT.STRING, SEEK_CUR)  # skips modelname
-        sqd['nchan'] = unpack('i', fid.read(KIT.INT))[0]
-        # channel locations
-        fid.seek(KIT_SYS.CHAN_LOC_OFFSET)
-        chan_offset = unpack('i', fid.read(KIT.INT))[0]
-        chan_size = unpack('i', fid.read(KIT.INT))[0]
-
-        fid.seek(chan_offset)
-        sensors = []
-        for i in range(KIT_SYS.N_SENS):
+        sqd['nchan'] = channel_count = unpack('i', fid.read(KIT.INT))[0]
+        comment = unpack('256s', fid.read(256))[0].decode()
+        create_time, last_modified_time = unpack('2i', fid.read(2 * KIT.INT))
+        fid.seek(KIT.INT * 3, SEEK_CUR)  # reserved
+        dewar_style = unpack('i', fid.read(KIT.INT))[0]
+        fid.seek(KIT.INT * 3, SEEK_CUR)  # spare
+        fll_type = unpack('i', fid.read(KIT.INT))[0]
+        fid.seek(KIT.INT * 3, SEEK_CUR)  # spare
+        trigger_type = unpack('i', fid.read(KIT.INT))[0]
+        fid.seek(KIT.INT * 3, SEEK_CUR)  # spare
+        adboard_type = unpack('i', fid.read(KIT.INT))[0]
+        fid.seek(KIT.INT * 29, SEEK_CUR)  # reserved
+
+        if version < 2 or (version == 2 and revision <= 3):
+            adc_range = float(unpack('i', fid.read(KIT.INT))[0])
+        else:
+            adc_range = unpack('d', fid.read(KIT.DOUBLE))[0]
+        adc_polarity, adc_allocated, adc_stored = unpack('3i',
+                                                         fid.read(3 * KIT.INT))
+
+        logger.debug("SQD file basic information:")
+        logger.debug("Meg160 version = V%iR%03i", version, revision)
+        logger.debug("System ID      = %i", sysid)
+        logger.debug("System name    = %s", system_name.replace('\n', '/'))
+        logger.debug("Model name     = %s", model_name)
+        logger.debug("Channel count  = %i", channel_count)
+        logger.debug("Comment        = %s", comment)
+        logger.debug("Dewar style    = %i", dewar_style)
+        logger.debug("FLL type       = %i", fll_type)
+        logger.debug("Trigger type   = %i", trigger_type)
+        logger.debug("A/D board type = %i", adboard_type)
+        logger.debug("ADC range      = +/-%s[V]", adc_range / 2.)
+        logger.debug("ADC allocate   = %i[bit]", adc_allocated)
+        logger.debug("ADC bit        = %i[bit]", adc_stored)
+
+        # check that we can read this file
+        if fll_type not in KIT.FLL_SETTINGS:
+            raise IOError("Unknown FLL type: %i" % fll_type)
+
+        # channel information
+        fid.seek(64)
+        chan_offset, chan_size = unpack('2i', fid.read(2 * KIT.INT))
+        sqd['channels'] = channels = []
+        for i in range(channel_count):
             fid.seek(chan_offset + chan_size * i)
-            sens_type = unpack('i', fid.read(KIT.INT))[0]
-            if sens_type == 1:
-                # magnetometer
-                # x,y,z,theta,phi,coilsize
-                sensors.append(np.fromfile(fid, dtype='d', count=6))
-            elif sens_type == 2:
-                # axialgradiometer
-                # x,y,z,theta,phi,baseline,coilsize
-                sensors.append(np.fromfile(fid, dtype='d', count=7))
-            elif sens_type == 3:
-                # planargradiometer
-                # x,y,z,theta,phi,btheta,bphi,baseline,coilsize
-                sensors.append(np.fromfile(fid, dtype='d', count=9))
-            elif sens_type in (257, 0):
-                # reference channels
-                sensors.append(np.zeros(7))
-                sqd['i'] = sens_type
+            channel_type, = unpack('i', fid.read(KIT.INT))
+            # System 52 mislabeled reference channels as NULL. This was fixed
+            # in system 53; not sure about 51...
+            if sysid == 52 and i < 160 and channel_type == KIT.CHANNEL_NULL:
+                channel_type = KIT.CHANNEL_MAGNETOMETER_REFERENCE
+
+            if channel_type in KIT.CHANNELS_MEG:
+                if channel_type not in KIT.CH_TO_FIFF_COIL:
+                    raise NotImplementedError(
+                        "KIT channel type %i can not be read. Please contact "
+                        "the mne-python developers." % channel_type)
+                channels.append({
+                    'type': channel_type,
+                    # (x, y, z, theta, phi) for all MEG channels. Some channel
+                    # types have additional information which we're not using.
+                    'loc': np.fromfile(fid, dtype='d', count=5)
+                })
+            elif channel_type in KIT.CHANNELS_MISC:
+                channel_no, = unpack('i', fid.read(KIT.INT))
+                name, = unpack('64s', fid.read(64))
+                channels.append({
+                    'type': channel_type,
+                    'no': channel_no,
+                })
+            elif channel_type == KIT.CHANNEL_NULL:
+                channels.append({'type': channel_type})
             else:
-                raise IOError("Unknown KIT channel type: %i" % sens_type)
-        sqd['sensor_locs'] = np.array(sensors)
-        if len(sqd['sensor_locs']) != KIT_SYS.N_SENS:
-            raise IOError("An error occurred while reading %s" % rawfile)
-
-        # amplifier gain
-        fid.seek(KIT_SYS.AMPLIFIER_INFO)
-        amp_offset = unpack('i', fid.read(KIT_SYS.INT))[0]
-        fid.seek(amp_offset)
-        amp_data = unpack('i', fid.read(KIT_SYS.INT))[0]
-
-        gain1 = KIT_SYS.GAINS[(KIT_SYS.GAIN1_MASK & amp_data) >>
-                              KIT_SYS.GAIN1_BIT]
-        gain2 = KIT_SYS.GAINS[(KIT_SYS.GAIN2_MASK & amp_data) >>
-                              KIT_SYS.GAIN2_BIT]
-        if KIT_SYS.GAIN3_BIT:
-            gain3 = KIT_SYS.GAINS[(KIT_SYS.GAIN3_MASK & amp_data) >>
-                                  KIT_SYS.GAIN3_BIT]
-            sqd['amp_gain'] = gain1 * gain2 * gain3
-        else:
-            sqd['amp_gain'] = gain1 * gain2
-
-        # filter settings
-        sqd['lowpass'] = KIT_SYS.LPFS[(KIT_SYS.LPF_MASK & amp_data) >>
-                                      KIT_SYS.LPF_BIT]
-        sqd['highpass'] = KIT_SYS.HPFS[(KIT_SYS.HPF_MASK & amp_data) >>
-                                       KIT_SYS.HPF_BIT]
-        sqd['notch'] = KIT_SYS.BEFS[(KIT_SYS.BEF_MASK & amp_data) >>
-                                    KIT_SYS.BEF_BIT]
+                raise IOError("Unknown KIT channel type: %i" % channel_type)
 
+        # Channel sensitivity information:
         # only sensor channels requires gain. the additional misc channels
         # (trigger channels, audio and voice channels) are passed
         # through unaffected
+        fid.seek(80)
+        sensitivity_offset, = unpack('i', fid.read(KIT.INT))
+        fid.seek(sensitivity_offset)
+        # (offset [Volt], gain [Tesla/Volt]) for each channel
+        sensitivity = np.fromfile(fid, dtype='d', count=channel_count * 2)
+        sensitivity.shape = (channel_count, 2)
+        channel_offset, channel_gain = sensitivity.T
 
-        fid.seek(KIT_SYS.CHAN_SENS)
-        sens_offset = unpack('i', fid.read(KIT_SYS.INT))[0]
-        fid.seek(sens_offset)
-        sens = np.fromfile(fid, dtype='d', count=sqd['nchan'] * 2)
-        sens.shape = (sqd['nchan'], 2)
-        sqd['sensor_gain'] = np.ones(KIT_SYS.NCHAN)
-        sqd['sensor_gain'][:KIT_SYS.N_SENS] = sens[:KIT_SYS.N_SENS, 1]
-
-        fid.seek(KIT_SYS.SAMPLE_INFO)
-        acqcond_offset = unpack('i', fid.read(KIT_SYS.INT))[0]
+        # amplifier gain
+        fid.seek(112)
+        amp_offset = unpack('i', fid.read(KIT.INT))[0]
+        fid.seek(amp_offset)
+        amp_data = unpack('i', fid.read(KIT.INT))[0]
+        if fll_type >= 100:  # Kapper Type
+            # gain:             mask           bit
+            gain1 = (amp_data & 0x00007000) >> 12
+            gain2 = (amp_data & 0x70000000) >> 28
+            gain3 = (amp_data & 0x07000000) >> 24
+            amp_gain = (KIT.GAINS[gain1] * KIT.GAINS[gain2] * KIT.GAINS[gain3])
+            # filter settings
+            hpf = (amp_data & 0x00000700) >> 8
+            lpf = (amp_data & 0x00070000) >> 16
+            bef = (amp_data & 0x00000003) >> 0
+        else:  # Hanger Type
+            # gain
+            input_gain = (amp_data & 0x1800) >> 11
+            output_gain = (amp_data & 0x0007) >> 0
+            amp_gain = KIT.GAINS[input_gain] * KIT.GAINS[output_gain]
+            # filter settings
+            hpf = (amp_data & 0x007) >> 4
+            lpf = (amp_data & 0x0700) >> 8
+            bef = (amp_data & 0xc000) >> 14
+        hpf_options, lpf_options, bef_options = KIT.FLL_SETTINGS[fll_type]
+        sqd['highpass'] = KIT.HPFS[hpf_options][hpf]
+        sqd['lowpass'] = KIT.LPFS[lpf_options][lpf]
+        sqd['notch'] = KIT.BEFS[bef_options][bef]
+
+        # Acquisition Parameters
+        fid.seek(128)
+        acqcond_offset, = unpack('i', fid.read(KIT.INT))
         fid.seek(acqcond_offset)
-        acq_type = unpack('i', fid.read(KIT_SYS.INT))[0]
-        sqd['sfreq'] = unpack('d', fid.read(KIT_SYS.DOUBLE))[0]
-        if acq_type == 1:
-            fid.read(KIT_SYS.INT)  # initialized estimate of samples
-            sqd['n_samples'] = unpack('i', fid.read(KIT_SYS.INT))[0]
-        elif acq_type == 2 or acq_type == 3:
-            sqd['frame_length'] = unpack('i', fid.read(KIT_SYS.INT))[0]
-            sqd['pretrigger_length'] = unpack('i', fid.read(KIT_SYS.INT))[0]
-            sqd['average_count'] = unpack('i', fid.read(KIT_SYS.INT))[0]
-            sqd['n_epochs'] = unpack('i', fid.read(KIT_SYS.INT))[0]
-            sqd['n_samples'] = sqd['frame_length'] * sqd['n_epochs']
-        else:
-            err = ("Your file is neither continuous nor epoched data. "
-                   "What type of file is it?!")
-            raise TypeError(err)
-        sqd['n_sens'] = KIT_SYS.N_SENS
-        sqd['nmegchan'] = KIT_SYS.NMEGCHAN
-        sqd['nmiscchan'] = KIT_SYS.NMISCCHAN
-        sqd['DYNAMIC_RANGE'] = KIT_SYS.DYNAMIC_RANGE
-        sqd['acq_type'] = acq_type
-
-        # Create raw.info dict for raw fif object with SQD data
-        info = _empty_info(float(sqd['sfreq']))
-        info.update(meas_date=int(time.time()), lowpass=sqd['lowpass'],
-                    highpass=sqd['highpass'], filename=rawfile,
-                    buffer_size_sec=1., kit_system_id=sysid)
-
-        # Creates a list of dicts of meg channels for raw.info
-        logger.info('Setting channel info structure...')
-        locs = sqd['sensor_locs']
-        chan_locs = apply_trans(als_ras_trans, locs[:, :3])
-        chan_angles = locs[:, 3:]
-        for idx, (ch_loc, ch_angles) in enumerate(zip(chan_locs, chan_angles),
-                                                  1):
-            chan_info = {'cal': KIT.CALIB_FACTOR,
-                         'logno': idx,
-                         'scanno': idx,
-                         'range': KIT.RANGE,
-                         'unit_mul': KIT.UNIT_MUL,
-                         'ch_name': 'MEG %03d' % idx,
-                         'unit': FIFF.FIFF_UNIT_T,
-                         'coord_frame': FIFF.FIFFV_COORD_DEVICE}
-            if idx <= sqd['nmegchan']:
-                chan_info['coil_type'] = FIFF.FIFFV_COIL_KIT_GRAD
-                chan_info['kind'] = FIFF.FIFFV_MEG_CH
+        sqd['acq_type'], = acq_type, = unpack('i', fid.read(KIT.INT))
+        sqd['sfreq'], = unpack('d', fid.read(KIT.DOUBLE))
+        if acq_type == KIT.CONTINUOUS:
+            samples_count, = unpack('i', fid.read(KIT.INT))
+            sqd['n_samples'], = unpack('i', fid.read(KIT.INT))
+        elif acq_type == KIT.EVOKED or acq_type == KIT.EPOCHS:
+            sqd['frame_length'], = unpack('i', fid.read(KIT.INT))
+            sqd['pretrigger_length'], = unpack('i', fid.read(KIT.INT))
+            sqd['average_count'], = unpack('i', fid.read(KIT.INT))
+            sqd['n_epochs'], = unpack('i', fid.read(KIT.INT))
+            if acq_type == KIT.EVOKED:
+                sqd['n_samples'] = sqd['frame_length']
             else:
-                chan_info['coil_type'] = FIFF.FIFFV_COIL_KIT_REF_MAG
-                chan_info['kind'] = FIFF.FIFFV_REF_MEG_CH
-
+                sqd['n_samples'] = sqd['frame_length'] * sqd['n_epochs']
+        else:
+            raise IOError("Invalid acquisition type: %i. Your file is neither "
+                          "continuous nor epoched data." % (acq_type,))
+
+    # precompute conversion factor for reading data
+    if unsupported_format:
+        if sysid not in LEGACY_AMP_PARAMS:
+            raise IOError("Legacy parameters for system ID %i unavailable" %
+                          (sysid,))
+        adc_range, adc_stored = LEGACY_AMP_PARAMS[sysid]
+    is_meg = np.array([ch['type'] in KIT.CHANNELS_MEG for ch in channels])
+    ad_to_volt = adc_range / (2. ** adc_stored)
+    ad_to_tesla = ad_to_volt / amp_gain * channel_gain
+    conv_factor = np.where(is_meg, ad_to_tesla, ad_to_volt)
+    sqd['conv_factor'] = conv_factor[:, np.newaxis]
+
+    # Create raw.info dict for raw fif object with SQD data
+    info = _empty_info(float(sqd['sfreq']))
+    info.update(meas_date=create_time, lowpass=sqd['lowpass'],
+                highpass=sqd['highpass'], buffer_size_sec=1.,
+                kit_system_id=sysid)
+
+    # Creates a list of dicts of meg channels for raw.info
+    logger.info('Setting channel info structure...')
+    info['chs'] = fiff_channels = []
+    channel_index = defaultdict(lambda: 0)
+    for idx, ch in enumerate(channels, 1):
+        if ch['type'] in KIT.CHANNELS_MEG:
+            ch_name = 'MEG %03d' % idx
             # create three orthogonal vector
             # ch_angles[0]: theta, ch_angles[1]: phi
-            ch_angles = np.radians(ch_angles)
-            x = np.sin(ch_angles[0]) * np.cos(ch_angles[1])
-            y = np.sin(ch_angles[0]) * np.sin(ch_angles[1])
-            z = np.cos(ch_angles[0])
+            theta, phi = np.radians(ch['loc'][3:])
+            x = sin(theta) * cos(phi)
+            y = sin(theta) * sin(phi)
+            z = cos(theta)
             vec_z = np.array([x, y, z])
-            length = linalg.norm(vec_z)
-            vec_z /= length
+            vec_z /= linalg.norm(vec_z)
             vec_x = np.zeros(vec_z.size, dtype=np.float)
             if vec_z[1] < vec_z[2]:
                 if vec_z[0] < vec_z[1]:
@@ -724,37 +758,34 @@ def get_kit_info(rawfile):
             else:
                 vec_x[2] = 1.0
             vec_x -= np.sum(vec_x * vec_z) * vec_z
-            length = linalg.norm(vec_x)
-            vec_x /= length
+            vec_x /= linalg.norm(vec_x)
             vec_y = np.cross(vec_z, vec_x)
             # transform to Neuromag like coordinate space
-            vecs = np.vstack((vec_x, vec_y, vec_z))
+            vecs = np.vstack((ch['loc'][:3], vec_x, vec_y, vec_z))
             vecs = apply_trans(als_ras_trans, vecs)
-            chan_info['loc'] = np.vstack((ch_loc, vecs)).ravel()
-            info['chs'].append(chan_info)
-
-        # label trigger and misc channels
-        for idx in range(1, sqd['nmiscchan'] + 1):
-            ch_idx = idx + KIT_SYS.N_SENS
-            chan_info = {'cal': KIT.CALIB_FACTOR,
-                         'logno': ch_idx,
-                         'scanno': ch_idx,
-                         'range': 1.0,
-                         'unit': FIFF.FIFF_UNIT_V,
-                         'unit_mul': 0,
-                         'ch_name': 'MISC %03d' % idx,
-                         'coil_type': FIFF.FIFFV_COIL_NONE,
-                         'loc': np.zeros(12),
-                         'kind': FIFF.FIFFV_MISC_CH}
-            info['chs'].append(chan_info)
+            unit = FIFF.FIFF_UNIT_T
+            loc = vecs.ravel()
+        else:
+            ch_type_label = KIT.CH_LABEL[ch['type']]
+            channel_index[ch_type_label] += 1
+            ch_type_index = channel_index[ch_type_label]
+            ch_name = '%s %03i' % (ch_type_label, ch_type_index)
+            unit = FIFF.FIFF_UNIT_V
+            loc = np.zeros(12)
+        fiff_channels.append(dict(
+            cal=KIT.CALIB_FACTOR, logno=idx, scanno=idx, range=KIT.RANGE,
+            unit=unit, unit_mul=KIT.UNIT_MUL, ch_name=ch_name,
+            coord_frame=FIFF.FIFFV_COORD_DEVICE,
+            coil_type=KIT.CH_TO_FIFF_COIL[ch['type']],
+            kind=KIT.CH_TO_FIFF_KIND[ch['type']], loc=loc))
     info._update_redundant()
     return info, sqd
 
 
 def read_raw_kit(input_fname, mrk=None, elp=None, hsp=None, stim='>',
                  slope='-', stimthresh=1, preload=False, stim_code='binary',
-                 verbose=None):
-    """Reader function for KIT conversion to FIF
+                 allow_unknown_format=False, verbose=None):
+    """Reader function for KIT conversion to FIF.
 
     Parameters
     ----------
@@ -791,8 +822,12 @@ def read_raw_kit(input_fname, mrk=None, elp=None, hsp=None, stim='>',
     stim_code : 'binary' | 'channel'
         How to decode trigger values from stim channels. 'binary' read stim
         channel events as binary code, 'channel' encodes channel number.
+    allow_unknown_format : bool
+        Force reading old data that is not officially supported. Alternatively,
+        read and re-save the data with the KIT MEG Laboratory application.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -802,15 +837,21 @@ def read_raw_kit(input_fname, mrk=None, elp=None, hsp=None, stim='>',
     See Also
     --------
     mne.io.Raw : Documentation of attribute and methods.
+
+    Notes
+    -----
+    If mrk, hsp or elp are array_like inputs, then the numbers in xyz
+    coordinates should be in units of meters.
     """
     return RawKIT(input_fname=input_fname, mrk=mrk, elp=elp, hsp=hsp,
                   stim=stim, slope=slope, stimthresh=stimthresh,
-                  preload=preload, stim_code=stim_code, verbose=verbose)
+                  preload=preload, stim_code=stim_code,
+                  allow_unknown_format=allow_unknown_format, verbose=verbose)
 
 
-def read_epochs_kit(input_fname, events, event_id=None,
-                    mrk=None, elp=None, hsp=None, verbose=None):
-    """Reader function for KIT epochs files
+def read_epochs_kit(input_fname, events, event_id=None, mrk=None, elp=None,
+                    hsp=None, allow_unknown_format=False, verbose=None):
+    """Reader function for KIT epochs files.
 
     Parameters
     ----------
@@ -839,8 +880,12 @@ def read_epochs_kit(input_fname, events, event_id=None,
     hsp : None | str | array, shape (n_points, 3)
         Digitizer head shape points, or path to head shape file. If more than
         10,000 points are in the head shape, they are automatically decimated.
+    allow_unknown_format : bool
+        Force reading old data that is not officially supported. Alternatively,
+        read and re-save the data with the KIT MEG Laboratory application.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -853,5 +898,6 @@ def read_epochs_kit(input_fname, events, event_id=None,
     """
     epochs = EpochsKIT(input_fname=input_fname, events=events,
                        event_id=event_id, mrk=mrk, elp=elp, hsp=hsp,
+                       allow_unknown_format=allow_unknown_format,
                        verbose=verbose)
     return epochs
diff --git a/mne/io/kit/tests/test_kit.py b/mne/io/kit/tests/test_kit.py
index ea5f4d7..55136cc 100644
--- a/mne/io/kit/tests/test_kit.py
+++ b/mne/io/kit/tests/test_kit.py
@@ -5,22 +5,29 @@ from __future__ import print_function
 #
 # License: BSD (3-clause)
 
-import os.path as op
 import inspect
+import os.path as op
+import warnings
+
 import numpy as np
 from numpy.testing import assert_array_almost_equal, assert_array_equal
-from nose.tools import assert_equal, assert_raises, assert_true
+from nose.tools import (assert_equal, assert_almost_equal, assert_raises,
+                        assert_true)
 from scipy import linalg
 import scipy.io
 
+import mne
 from mne import pick_types, Epochs, find_events, read_events
+from mne.datasets.testing import requires_testing_data
 from mne.transforms import apply_trans
 from mne.tests.common import assert_dig_allclose
-from mne.utils import run_tests_if_main
+from mne.utils import run_tests_if_main, _TempDir
 from mne.io import read_raw_fif, read_raw_kit, read_epochs_kit
+from mne.io.constants import FIFF
 from mne.io.kit.coreg import read_sns
-from mne.io.kit.constants import KIT, KIT_CONSTANTS, KIT_NY, KIT_UMD_2014
+from mne.io.kit.constants import KIT
 from mne.io.tests.test_raw import _test_raw_reader
+from mne.surface import _get_ico_surface
 
 FILE = inspect.getfile(inspect.currentframe())
 parent_dir = op.dirname(op.abspath(FILE))
@@ -37,7 +44,11 @@ hsp_txt_path = op.join(data_dir, 'test_hsp.txt')
 elp_path = op.join(data_dir, 'test.elp')
 hsp_path = op.join(data_dir, 'test.hsp')
 
+data_path = mne.datasets.testing.data_path(download=False)
+sqd_as_path = op.join(data_path, 'KIT', 'test_as-raw.con')
 
+
+ at requires_testing_data
 def test_data():
     """Test reading raw kit files."""
     assert_raises(TypeError, read_raw_kit, epochs_path)
@@ -56,7 +67,18 @@ def test_data():
                               stimthresh=1)
     assert_true('RawKIT' in repr(raw_py))
     assert_equal(raw_mrk.info['kit_system_id'], KIT.SYSTEM_NYU_2010)
-    assert_true(KIT_CONSTANTS[raw_mrk.info['kit_system_id']] is KIT_NY)
+
+    # check number/kind of channels
+    assert_equal(len(raw_py.info['chs']), 193)
+    kit_channels = (('kind', {FIFF.FIFFV_MEG_CH: 157, FIFF.FIFFV_REF_MEG_CH: 3,
+                              FIFF.FIFFV_MISC_CH: 32, FIFF.FIFFV_STIM_CH: 1}),
+                    ('coil_type', {FIFF.FIFFV_COIL_KIT_GRAD: 157,
+                                   FIFF.FIFFV_COIL_KIT_REF_MAG: 3,
+                                   FIFF.FIFFV_COIL_NONE: 33}))
+    for label, target in kit_channels:
+        actual = {id_: sum(ch[label] == id_ for ch in raw_py.info['chs']) for
+                  id_ in target.keys()}
+        assert_equal(actual, target)
 
     # Test stim channel
     raw_stim = read_raw_kit(sqd_path, mrk_path, elp_txt_path, hsp_txt_path,
@@ -71,7 +93,7 @@ def test_data():
     # Binary file only stores the sensor channels
     py_picks = pick_types(raw_py.info, exclude='bads')
     raw_bin = op.join(data_dir, 'test_bin_raw.fif')
-    raw_bin = read_raw_fif(raw_bin, preload=True, add_eeg_ref=False)
+    raw_bin = read_raw_fif(raw_bin, preload=True)
     bin_picks = pick_types(raw_bin.info, stim=True, exclude='bads')
     data_bin, _ = raw_bin[bin_picks]
     data_py, _ = raw_py[py_picks]
@@ -92,15 +114,34 @@ def test_data():
     _test_raw_reader(read_raw_kit, input_fname=sqd_umd_path)
     raw = read_raw_kit(sqd_umd_path)
     assert_equal(raw.info['kit_system_id'], KIT.SYSTEM_UMD_2014_12)
-    assert_true(KIT_CONSTANTS[raw.info['kit_system_id']] is KIT_UMD_2014)
+    # check number/kind of channels
+    assert_equal(len(raw.info['chs']), 193)
+    for label, target in kit_channels:
+        actual = {id_: sum(ch[label] == id_ for ch in raw.info['chs']) for
+                  id_ in target.keys()}
+        assert_equal(actual, target)
+
+    # KIT Academia Sinica
+    raw = read_raw_kit(sqd_as_path, slope='+')
+    assert_equal(raw.info['kit_system_id'], KIT.SYSTEM_AS_2008)
+    assert_equal(raw.info['chs'][100]['ch_name'], 'MEG 101')
+    assert_equal(raw.info['chs'][100]['kind'], FIFF.FIFFV_MEG_CH)
+    assert_equal(raw.info['chs'][100]['coil_type'], FIFF.FIFFV_COIL_KIT_GRAD)
+    assert_equal(raw.info['chs'][157]['ch_name'], 'MEG 158')
+    assert_equal(raw.info['chs'][157]['kind'], FIFF.FIFFV_REF_MEG_CH)
+    assert_equal(raw.info['chs'][157]['coil_type'],
+                 FIFF.FIFFV_COIL_KIT_REF_MAG)
+    assert_equal(raw.info['chs'][160]['ch_name'], 'EEG 001')
+    assert_equal(raw.info['chs'][160]['kind'], FIFF.FIFFV_EEG_CH)
+    assert_equal(raw.info['chs'][160]['coil_type'], FIFF.FIFFV_COIL_EEG)
+    assert_array_equal(find_events(raw), [[91, 0, 2]])
 
 
 def test_epochs():
     """Test reading epoched SQD file."""
     raw = read_raw_kit(sqd_path, stim=None)
     events = read_events(events_path)
-    raw_epochs = Epochs(raw, events, None, tmin=0, tmax=.099, baseline=None,
-                        add_eeg_ref=False)
+    raw_epochs = Epochs(raw, events, None, tmin=0, tmax=.099, baseline=None)
     data1 = raw_epochs.get_data()
     epochs = read_epochs_kit(epochs_path, events_path)
     data11 = epochs.get_data()
@@ -141,10 +182,10 @@ def test_ch_loc():
     """Test raw kit loc."""
     raw_py = read_raw_kit(sqd_path, mrk_path, elp_txt_path, hsp_txt_path,
                           stim='<')
-    raw_bin = read_raw_fif(op.join(data_dir, 'test_bin_raw.fif'),
-                           add_eeg_ref=False)
+    raw_bin = read_raw_fif(op.join(data_dir, 'test_bin_raw.fif'))
 
-    ch_py = raw_py._raw_extras[0]['sensor_locs'][:, :5]
+    ch_py = np.array([ch['loc'] for ch in
+                      raw_py._raw_extras[0]['channels'][:160]])
     # ch locs stored as m, not mm
     ch_py[:, :3] *= 1e3
     ch_sns = read_sns(op.join(data_dir, 'sns.txt'))
@@ -187,4 +228,34 @@ def test_hsp_elp():
     assert_array_almost_equal(pts_elp_in_dev, pts_txt_in_dev, decimal=5)
 
 
+def test_decimate():
+    """Test decimation of digitizer headshapes with too many points."""
+    # load headshape and convert to meters
+    hsp_mm = _get_ico_surface(5)['rr'] * 100
+    hsp_m = hsp_mm / 1000.
+
+    # save headshape to a file in mm in temporary directory
+    tempdir = _TempDir()
+    sphere_hsp_path = op.join(tempdir, 'test_sphere.txt')
+    np.savetxt(sphere_hsp_path, hsp_mm)
+
+    # read in raw data using spherical hsp, and extract new hsp
+    with warnings.catch_warnings(record=True) as w:
+        raw = read_raw_kit(sqd_path, mrk_path, elp_txt_path, sphere_hsp_path)
+    assert_true(any('more than' in str(ww.message) for ww in w))
+    # collect headshape from raw (should now be in m)
+    hsp_dec = np.array([dig['r'] for dig in raw.info['dig']])[8:]
+
+    # with 10242 points and _decimate_points set to resolution of 5 mm, hsp_dec
+    # should be a bit over 5000 points. If not, something is wrong or
+    # decimation resolution has been purposefully changed
+    assert_true(len(hsp_dec) > 5000)
+
+    # should have similar size, distance from center
+    dist = np.sqrt(np.sum((hsp_m - np.mean(hsp_m, axis=0))**2, axis=1))
+    dist_dec = np.sqrt(np.sum((hsp_dec - np.mean(hsp_dec, axis=0))**2, axis=1))
+    hsp_rad = np.mean(dist)
+    hsp_dec_rad = np.mean(dist_dec)
+    assert_almost_equal(hsp_rad, hsp_dec_rad, places=3)
+
 run_tests_if_main()
diff --git a/mne/io/matrix.py b/mne/io/matrix.py
index e636a65..2bee4c8 100644
--- a/mne/io/matrix.py
+++ b/mne/io/matrix.py
@@ -11,7 +11,7 @@ from ..utils import logger, verbose
 
 
 def _transpose_named_matrix(mat):
-    """Transpose mat inplace (no copy)"""
+    """Transpose mat inplace (no copy)."""
     mat['nrow'], mat['ncol'] = mat['ncol'], mat['nrow']
     mat['row_names'], mat['col_names'] = mat['col_names'], mat['row_names']
     mat['data'] = mat['data'].T
@@ -20,7 +20,7 @@ def _transpose_named_matrix(mat):
 @verbose
 def _read_named_matrix(fid, node, matkind, indent='    ', transpose=False,
                        verbose=None):
-    """Read named matrix from the given node
+    """Read named matrix from the given node.
 
     Parameters
     ----------
@@ -33,7 +33,8 @@ def _read_named_matrix(fid, node, matkind, indent='    ', transpose=False,
     transpose : bool
         If True, transpose the matrix. Default is False.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -89,7 +90,7 @@ def _read_named_matrix(fid, node, matkind, indent='    ', transpose=False,
 
 
 def write_named_matrix(fid, kind, mat):
-    """Write named matrix from the given node
+    """Write named matrix from the given node.
 
     Parameters
     ----------
diff --git a/mne/io/meas_info.py b/mne/io/meas_info.py
index f749640..5280b4b 100644
--- a/mne/io/meas_info.py
+++ b/mne/io/meas_info.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
 # Authors: Alexandre Gramfort <alexandre.gramfort at telecom-paristech.fr>
 #          Matti Hamalainen <msh at nmr.mgh.harvard.edu>
 #          Teon Brooks <teon.brooks at gmail.com>
@@ -6,7 +7,7 @@
 
 from collections import Counter
 from copy import deepcopy
-from datetime import datetime as dt
+import datetime
 import os.path as op
 import re
 
@@ -21,11 +22,12 @@ from .tag import read_tag, find_tag
 from .proj import _read_proj, _write_proj, _uniquify_projs, _normalize_proj
 from .ctf_comp import read_ctf_comp, write_ctf_comp
 from .write import (start_file, end_file, start_block, end_block,
-                    write_string, write_dig_point, write_float, write_int,
+                    write_string, write_dig_points, write_float, write_int,
                     write_coord_trans, write_ch_info, write_name_list,
                     write_julian, write_float_matrix)
 from .proc_history import _read_proc_history, _write_proc_history
-from ..utils import logger, verbose, warn
+from ..transforms import _to_const
+from ..utils import logger, verbose, warn, object_diff
 from .. import __version__
 from ..externals.six import b, BytesIO, string_types, text_type
 
@@ -40,6 +42,7 @@ _kind_dict = dict(
     stim=(FIFF.FIFFV_STIM_CH, FIFF.FIFFV_COIL_NONE, FIFF.FIFF_UNIT_V),
     eog=(FIFF.FIFFV_EOG_CH, FIFF.FIFFV_COIL_NONE, FIFF.FIFF_UNIT_V),
     ecg=(FIFF.FIFFV_ECG_CH, FIFF.FIFFV_COIL_NONE, FIFF.FIFF_UNIT_V),
+    emg=(FIFF.FIFFV_EMG_CH, FIFF.FIFFV_COIL_NONE, FIFF.FIFF_UNIT_V),
     seeg=(FIFF.FIFFV_SEEG_CH, FIFF.FIFFV_COIL_EEG, FIFF.FIFF_UNIT_V),
     bio=(FIFF.FIFFV_BIO_CH, FIFF.FIFFV_COIL_NONE, FIFF.FIFF_UNIT_V),
     ecog=(FIFF.FIFFV_ECOG_CH, FIFF.FIFFV_COIL_EEG, FIFF.FIFF_UNIT_V),
@@ -49,116 +52,321 @@ _kind_dict = dict(
 
 
 def _summarize_str(st):
-    """Aux function"""
+    """Make summary string."""
     return st[:56][::-1].split(',', 1)[-1][::-1] + ', ...'
 
 
+def _stamp_to_dt(stamp):
+    """Convert timestamp to datetime object in Windows-friendly way."""
+    # The min on windows is 86400
+    stamp = [int(s) for s in stamp]
+    if len(stamp) == 1:  # In case there is no microseconds information
+        stamp.append(0)
+    return (datetime.datetime.utcfromtimestamp(stamp[0]) +
+            datetime.timedelta(0, 0, stamp[1]))  # day, sec, μs
+
+
+# XXX Eventually this should be de-duplicated with the MNE-MATLAB stuff...
 class Info(dict):
-    """Information about the recording.
+    """Measurement information.
 
-    This data structure behaves like a dictionary. It contains all meta-data
+    This data structure behaves like a dictionary. It contains all metadata
     that is available for a recording.
 
-    The attributes listed below are the possible dictionary entries:
+    This class should not be instantiated directly. To create a measurement
+    information strucure, use :func:`mne.create_info`.
 
-    Attributes
+    The only entries that should be manually changed by the user are
+    ``info['bads']`` and ``info['description']``. All other entries should
+    be considered read-only, or should be modified by functions or methods.
+
+    Parameters
     ----------
+    acq_pars : str | None
+        MEG system acquition parameters.
+        See :class:`mne.AcqParserFIF` for details.
+    acq_stim : str | None
+        MEG system stimulus parameters.
     bads : list of str
         List of bad (noisy/broken) channels, by name. These channels will by
         default be ignored by many processing steps.
-    ch_names : list-like of str (read-only)
+    buffer_size_sec : float | None
+        Buffer size (in seconds) when reading the raw data in chunks.
+    ch_names : list of str
         The names of the channels.
-        This object behaves like a read-only Python list. Behind the scenes
-        it iterates over the channels dictionaries in `info['chs']`:
-        `info['ch_names'][x] == info['chs'][x]['ch_name']`
     chs : list of dict
-        A list of channel information structures.
-        See: :ref:`faq` for details.
+        A list of channel information dictionaries, one per channel.
+        See Notes for more information.
     comps : list of dict
         CTF software gradient compensation data.
-        See: :ref:`faq` for details.
+        See Notes for more information.
+    ctf_head_t : dict | None
+        The transformation from 4D/CTF head coordinates to Neuromag head
+        coordinates. This is only present in 4D/CTF data.
     custom_ref_applied : bool
         Whether a custom (=other than average) reference has been applied to
         the EEG data. This flag is checked by some algorithms that require an
         average reference to be set.
-    events : list of dict
-        Event list, usually extracted from the stim channels.
-        See: :ref:`faq` for details.
-    hpi_results : list of dict
-        Head position indicator (HPI) digitization points and fit information
-        (e.g., the resulting transform). See: :ref:`faq` for details.
-    meas_date : list of int
-        The first element of this list is a POSIX timestamp (milliseconds since
-        1970-01-01 00:00:00) denoting the date and time at which the
-        measurement was taken. The second element is the number of
-        microseconds.
-    nchan : int
-        Number of channels.
-    projs : list of dict
-        List of SSP operators that operate on the data.
-        See: :ref:`faq` for details.
-    sfreq : float
-        Sampling frequency in Hertz.
-        See: :ref:`faq` for details.
-    acq_pars : str | None
-        MEG system acquition parameters.
-    acq_stim : str | None
-        MEG system stimulus parameters.
-    buffer_size_sec : float | None
-        Buffer size (in seconds) when reading the raw data in chunks.
-    ctf_head_t : dict | None
-        The transformation from 4D/CTF head coordinates to Neuromag head
-        coordinates. This is only present in 4D/CTF data.
-        See: :ref:`faq` for details.
     description : str | None
         String description of the recording.
     dev_ctf_t : dict | None
         The transformation from device coordinates to 4D/CTF head coordinates.
         This is only present in 4D/CTF data.
-        See: :ref:`faq` for details.
     dev_head_t : dict | None
         The device to head transformation.
-        See: :ref:`faq` for details.
     dig : list of dict | None
         The Polhemus digitization data in head coordinates.
-        See: :ref:`faq` for details.
-    experimentor : str | None
+        See Notes for more information.
+    events : list of dict
+        Event list, sometimes extracted from the stim channels by Neuromag
+        systems. In general this should not be used and
+        :func:`mne.find_events` should be used for event processing.
+        See Notes for more information.
+    experimenter : str | None
         Name of the person that ran the experiment.
     file_id : dict | None
-        The fif ID datastructure of the measurement file.
-        See: :ref:`faq` for details.
-    filename : str | None
-        The name of the file that provided the raw data.
+        The FIF globally unique ID. See Notes for more information.
     highpass : float | None
         Highpass corner frequency in Hertz. Zero indicates a DC recording.
-    hpi_meas : list of dict | None
+    hpi_meas : list of dict
         HPI measurements that were taken at the start of the recording
         (e.g. coil frequencies).
+        See Notes for details.
+    hpi_results : list of dict
+        Head position indicator (HPI) digitization points and fit information
+        (e.g., the resulting transform).
+        See Notes for details.
     hpi_subsystem : dict | None
         Information about the HPI subsystem that was used (e.g., event
         channel used for cHPI measurements).
+        See Notes for details.
     line_freq : float | None
         Frequency of the power line in Hertz.
     lowpass : float | None
         Lowpass corner frequency in Hertz.
+    meas_date : list of int
+        The first element of this list is a POSIX timestamp (milliseconds since
+        1970-01-01 00:00:00) denoting the date and time at which the
+        measurement was taken. The second element is the number of
+        microseconds.
     meas_id : dict | None
-        The ID assigned to this measurement by the acquisition system or during
-        file conversion.
-        See: :ref:`faq` for details.
+        The ID assigned to this measurement by the acquisition system or
+        during file conversion. Follows the same format as ``file_id``.
+    nchan : int
+        Number of channels.
+    proc_history : list of dict
+        The MaxFilter processing history.
+        See Notes for details.
     proj_id : int | None
         ID number of the project the experiment belongs to.
     proj_name : str | None
         Name of the project the experiment belongs to.
+    projs : list of Projection
+        List of SSP operators that operate on the data.
+        See :class:`mne.Projection` for details.
+    sfreq : float
+        Sampling frequency in Hertz.
     subject_info : dict | None
         Information about the subject.
-    proc_history : list of dict | None | not present in dict
-        The SSS info, the CTC correction and the calibaraions from the SSS
-        processing logs inside of a raw file.
-        See: :ref:`faq` for details.
+        See Notes for details.
+
+    See Also
+    --------
+    mne.create_info
+
+    Notes
+    -----
+    The following parameters have a nested structure.
+
+    * ``chs`` list of dict:
+
+        cal : float
+            The calibration factor to bring the channels to physical
+            units. Used in product with ``range`` to scale the data read
+            from disk.
+        ch_name : str
+            The channel name.
+        coil_type : int
+            Coil type, e.g. ``FIFFV_COIL_MEG``.
+        coord_frame : int
+            The coordinate frame used, e.g. ``FIFFV_COORD_HEAD``.
+        kind : int
+            The kind of channel, e.g. ``FIFFV_EEG_CH``.
+        loc : array, shape (12,)
+            Channel location. For MEG this is the position plus the
+            normal given by a 3x3 rotation matrix. For EEG this is the
+            position followed by reference position (with 6 unused).
+            The values are specified in device coordinates for MEG and in
+            head coordinates for EEG channels, respectively.
+        logno : int
+            Logical channel number, conventions in the usage of this
+            number vary.
+        range : float
+            The hardware-oriented part of the calibration factor.
+            This should be only applied to the continuous raw data.
+            Used in product with ``cal`` to scale data read from disk.
+        scanno : int
+            Scanning order number, starting from 1.
+        unit : int
+            The unit to use, e.g. ``FIFF_UNIT_T_M``.
+        unit_mul : int
+            Unit multipliers, most commontly ``FIFF_UNITM_NONE``.
+
+    * ``comps`` list of dict:
+
+        ctfkind : int
+            CTF compensation grade.
+        colcals : ndarray
+            Column calibrations.
+        mat : dict
+            A named matrix dictionary (with entries "data", "col_names", etc.)
+            containing the compensation matrix.
+        rowcals : ndarray
+            Row calibrations.
+        save_calibrated : bool
+            Were the compensation data saved in calibrated form.
+
+    * ``dig`` dict:
+
+        kind : int
+            Digitization kind, e.g. ``FIFFV_POINT_EXTRA``.
+        ident : int
+            Identifier.
+        r : ndarary, shape (3,)
+            Position.
+        coord_frame : int
+            Coordinate frame, e.g. ``FIFFV_COORD_HEAD``.
+
+    * ``events`` list of dict:
+
+        channels : list of int
+            Channel indices for the events.
+        list : ndarray, shape (n_events * 3,)
+            Events in triplets as number of samples, before, after.
+
+    * ``file_id`` dict:
+
+        version : int
+            FIF format version, i.e. ``FIFFC_VERSION``.
+        machid : ndarray, shape (2,)
+            Unique machine ID, usually derived from the MAC address.
+        secs : int
+            Time in seconds.
+        usecs : int
+            Time in microseconds.
+
+    * ``hpi_meas`` list of dict:
+
+        creator : str
+            Program that did the measurement.
+        sfreq : float
+            Sample rate.
+        nchan : int
+            Number of channels used.
+        nave : int
+            Number of averages used.
+        ncoil : int
+            Number of coils used.
+        first_samp : int
+            First sample used.
+        last_samp : int
+            Last sample used.
+        hpi_coils : list of dict
+            Coils, containing:
+
+                number: int
+                    Coil number
+                epoch : ndarray
+                    Buffer containing one epoch and channel.
+                slopes : ndarray, shape (n_channels,)
+                    HPI data.
+                corr_coeff : ndarray, shape (n_channels,)
+                    HPI curve fit correlations.
+                coil_freq : float
+                    HPI coil excitation frequency
+
+    * ``hpi_results`` list of dict:
+
+        dig_points : list
+            Digitization points (see ``dig`` definition) for the HPI coils.
+        order : ndarray, shape (ncoil,)
+            The determined digitization order.
+        used : ndarray, shape (nused,)
+            The indices of the used coils.
+        moments : ndarray, shape (ncoil, 3)
+            The coil moments.
+        goodness : ndarray, shape (ncoil,)
+            The goodness of fits.
+        good_limit : float
+            The goodness of fit limit.
+        dist_limit : float
+            The distance limit.
+        accept : int
+            Whether or not the fit was accepted.
+        coord_trans : instance of Transformation
+            The resulting MEG<->head transformation.
+
+    * ``hpi_subsystem`` dict:
+
+        ncoil : int
+            The number of coils.
+        event_channel : str
+            The event channel used to encode cHPI status (e.g., STI201).
+        hpi_coils : list of ndarray
+            List of length ``ncoil``, each 4-element ndarray contains the
+            event bits used on the event channel to indicate cHPI status
+            (using the first element of these arrays is typically
+            sufficient).
+
+    * ``proc_history`` list of dict:
+
+        block_id : dict
+            See ``id`` above.
+        date : ndarray, shape (2,)
+            2-element tuple of seconds and microseconds.
+        experimenter : str
+            Name of the person who ran the program.
+        creator : str
+            Program that did the processing.
+        max_info : dict
+            Maxwel filtering info, can contain:
+
+                sss_info : dict
+                    SSS processing information.
+                max_st
+                    tSSS processing information.
+                sss_ctc : dict
+                    Cross-talk processing information.
+                sss_cal : dict
+                    Fine-calibration information.
+        smartshield : dict
+            MaxShield information. This dictionary is (always?) empty,
+            but its presence implies that MaxShield was used during
+            acquisiton.
+
+    * ``subject_info`` dict:
+
+        id : int
+            Integer subject identifier.
+        his_id : str
+            String subject identifier.
+        last_name : str
+            Last name.
+        first_name : str
+            First name.
+        middle_name : str
+            Middle name.
+        birthday : tuple of int
+            Birthday in (year, month, day) format.
+        sex : int
+            Subject sex (0=unknown, 1=male, 2=female).
+        hand : int
+            Handedness (1=right, 2=left).
+
     """
 
     def copy(self):
-        """Copy the instance
+        """Copy the instance.
 
         Returns
         -------
@@ -168,7 +376,7 @@ class Info(dict):
         return Info(deepcopy(self))
 
     def normalize_proj(self):
-        """(Re-)Normalize projection vectors after subselection
+        """(Re-)Normalize projection vectors after subselection.
 
         Applying projection after sub-selecting a set of channels that
         were originally used to compute the original projection vectors
@@ -184,19 +392,16 @@ class Info(dict):
         _normalize_proj(self)
 
     def __repr__(self):
-        """Summarize info instead of printing all"""
+        """Summarize info instead of printing all."""
         strs = ['<Info | %s non-empty fields']
         non_empty = 0
         for k, v in self.items():
             if k in ['bads', 'ch_names']:
                 entr = (', '.join(b for ii, b in enumerate(v) if ii < 10)
                         if v else '0 items')
-                if len(entr) >= 56:
+                if len(v) > 10:
                     # get rid of of half printed ch names
                     entr = _summarize_str(entr)
-            elif k == 'filename' and v:
-                path, fname = op.split(v)
-                entr = path[:10] + '.../' + fname
             elif k == 'projs' and v:
                 entr = ', '.join(p['desc'] + ': o%s' %
                                  {0: 'ff', 1: 'n'}[p['active']] for p in v)
@@ -204,9 +409,9 @@ class Info(dict):
                     entr = _summarize_str(entr)
             elif k == 'meas_date' and np.iterable(v):
                 # first entry in meas_date is meaningful
-                entr = dt.fromtimestamp(v[0]).strftime('%Y-%m-%d %H:%M:%S')
+                entr = _stamp_to_dt(v).strftime('%Y-%m-%d %H:%M:%S') + ' GMT'
             elif k == 'kit_system_id' and v is not None:
-                from .kit.constants import SYSNAMES as KIT_SYSNAMES
+                from .kit.constants import KIT_SYSNAMES
                 entr = '%i (%s)' % (v, KIT_SYSNAMES.get(v, 'unknown'))
             else:
                 this_len = (len(v) if hasattr(v, '__len__') else
@@ -222,7 +427,7 @@ class Info(dict):
                 entr += " (%s)" % ', '.join("%s: %d" % (ch_type.upper(), count)
                                             for ch_type, count
                                             in ch_counts.items())
-            strs.append('%s : %s%s' % (k, str(type(v))[7:-2], entr))
+            strs.append('%s : %s%s' % (k, type(v).__name__, entr))
             if k in ['sfreq', 'lowpass', 'highpass']:
                 strs[-1] += ' Hz'
         strs_non_empty = sorted(s for s in strs if '|' in s)
@@ -233,7 +438,7 @@ class Info(dict):
         return st
 
     def _check_consistency(self):
-        """Do some self-consistency checks and datatype tweaks"""
+        """Do some self-consistency checks and datatype tweaks."""
         missing = [bad for bad in self['bads'] if bad not in self['ch_names']]
         if len(missing) > 0:
             raise RuntimeError('bad channel(s) %s marked do not exist in info'
@@ -251,22 +456,61 @@ class Info(dict):
             if self.get(key) is not None:
                 self[key] = float(self[key])
 
+        # make sure channel names are not too long
+        self._check_ch_name_length()
+
         # make sure channel names are unique
         unique_ids = np.unique(self['ch_names'], return_index=True)[1]
         if len(unique_ids) != self['nchan']:
             dups = set(self['ch_names'][x]
                        for x in np.setdiff1d(range(self['nchan']), unique_ids))
-            raise RuntimeError('Channel names are not unique, found '
-                               'duplicates for: %s' % dups)
+            warn('Channel names are not unique, found duplicates for: '
+                 '%s. Applying running numbers for duplicates.' % dups)
+            for ch_stem in dups:
+                overlaps = np.where(np.array(self['ch_names']) == ch_stem)[0]
+                n_keep = min(len(ch_stem),
+                             14 - int(np.ceil(np.log10(len(overlaps)))))
+                ch_stem = ch_stem[:n_keep]
+                for idx, ch_idx in enumerate(overlaps):
+                    ch_name = ch_stem + '-%s' % idx
+                    assert ch_name not in self['ch_names']
+                    self['ch_names'][ch_idx] = ch_name
+                    self['chs'][ch_idx]['ch_name'] = ch_name
+
+        if 'filename' in self:
+            warn('the "filename" key is misleading\
+                 and info should not have it')
+
+    def _check_ch_name_length(self):
+        """Check that channel names are sufficiently short."""
+        bad_names = list()
+        for ch in self['chs']:
+            if len(ch['ch_name']) > 15:
+                bad_names.append(ch['ch_name'])
+                ch['ch_name'] = ch['ch_name'][:15]
+        if len(bad_names) > 0:
+            warn('%d channel names are too long, have been truncated to 15 '
+                 'characters:\n%s' % (len(bad_names), bad_names))
+            self._update_redundant()
 
     def _update_redundant(self):
-        """Update the redundant entries"""
+        """Update the redundant entries."""
         self['ch_names'] = [ch['ch_name'] for ch in self['chs']]
         self['nchan'] = len(self['chs'])
 
 
+def _simplify_info(info):
+    """Return a simplified info structure to speed up picking."""
+    chs = [{key: ch[key] for key in ('ch_name', 'kind', 'unit', 'coil_type',
+                                     'loc')}
+           for ch in info['chs']]
+    sub_info = Info(chs=chs, bads=info['bads'], comps=info['comps'])
+    sub_info._update_redundant()
+    return sub_info
+
+
 def read_fiducials(fname):
-    """Read fiducials from a fiff file
+    """Read fiducials from a fiff file.
 
     Parameters
     ----------
@@ -286,7 +530,7 @@ def read_fiducials(fname):
         isotrak = dir_tree_find(tree, FIFF.FIFFB_ISOTRAK)
         isotrak = isotrak[0]
         pts = []
-        coord_frame = FIFF.FIFFV_COORD_UNKNOWN
+        coord_frame = FIFF.FIFFV_COORD_HEAD
         for k in range(isotrak['nent']):
             kind = isotrak['directory'][k].kind
             pos = isotrak['directory'][k].pos
@@ -297,11 +541,6 @@ def read_fiducials(fname):
                 tag = read_tag(fid, pos)
                 coord_frame = tag.data[0]
 
-    if coord_frame == FIFF.FIFFV_COORD_UNKNOWN:
-        err = ("No coordinate frame was found in the file %r, it is probably "
-               "not a valid fiducials file." % fname)
-        raise ValueError(err)
-
     # coord_frame is not stored in the tag
     for pt in pts:
         pt['coord_frame'] = coord_frame
@@ -309,8 +548,8 @@ def read_fiducials(fname):
     return pts, coord_frame
 
 
-def write_fiducials(fname, pts, coord_frame=0):
-    """Write fiducials to a fiff file
+def write_fiducials(fname, pts, coord_frame=FIFF.FIFFV_COORD_UNKNOWN):
+    """Write fiducials to a fiff file.
 
     Parameters
     ----------
@@ -321,27 +560,42 @@ def write_fiducials(fname, pts, coord_frame=0):
         the keys 'kind', 'ident' and 'r'.
     coord_frame : int
         The coordinate frame of the points (one of
-        mne.io.constants.FIFF.FIFFV_COORD_...)
+        mne.io.constants.FIFF.FIFFV_COORD_...).
     """
-    pts_frames = set((pt.get('coord_frame', coord_frame) for pt in pts))
-    bad_frames = pts_frames - set((coord_frame,))
-    if len(bad_frames) > 0:
-        err = ("Points have coord_frame entries that are incompatible with "
-               "coord_frame=%i: %s." % (coord_frame, str(tuple(bad_frames))))
-        raise ValueError(err)
+    write_dig(fname, pts, coord_frame)
 
-    fid = start_file(fname)
-    start_block(fid, FIFF.FIFFB_ISOTRAK)
-    write_int(fid, FIFF.FIFF_MNE_COORD_FRAME, coord_frame)
-    for pt in pts:
-        write_dig_point(fid, pt)
 
-    end_block(fid, FIFF.FIFFB_ISOTRAK)
-    end_file(fid)
+def write_dig(fname, pts, coord_frame=None):
+    """Write digitization data to a FIF file.
+
+    Parameters
+    ----------
+    fname : str
+        Destination file name.
+    pts : iterator of dict
+        Iterator through digitizer points. Each point is a dictionary with
+        the keys 'kind', 'ident' and 'r'.
+    coord_frame : int | str | None
+        If all the points have the same coordinate frame, specify the type
+        here. Can be None (default) if the points could have varying
+        coordinate frames.
+    """
+    if coord_frame is not None:
+        coord_frame = _to_const(coord_frame)
+        pts_frames = set((pt.get('coord_frame', coord_frame) for pt in pts))
+        bad_frames = pts_frames - set((coord_frame,))
+        if len(bad_frames) > 0:
+            raise ValueError(
+                'Points have coord_frame entries that are incompatible with '
+                'coord_frame=%i: %s.' % (coord_frame, str(tuple(bad_frames))))
+
+    with start_file(fname) as fid:
+        write_dig_points(fid, pts, block=True, coord_frame=coord_frame)
+        end_file(fid)
 
 
 def _read_dig_fif(fid, meas_info):
-    """Helper to read digitizer data from a FIFF file"""
+    """Read digitizer data from a FIFF file."""
     isotrak = dir_tree_find(meas_info, FIFF.FIFFB_ISOTRAK)
     dig = None
     if len(isotrak) == 0:
@@ -370,7 +624,8 @@ def _read_dig_points(fname, comments='%', unit='auto'):
     Parameters
     ----------
     fname : str
-        The filepath of space delimited file with points.
+        The filepath of space delimited file with points, or a .mat file
+        (Polhemus FastTrak format).
     comments : str
         The character used to indicate the start of a comment;
         Default: '%'.
@@ -392,17 +647,24 @@ def _read_dig_points(fname, comments='%', unit='auto'):
     if ext == '.elp' or ext == '.hsp':
         with open(fname) as fid:
             file_str = fid.read()
-        value_pattern = "\-?\d+\.?\d*e?\-?\d*"
-        coord_pattern = "({0})\s+({0})\s+({0})\s*$".format(value_pattern)
+        value_pattern = r"\-?\d+\.?\d*e?\-?\d*"
+        coord_pattern = r"({0})\s+({0})\s+({0})\s*$".format(value_pattern)
         if ext == '.hsp':
             coord_pattern = '^' + coord_pattern
         points_str = [m.groups() for m in re.finditer(coord_pattern, file_str,
                                                       re.MULTILINE)]
         dig_points = np.array(points_str, dtype=float)
+    elif ext == '.mat':  # like FastScan II
+        from scipy.io import loadmat
+        dig_points = loadmat(fname)['Points'].T
     else:
         dig_points = np.loadtxt(fname, comments=comments, ndmin=2)
         if unit == 'auto':
             unit = 'mm'
+        if dig_points.shape[1] > 3:
+            warn('Found %d columns instead of 3, using first 3 for XYZ '
+                 'coordinates' % (dig_points.shape[1],))
+            dig_points = dig_points[:, :3]
 
     if dig_points.shape[-1] != 3:
         err = 'Data must be (n, 3) instead of %s' % (dig_points.shape,)
@@ -417,7 +679,7 @@ def _read_dig_points(fname, comments='%', unit='auto'):
 
 
 def _write_dig_points(fname, dig_points):
-    """Write points to text file
+    """Write points to text file.
 
     Parameters
     ----------
@@ -437,7 +699,7 @@ def _write_dig_points(fname, dig_points):
     if ext == '.txt':
         with open(fname, 'wb') as fid:
             version = __version__
-            now = dt.now().strftime("%I:%M%p on %B %d, %Y")
+            now = datetime.datetime.now().strftime("%I:%M%p on %B %d, %Y")
             fid.write(b("% Ascii 3D points file created by mne-python version "
                         "{version} at {now}\n".format(version=version,
                                                       now=now)))
@@ -450,8 +712,8 @@ def _write_dig_points(fname, dig_points):
 
 
 def _make_dig_points(nasion=None, lpa=None, rpa=None, hpi=None,
-                     dig_points=None, dig_ch_pos=None):
-    """Constructs digitizer info for the info.
+                     extra_points=None, dig_ch_pos=None):
+    """Construct digitizer info for the info.
 
     Parameters
     ----------
@@ -463,7 +725,7 @@ def _make_dig_points(nasion=None, lpa=None, rpa=None, hpi=None,
         Point designated as the right auricular point.
     hpi : array-like | numpy.ndarray, shape (n_points, 3) | None
         Points designated as head position indicator points.
-    dig_points : array-like | numpy.ndarray, shape (n_points, 3)
+    extra_points : array-like | numpy.ndarray, shape (n_points, 3)
         Points designed as the headshape points.
     dig_ch_pos : dict
         Dict of EEG channel positions.
@@ -476,60 +738,54 @@ def _make_dig_points(nasion=None, lpa=None, rpa=None, hpi=None,
     dig = []
     if lpa is not None:
         lpa = np.asarray(lpa)
-        if lpa.shape == (3,):
-            dig.append({'r': lpa, 'ident': FIFF.FIFFV_POINT_LPA,
-                        'kind': FIFF.FIFFV_POINT_CARDINAL,
-                        'coord_frame': FIFF.FIFFV_COORD_HEAD})
-        else:
-            msg = ('LPA should have the shape (3,) instead of %s'
-                   % (lpa.shape,))
-            raise ValueError(msg)
+        if lpa.shape != (3,):
+            raise ValueError('LPA should have the shape (3,) instead of %s'
+                             % (lpa.shape,))
+        dig.append({'r': lpa, 'ident': FIFF.FIFFV_POINT_LPA,
+                    'kind': FIFF.FIFFV_POINT_CARDINAL,
+                    'coord_frame': FIFF.FIFFV_COORD_HEAD})
     if nasion is not None:
         nasion = np.asarray(nasion)
-        if nasion.shape == (3,):
-            dig.append({'r': nasion, 'ident': FIFF.FIFFV_POINT_NASION,
-                        'kind': FIFF.FIFFV_POINT_CARDINAL,
-                        'coord_frame': FIFF.FIFFV_COORD_HEAD})
-        else:
-            msg = ('Nasion should have the shape (3,) instead of %s'
-                   % (nasion.shape,))
-            raise ValueError(msg)
+        if nasion.shape != (3,):
+            raise ValueError('Nasion should have the shape (3,) instead of %s'
+                             % (nasion.shape,))
+        dig.append({'r': nasion, 'ident': FIFF.FIFFV_POINT_NASION,
+                    'kind': FIFF.FIFFV_POINT_CARDINAL,
+                    'coord_frame': FIFF.FIFFV_COORD_HEAD})
     if rpa is not None:
         rpa = np.asarray(rpa)
-        if rpa.shape == (3,):
-            dig.append({'r': rpa, 'ident': FIFF.FIFFV_POINT_RPA,
-                        'kind': FIFF.FIFFV_POINT_CARDINAL,
-                        'coord_frame': FIFF.FIFFV_COORD_HEAD})
-        else:
-            msg = ('RPA should have the shape (3,) instead of %s'
-                   % (rpa.shape,))
-            raise ValueError(msg)
+        if rpa.shape != (3,):
+            raise ValueError('RPA should have the shape (3,) instead of %s'
+                             % (rpa.shape,))
+        dig.append({'r': rpa, 'ident': FIFF.FIFFV_POINT_RPA,
+                    'kind': FIFF.FIFFV_POINT_CARDINAL,
+                    'coord_frame': FIFF.FIFFV_COORD_HEAD})
     if hpi is not None:
         hpi = np.asarray(hpi)
-        if hpi.shape[1] == 3:
-            for idx, point in enumerate(hpi):
-                dig.append({'r': point, 'ident': idx + 1,
-                            'kind': FIFF.FIFFV_POINT_HPI,
-                            'coord_frame': FIFF.FIFFV_COORD_HEAD})
-        else:
-            msg = ('HPI should have the shape (n_points, 3) instead of '
-                   '%s' % (hpi.shape,))
-            raise ValueError(msg)
-    if dig_points is not None:
-        dig_points = np.asarray(dig_points)
-        if dig_points.shape[1] == 3:
-            for idx, point in enumerate(dig_points):
-                dig.append({'r': point, 'ident': idx + 1,
-                            'kind': FIFF.FIFFV_POINT_EXTRA,
-                            'coord_frame': FIFF.FIFFV_COORD_HEAD})
-        else:
-            msg = ('Points should have the shape (n_points, 3) instead of '
-                   '%s' % (dig_points.shape,))
-            raise ValueError(msg)
+        if hpi.ndim != 2 or hpi.shape[1] != 3:
+            raise ValueError('HPI should have the shape (n_points, 3) instead '
+                             'of %s' % (hpi.shape,))
+        for idx, point in enumerate(hpi):
+            dig.append({'r': point, 'ident': idx + 1,
+                        'kind': FIFF.FIFFV_POINT_HPI,
+                        'coord_frame': FIFF.FIFFV_COORD_HEAD})
+    if extra_points is not None:
+        extra_points = np.asarray(extra_points)
+        if extra_points.shape[1] != 3:
+            raise ValueError('Points should have the shape (n_points, 3) '
+                             'instead of %s' % (extra_points.shape,))
+        for idx, point in enumerate(extra_points):
+            dig.append({'r': point, 'ident': idx + 1,
+                        'kind': FIFF.FIFFV_POINT_EXTRA,
+                        'coord_frame': FIFF.FIFFV_COORD_HEAD})
     if dig_ch_pos is not None:
         keys = sorted(dig_ch_pos.keys())
-        for key in keys:
-            dig.append({'r': dig_ch_pos[key], 'ident': int(key[-3:]),
+        try:  # use the last 3 as int if possible (e.g., EEG001->1)
+            idents = [int(key[-3:]) for key in keys]
+        except ValueError:  # and if any conversion fails, simply use arange
+            idents = np.arange(1, len(keys) + 1)
+        for key, ident in zip(keys, idents):
+            dig.append({'r': dig_ch_pos[key], 'ident': ident,
                         'kind': FIFF.FIFFV_POINT_EEG,
                         'coord_frame': FIFF.FIFFV_COORD_HEAD})
     return dig
@@ -537,14 +793,15 @@ def _make_dig_points(nasion=None, lpa=None, rpa=None, hpi=None,
 
 @verbose
 def read_info(fname, verbose=None):
-    """Read measurement info from a file
+    """Read measurement info from a file.
 
     Parameters
     ----------
     fname : str
         File name.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -558,7 +815,7 @@ def read_info(fname, verbose=None):
 
 
 def read_bad_channels(fid, node):
-    """Read bad channels
+    """Read bad channels.
 
     Parameters
     ----------
@@ -586,7 +843,7 @@ def read_bad_channels(fid, node):
 
 @verbose
 def read_meas_info(fid, tree, clean_bads=False, verbose=None):
-    """Read the measurement info
+    """Read the measurement info.
 
     Parameters
     ----------
@@ -599,7 +856,8 @@ def read_meas_info(fid, tree, clean_bads=False, verbose=None):
         Should only be needed for old files where we did not check bads
         before saving.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -608,7 +866,6 @@ def read_meas_info(fid, tree, clean_bads=False, verbose=None):
     meas : dict
         Node in tree that contains the info.
     """
-
     #   Find the desired blocks
     meas = dir_tree_find(tree, FIFF.FIFFB_MEAS)
     if len(meas) == 0:
@@ -921,7 +1178,7 @@ def read_meas_info(fid, tree, clean_bads=False, verbose=None):
     info['hpi_subsystem'] = hs
 
     #   Read processing history
-    _read_proc_history(fid, tree, info)
+    info['proc_history'] = _read_proc_history(fid, tree)
 
     #  Make the most appropriate selection for the measurement id
     if meas_info['parent_id'] is None:
@@ -986,7 +1243,7 @@ def read_meas_info(fid, tree, clean_bads=False, verbose=None):
 
 
 def write_meas_info(fid, info, data_type=None, reset_range=True):
-    """Write measurement info into a file id (from a fif file)
+    """Write measurement info into a file id (from a fif file).
 
     Parameters
     ----------
@@ -1021,8 +1278,7 @@ def write_meas_info(fid, info, data_type=None, reset_range=True):
     #   HPI Result
     for hpi_result in info['hpi_results']:
         start_block(fid, FIFF.FIFFB_HPI_RESULT)
-        for d in hpi_result['dig_points']:
-            write_dig_point(fid, d)
+        write_dig_points(fid, hpi_result['dig_points'])
         if 'order' in hpi_result:
             write_int(fid, FIFF.FIFF_HPI_DIGITIZATION_ORDER,
                       hpi_result['order'])
@@ -1081,12 +1337,7 @@ def write_meas_info(fid, info, data_type=None, reset_range=True):
         end_block(fid, FIFF.FIFFB_HPI_MEAS)
 
     #   Polhemus data
-    if info['dig'] is not None:
-        start_block(fid, FIFF.FIFFB_ISOTRAK)
-        for d in info['dig']:
-            write_dig_point(fid, d)
-
-        end_block(fid, FIFF.FIFFB_ISOTRAK)
+    write_dig_points(fid, info['dig'], block=True)
 
     #   megacq parameters
     if info['acq_pars'] is not None or info['acq_stim'] is not None:
@@ -1228,26 +1479,9 @@ def write_info(fname, info, data_type=None, reset_range=True):
     end_file(fid)
 
 
-def _is_equal_dict(dicts):
-    """Aux function"""
-    tests = zip(*[d.items() for d in dicts])
-    is_equal = []
-    for d in tests:
-        k0, v0 = d[0]
-        if (isinstance(v0, (list, np.ndarray)) and len(v0) > 0 and
-                isinstance(v0[0], dict)):
-            for k, v in d:
-                is_equal.append((k0 == k) and _is_equal_dict(v))
-        else:
-            is_equal.append(all(np.all(k == k0) and
-                            (np.array_equal(v, v0) if isinstance(v, np.ndarray)
-                             else np.all(v == v0)) for k, v in d))
-    return all(is_equal)
-
-
 @verbose
 def _merge_dict_values(dicts, key, verbose=None):
-    """Merge things together
+    """Merge things together.
 
     Fork for {'dict', 'list', 'array', 'other'}
     and consider cases where one or all are of the same type.
@@ -1263,7 +1497,7 @@ def _merge_dict_values(dicts, key, verbose=None):
         return func([isinstance(v, kind) for v in values])
 
     def _where_isinstance(values, kind):
-        """Aux function"""
+        """Get indices of instances."""
         return np.where([isinstance(v, type) for v in values])[0]
 
     # list
@@ -1280,7 +1514,7 @@ def _merge_dict_values(dicts, key, verbose=None):
             return _flatten(lists)
     # dict
     elif _check_isinstance(values, dict, all):
-        is_qual = _is_equal_dict(values)
+        is_qual = all(object_diff(values[0], v) == '' for v in values[1:])
         if is_qual:
             return values[0]
         else:
@@ -1347,7 +1581,8 @@ def _merge_info(infos, force_update_to_first=False, verbose=None):
         to match those in the first item. Use at your own risk, as this
         may overwrite important metadata.
     verbose : bool, str, int, or NonIe
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -1400,11 +1635,11 @@ def _merge_info(infos, force_update_to_first=False, verbose=None):
     # other fields
     other_fields = ['acq_pars', 'acq_stim', 'bads', 'buffer_size_sec',
                     'comps', 'custom_ref_applied', 'description', 'dig',
-                    'experimenter', 'file_id', 'filename', 'highpass',
+                    'experimenter', 'file_id', 'highpass',
                     'hpi_results', 'hpi_meas', 'hpi_subsystem', 'events',
                     'line_freq', 'lowpass', 'meas_date', 'meas_id',
                     'proj_id', 'proj_name', 'projs', 'sfreq',
-                    'subject_info', 'sfreq', 'xplotter_layout']
+                    'subject_info', 'sfreq', 'xplotter_layout', 'proc_history']
     for k in other_fields:
         info[k] = _merge_dict_values(infos, k)
 
@@ -1412,8 +1647,9 @@ def _merge_info(infos, force_update_to_first=False, verbose=None):
     return info
 
 
-def create_info(ch_names, sfreq, ch_types=None, montage=None):
-    """Create a basic Info instance suitable for use with create_raw
+ at verbose
+def create_info(ch_names, sfreq, ch_types=None, montage=None, verbose=None):
+    """Create a basic Info instance suitable for use with create_raw.
 
     Parameters
     ----------
@@ -1425,7 +1661,7 @@ def create_info(ch_names, sfreq, ch_types=None, montage=None):
     ch_types : list of str | str
         Channel types. If None, data are assumed to be misc.
         Currently supported fields are 'ecg', 'bio', 'stim', 'eog', 'misc',
-        'seeg', 'ecog', 'mag', 'eeg', 'ref_meg', 'grad', 'hbr' or 'hbo'.
+        'seeg', 'ecog', 'mag', 'eeg', 'ref_meg', 'grad', 'emg', 'hbr' or 'hbo'.
         If str, then all channels are assumed to be of the same type.
     montage : None | str | Montage | DigMontage | list
         A montage containing channel positions. If str or Montage is
@@ -1434,6 +1670,9 @@ def create_info(ch_names, sfreq, ch_types=None, montage=None):
         digitizer information will be updated. A list of unique montages,
         can be specifed and applied to the info. See also the documentation of
         :func:`mne.channels.read_montage` for more information.
+    verbose : bool, str, int, or None
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -1449,6 +1688,14 @@ def create_info(ch_names, sfreq, ch_types=None, montage=None):
 
     Note that the MEG device-to-head transform ``info['dev_head_t']`` will
     be initialized to the identity transform.
+
+    Proper units of measure:
+    * V: eeg, eog, seeg, emg, ecg, bio, ecog
+    * T: mag
+    * T/m: grad
+    * M: hbo, hbr
+    * Am: dipole
+    * AU: misc
     """
     if isinstance(ch_names, int):
         ch_names = list(np.arange(ch_names).astype(str))
@@ -1506,32 +1753,32 @@ RAW_INFO_FIELDS = (
     'acq_pars', 'acq_stim', 'bads', 'buffer_size_sec', 'ch_names', 'chs',
     'comps', 'ctf_head_t', 'custom_ref_applied', 'description', 'dev_ctf_t',
     'dev_head_t', 'dig', 'experimenter', 'events',
-    'file_id', 'filename', 'highpass', 'hpi_meas', 'hpi_results',
+    'file_id', 'highpass', 'hpi_meas', 'hpi_results',
     'hpi_subsystem', 'kit_system_id', 'line_freq', 'lowpass', 'meas_date',
     'meas_id', 'nchan', 'proj_id', 'proj_name', 'projs', 'sfreq',
-    'subject_info', 'xplotter_layout',
+    'subject_info', 'xplotter_layout', 'proc_history',
 )
 
 
 def _empty_info(sfreq):
-    """Create an empty info dictionary"""
+    """Create an empty info dictionary."""
     from ..transforms import Transform
     _none_keys = (
         'acq_pars', 'acq_stim', 'buffer_size_sec', 'ctf_head_t', 'description',
         'dev_ctf_t', 'dig', 'experimenter',
-        'file_id', 'filename', 'highpass', 'hpi_subsystem', 'kit_system_id',
+        'file_id', 'highpass', 'hpi_subsystem', 'kit_system_id',
         'line_freq', 'lowpass', 'meas_date', 'meas_id', 'proj_id', 'proj_name',
         'subject_info', 'xplotter_layout',
     )
     _list_keys = ('bads', 'chs', 'comps', 'events', 'hpi_meas', 'hpi_results',
-                  'projs')
+                  'projs', 'proc_history')
     info = Info()
     for k in _none_keys:
         info[k] = None
     for k in _list_keys:
         info[k] = list()
     info['custom_ref_applied'] = False
-    info['dev_head_t'] = Transform('meg', 'head', np.eye(4))
+    info['dev_head_t'] = Transform('meg', 'head')
     info['highpass'] = 0.
     info['sfreq'] = float(sfreq)
     info['lowpass'] = info['sfreq'] / 2.
@@ -1595,6 +1842,11 @@ def anonymize_info(info):
         del info['subject_info']
     info['meas_date'] = [0, 0]
     for key_1 in ('file_id', 'meas_id'):
+        key = info.get(key_1)
+        if key is None:
+            continue
         for key_2 in ('secs', 'msecs', 'usecs'):
+            if key_2 not in key:
+                continue
             info[key_1][key_2] = 0
     return info
diff --git a/mne/io/nicolet/__init__.py b/mne/io/nicolet/__init__.py
index 4b05df3..77d9224 100644
--- a/mne/io/nicolet/__init__.py
+++ b/mne/io/nicolet/__init__.py
@@ -1,4 +1,4 @@
-"""Nicolet module for conversion to FIF"""
+"""Nicolet module for conversion to FIF."""
 
 # Author: Jaakko Leppakangas <jaeilepp at student.jyu.fi>
 #
diff --git a/mne/io/nicolet/nicolet.py b/mne/io/nicolet/nicolet.py
index 6a1e0f4..57d3caf 100644
--- a/mne/io/nicolet/nicolet.py
+++ b/mne/io/nicolet/nicolet.py
@@ -9,14 +9,14 @@ import calendar
 
 from ...utils import logger
 from ..utils import _read_segments_file, _find_channels, _create_chs
-from ..base import _BaseRaw, _check_update_montage
+from ..base import BaseRaw, _check_update_montage
 from ..meas_info import _empty_info
 from ..constants import FIFF
 
 
 def read_raw_nicolet(input_fname, ch_type, montage=None, eog=(), ecg=(),
                      emg=(), misc=(), preload=False, verbose=None):
-    """Read Nicolet data as raw object
+    """Read Nicolet data as raw object.
 
     Note: This reader takes data files with the extension ``.data`` as an
     input. The header file with the same file name stem and an extension
@@ -55,7 +55,8 @@ def read_raw_nicolet(input_fname, ch_type, montage=None, eog=(), ecg=(),
         file name of a memory-mapped file which is used to store the data
         on the hard drive (slower, requires less memory).
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -71,7 +72,7 @@ def read_raw_nicolet(input_fname, ch_type, montage=None, eog=(), ecg=(),
 
 
 def _get_nicolet_info(fname, ch_type, eog, ecg, emg, misc):
-    """Function for extracting info from Nicolet header files."""
+    """Extract info from Nicolet header files."""
     fname = path.splitext(fname)[0]
     header = fname + '.head'
 
@@ -103,8 +104,7 @@ def _get_nicolet_info(fname, ch_type, eog, ecg, emg, misc):
     date = datetime.datetime(int(date[0]), int(date[1]), int(date[2]),
                              int(time[0]), int(time[1]), int(sec), int(msec))
     info = _empty_info(header_info['sample_freq'])
-    info.update({'filename': fname,
-                 'meas_date': calendar.timegm(date.utctimetuple()),
+    info.update({'meas_date': calendar.timegm(date.utctimetuple()),
                  'description': None, 'buffer_size_sec': 1.})
 
     if ch_type == 'eeg':
@@ -125,7 +125,7 @@ def _get_nicolet_info(fname, ch_type, eog, ecg, emg, misc):
     return info, header_info
 
 
-class RawNicolet(_BaseRaw):
+class RawNicolet(BaseRaw):
     """Raw object from Nicolet file.
 
     Parameters
@@ -161,14 +161,16 @@ class RawNicolet(_BaseRaw):
         file name of a memory-mapped file which is used to store the data
         on the hard drive (slower, requires less memory).
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     See Also
     --------
     mne.io.Raw : Documentation of attribute and methods.
     """
+
     def __init__(self, input_fname, ch_type, montage=None, eog=(), ecg=(),
-                 emg=(), misc=(), preload=False, verbose=None):
+                 emg=(), misc=(), preload=False, verbose=None):  # noqa: D102
         input_fname = path.abspath(input_fname)
         info, header_info = _get_nicolet_info(input_fname, ch_type, eog, ecg,
                                               emg, misc)
@@ -180,5 +182,5 @@ class RawNicolet(_BaseRaw):
             verbose=verbose)
 
     def _read_segment_file(self, data, idx, fi, start, stop, cals, mult):
-        """Read a chunk of raw data"""
+        """Read a chunk of raw data."""
         _read_segments_file(self, data, idx, fi, start, stop, cals, mult)
diff --git a/mne/io/open.py b/mne/io/open.py
index 7e81ef8..a9c4812 100644
--- a/mne/io/open.py
+++ b/mne/io/open.py
@@ -14,11 +14,11 @@ from .tag import read_tag_info, read_tag, read_big, Tag
 from .tree import make_dir_tree, dir_tree_find
 from .constants import FIFF
 from ..utils import logger, verbose
-from ..externals.six import string_types, iteritems
+from ..externals.six import string_types, iteritems, text_type
 
 
 def _fiff_get_fid(fname):
-    """Helper to open a FIF file with no additional parsing"""
+    """Open a FIF file with no additional parsing."""
     if isinstance(fname, string_types):
         if op.splitext(fname)[1].lower() == '.gz':
             logger.debug('Using gzip')
@@ -33,7 +33,7 @@ def _fiff_get_fid(fname):
 
 
 def _get_next_fname(fid, fname, tree):
-    """Auxiliary function to get the next filename in split files."""
+    """Get the next filename in split files."""
     nodes_list = dir_tree_find(tree, FIFF.FIFFB_REF)
     next_fname = None
     for nodes in nodes_list:
@@ -86,7 +86,8 @@ def fiff_open(fname, preload=False, verbose=None):
         requires more memory, but can be faster for I/O operations that require
         frequent seeks.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -154,8 +155,8 @@ def fiff_open(fname, preload=False, verbose=None):
 
 
 def show_fiff(fname, indent='    ', read_limit=np.inf, max_str=30,
-              output=str, verbose=None):
-    """Show FIFF information
+              output=str, tag=None, verbose=None):
+    """Show FIFF information.
 
     This function is similar to mne_show_fiff.
 
@@ -173,25 +174,31 @@ def show_fiff(fname, indent='    ', read_limit=np.inf, max_str=30,
         each tag's data.
     output : type
         Either str or list. str is a convenience output for printing.
+    tag : int | None
+        Provide information about this tag. If None (default), all information
+        is shown.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
     """
     if output not in [list, str]:
         raise ValueError('output must be list or str')
+    if isinstance(tag, string_types):  # command mne show_fiff passes string
+        tag = int(tag)
     f, tree, directory = fiff_open(fname)
     # This gets set to 0 (unknown) by fiff_open, but FIFFB_ROOT probably
     # makes more sense for display
     tree['block'] = FIFF.FIFFB_ROOT
     with f as fid:
         out = _show_tree(fid, tree, indent=indent, level=0,
-                         read_limit=read_limit, max_str=max_str)
+                         read_limit=read_limit, max_str=max_str, tag_id=tag)
     if output == str:
         out = '\n'.join(out)
     return out
 
 
 def _find_type(value, fmts=['FIFF_'], exclude=['FIFF_UNIT']):
-    """Helper to find matching values"""
+    """Find matching values."""
     value = int(value)
     vals = [k for k, v in iteritems(FIFF)
             if v == value and any(fmt in k for fmt in fmts) and
@@ -201,14 +208,18 @@ def _find_type(value, fmts=['FIFF_'], exclude=['FIFF_UNIT']):
     return vals
 
 
-def _show_tree(fid, tree, indent, level, read_limit, max_str):
-    """Helper for showing FIFF"""
+def _show_tree(fid, tree, indent, level, read_limit, max_str, tag_id):
+    """Show FIFF tree."""
     from scipy import sparse
     this_idt = indent * level
     next_idt = indent * (level + 1)
     # print block-level information
     out = [this_idt + str(int(tree['block'])) + ' = ' +
            '/'.join(_find_type(tree['block'], fmts=['FIFFB_']))]
+    tag_found = False
+    if tag_id is None or out[0].strip().startswith(str(tag_id)):
+        tag_found = True
+
     if tree['directory'] is not None:
         kinds = [ent.kind for ent in tree['directory']] + [-1]
         sizes = [ent.size for ent in tree['directory']]
@@ -216,6 +227,8 @@ def _show_tree(fid, tree, indent, level, read_limit, max_str):
         counter = 0
         good = True
         for k, kn, size, pos in zip(kinds[:-1], kinds[1:], sizes, poss):
+            if not tag_found and k != tag_id:
+                continue
             tag = Tag(k, size, 0, pos)
             if read_limit is None or size <= read_limit:
                 try:
@@ -234,7 +247,7 @@ def _show_tree(fid, tree, indent, level, read_limit, max_str):
                 postpend = ''
                 # print tag data nicely
                 if tag.data is not None:
-                    postpend = ' = ' + str(tag.data)[:max_str]
+                    postpend = ' = ' + text_type(tag.data)[:max_str]
                     if isinstance(tag.data, np.ndarray):
                         if tag.data.size > 1:
                             postpend += ' ... array size=' + str(tag.data.size)
@@ -256,8 +269,13 @@ def _show_tree(fid, tree, indent, level, read_limit, max_str):
                 out[-1] = out[-1].replace('\n', u'¶')
                 counter = 0
                 good = True
-
+        if tag_id in kinds:
+            tag_found = True
+    if not tag_found:
+        out = ['']
+        level = -1  # removes extra indent
     # deal with children
     for branch in tree['children']:
-        out += _show_tree(fid, branch, indent, level + 1, read_limit, max_str)
+        out += _show_tree(fid, branch, indent, level + 1, read_limit, max_str,
+                          tag_id)
     return out
diff --git a/mne/io/pick.py b/mne/io/pick.py
index da4b47d..c7c9c12 100644
--- a/mne/io/pick.py
+++ b/mne/io/pick.py
@@ -15,7 +15,7 @@ from ..externals.six import string_types
 
 
 def channel_type(info, idx):
-    """Get channel type
+    """Get channel type.
 
     Parameters
     ----------
@@ -83,7 +83,7 @@ def channel_type(info, idx):
 
 
 def pick_channels(ch_names, include, exclude=[]):
-    """Pick channels by names
+    """Pick channels by names.
 
     Returns the indices of the good channels in ch_names.
 
@@ -93,6 +93,10 @@ def pick_channels(ch_names, include, exclude=[]):
         List of channels.
     include : list of string
         List of channels to include (if empty include all available).
+
+        .. note:: This is to be treated as a set. The order of this list
+           is not used or maintained in ``sel``.
+
     exclude : list of string
         List of channels to exclude (if empty do not exclude any channel).
         Defaults to [].
@@ -123,7 +127,7 @@ def pick_channels(ch_names, include, exclude=[]):
 
 
 def pick_channels_regexp(ch_names, regexp):
-    """Pick channels using regular expression
+    """Pick channels using regular expression.
 
     Returns the indices of the good channels in ch_names.
 
@@ -157,7 +161,7 @@ def pick_channels_regexp(ch_names, regexp):
 
 
 def _triage_meg_pick(ch, meg):
-    """Helper to triage an MEG pick type"""
+    """Triage an MEG pick type."""
     if meg is True:
         return True
     elif ch['unit'] == FIFF.FIFF_UNIT_T_M:
@@ -173,7 +177,7 @@ def _triage_meg_pick(ch, meg):
 
 
 def _triage_fnirs_pick(ch, fnirs):
-    """Helper to triage an fNIRS pick type."""
+    """Triage an fNIRS pick type."""
     if fnirs is True:
         return True
     elif ch['coil_type'] == FIFF.FIFFV_COIL_FNIRS_HBO and fnirs == 'hbo':
@@ -184,7 +188,7 @@ def _triage_fnirs_pick(ch, fnirs):
 
 
 def _check_meg_type(meg, allow_auto=False):
-    """Helper to ensure a valid meg type"""
+    """Ensure a valid meg type."""
     if isinstance(meg, string_types):
         allowed_types = ['grad', 'mag', 'planar1', 'planar2']
         allowed_types += ['auto'] if allow_auto else []
@@ -196,9 +200,9 @@ def _check_meg_type(meg, allow_auto=False):
 def pick_types(info, meg=True, eeg=False, stim=False, eog=False, ecg=False,
                emg=False, ref_meg='auto', misc=False, resp=False, chpi=False,
                exci=False, ias=False, syst=False, seeg=False, dipole=False,
-               gof=False, bio=False, ecog=False, fnirs=False, include=[],
+               gof=False, bio=False, ecog=False, fnirs=False, include=(),
                exclude='bads', selection=None):
-    """Pick channels by type and names
+    """Pick channels by type and names.
 
     Parameters
     ----------
@@ -300,7 +304,7 @@ def pick_types(info, meg=True, eeg=False, stim=False, eog=False, ecg=False,
     for k in range(nchan):
         kind = info['chs'][k]['kind']
         # XXX eventually we should de-duplicate this with channel_type!
-        if kind == FIFF.FIFFV_MEG_CH:
+        if kind == FIFF.FIFFV_MEG_CH and meg:
             pick[k] = _triage_meg_pick(info['chs'][k], meg)
         elif kind == FIFF.FIFFV_EEG_CH and eeg:
             pick[k] = True
@@ -347,7 +351,7 @@ def pick_types(info, meg=True, eeg=False, stim=False, eog=False, ecg=False,
         # the selection only restricts these types of channels
         sel_kind = [FIFF.FIFFV_MEG_CH, FIFF.FIFFV_REF_MEG_CH,
                     FIFF.FIFFV_EEG_CH]
-        for k in np.where(pick == True)[0]:  # noqa
+        for k in np.where(pick)[0]:
             if (info['chs'][k]['kind'] in sel_kind and
                     info['ch_names'][k] not in selection):
                 pick[k] = False
@@ -364,7 +368,7 @@ def pick_types(info, meg=True, eeg=False, stim=False, eog=False, ecg=False,
 
 
 def pick_info(info, sel=(), copy=True):
-    """Restrict an info structure to a selection of channels
+    """Restrict an info structure to a selection of channels.
 
     Parameters
     ----------
@@ -381,8 +385,7 @@ def pick_info(info, sel=(), copy=True):
         Info structure restricted to a selection of channels.
     """
     info._check_consistency()
-    if copy:
-        info = deepcopy(info)
+    info = info.copy() if copy else info
     if sel is None:
         return info
     elif len(sel) == 0:
@@ -392,23 +395,24 @@ def pick_info(info, sel=(), copy=True):
     info._update_redundant()
     info['bads'] = [ch for ch in info['bads'] if ch in info['ch_names']]
 
-    comps = deepcopy(info['comps'])
-    for c in comps:
-        row_idx = [k for k, n in enumerate(c['data']['row_names'])
-                   if n in info['ch_names']]
-        row_names = [c['data']['row_names'][i] for i in row_idx]
-        rowcals = c['rowcals'][row_idx]
-        c['rowcals'] = rowcals
-        c['data']['nrow'] = len(row_names)
-        c['data']['row_names'] = row_names
-        c['data']['data'] = c['data']['data'][row_idx]
-    info['comps'] = comps
+    if 'comps' in info:
+        comps = deepcopy(info['comps'])
+        for c in comps:
+            row_idx = [k for k, n in enumerate(c['data']['row_names'])
+                       if n in info['ch_names']]
+            row_names = [c['data']['row_names'][i] for i in row_idx]
+            rowcals = c['rowcals'][row_idx]
+            c['rowcals'] = rowcals
+            c['data']['nrow'] = len(row_names)
+            c['data']['row_names'] = row_names
+            c['data']['data'] = c['data']['data'][row_idx]
+        info['comps'] = comps
     info._check_consistency()
     return info
 
 
 def _has_kit_refs(info, picks):
-    """Helper to determine if KIT ref channels are chosen
+    """Determine if KIT ref channels are chosen.
 
     This is currently only used by make_forward_solution, which cannot
     run when KIT reference channels are included.
@@ -420,7 +424,7 @@ def _has_kit_refs(info, picks):
 
 
 def pick_channels_evoked(orig, include=[], exclude='bads'):
-    """Pick channels from evoked data
+    """Pick channels from evoked data.
 
     Parameters
     ----------
@@ -464,7 +468,7 @@ def pick_channels_evoked(orig, include=[], exclude='bads'):
 
 @verbose
 def pick_channels_forward(orig, include=[], exclude=[], verbose=None):
-    """Pick channels from forward operator
+    """Pick channels from forward operator.
 
     Parameters
     ----------
@@ -477,7 +481,8 @@ def pick_channels_forward(orig, include=[], exclude=[], verbose=None):
         Channels to exclude (if empty, do not exclude any). Defaults to [].
         If 'bads', then exclude bad channels in orig.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -539,7 +544,7 @@ def pick_channels_forward(orig, include=[], exclude=[], verbose=None):
 
 def pick_types_forward(orig, meg=True, eeg=False, ref_meg=True, seeg=False,
                        ecog=False, include=[], exclude=[]):
-    """Pick by channel type and names from a forward operator
+    """Pick by channel type and names from a forward operator.
 
     Parameters
     ----------
@@ -579,8 +584,7 @@ def pick_types_forward(orig, meg=True, eeg=False, ref_meg=True, seeg=False,
 
 
 def channel_indices_by_type(info):
-    """Get indices of channels by type
-    """
+    """Get indices of channels by type."""
     idx = dict((key, list()) for key in _PICK_TYPES_KEYS if
                key not in ('meg', 'fnirs'))
     idx.update(mag=list(), grad=list(), hbo=list(), hbr=list())
@@ -593,7 +597,7 @@ def channel_indices_by_type(info):
 
 
 def pick_channels_cov(orig, include=[], exclude='bads'):
-    """Pick channels from covariance matrix
+    """Pick channels from covariance matrix.
 
     Parameters
     ----------
@@ -609,23 +613,22 @@ def pick_channels_cov(orig, include=[], exclude='bads'):
     res : dict
         Covariance solution restricted to selected channels.
     """
+    from ..cov import Covariance
     exclude = orig['bads'] if exclude == 'bads' else exclude
     sel = pick_channels(orig['names'], include=include, exclude=exclude)
-    res = deepcopy(orig)
-    res['dim'] = len(sel)
-    if not res['diag']:
-        res['data'] = orig['data'][sel][:, sel]
-    else:
-        res['data'] = orig['data'][sel]
-    res['names'] = [orig['names'][k] for k in sel]
-    res['bads'] = [name for name in orig['bads'] if name in res['names']]
-    res['eig'] = None
-    res['eigvec'] = None
+    data = orig['data'][sel][:, sel] if not orig['diag'] else orig['data'][sel]
+    names = [orig['names'][k] for k in sel]
+    bads = [name for name in orig['bads'] if name in orig['names']]
+    res = Covariance(
+        data=data, names=names, bads=bads, projs=deepcopy(orig['projs']),
+        nfree=orig['nfree'], eig=None, eigvec=None,
+        method=orig.get('method', None), loglik=orig.get('loglik', None))
     return res
 
 
-def _picks_by_type(info, meg_combined=False, ref_meg=False):
-    """Get data channel indices as separate list of tuples
+def _picks_by_type(info, meg_combined=False, ref_meg=False, exclude='bads'):
+    """Get data channel indices as separate list of tuples.
+
     Parameters
     ----------
     info : instance of mne.measuerment_info.Info
@@ -634,6 +637,9 @@ def _picks_by_type(info, meg_combined=False, ref_meg=False):
         Whether to return combined picks for grad and mag.
     ref_meg : bool
         If True include CTF / 4D reference channels
+    exclude : list of string | str
+        List of channels to exclude. If 'bads' (default), exclude channels
+        in info['bads'].
 
     Returns
     -------
@@ -647,22 +653,22 @@ def _picks_by_type(info, meg_combined=False, ref_meg=False):
     if has_mag and (meg_combined is not True or not has_grad):
         picks_list.append(
             ('mag', pick_types(info, meg='mag', eeg=False, stim=False,
-             ref_meg=ref_meg))
+             ref_meg=ref_meg, exclude=exclude))
         )
     if has_grad and (meg_combined is not True or not has_mag):
         picks_list.append(
             ('grad', pick_types(info, meg='grad', eeg=False, stim=False,
-             ref_meg=ref_meg))
+             ref_meg=ref_meg, exclude=exclude))
         )
     if has_mag and has_grad and meg_combined is True:
         picks_list.append(
             ('meg', pick_types(info, meg=True, eeg=False, stim=False,
-             ref_meg=ref_meg))
+             ref_meg=ref_meg, exclude=exclude))
         )
     if has_eeg:
         picks_list.append(
             ('eeg', pick_types(info, meg=False, eeg=True, stim=False,
-             ref_meg=ref_meg))
+             ref_meg=ref_meg, exclude=exclude))
         )
     return picks_list
 
@@ -706,15 +712,20 @@ _PICK_TYPES_DATA_DICT = dict(
 _PICK_TYPES_KEYS = tuple(list(_PICK_TYPES_DATA_DICT.keys()) + ['ref_meg'])
 _DATA_CH_TYPES_SPLIT = ['mag', 'grad', 'eeg', 'seeg', 'ecog', 'hbo', 'hbr']
 
+# Valid data types, ordered for consistency, used in viz/evoked.
+_VALID_CHANNEL_TYPES = ['eeg', 'grad', 'mag', 'seeg', 'eog', 'ecg', 'emg',
+                        'dipole', 'gof', 'bio', 'ecog', 'hbo', 'hbr',
+                        'misc']
+
 
 def _pick_data_channels(info, exclude='bads', with_ref_meg=True):
-    """Convenience function for picking only data channels."""
+    """Pick only data channels."""
     return pick_types(info, ref_meg=with_ref_meg, include=[], exclude=exclude,
                       selection=None, **_PICK_TYPES_DATA_DICT)
 
 
 def _pick_aux_channels(info, exclude='bads'):
-    """Convenience function for picking only auxiliary channels
+    """Pick only auxiliary channels.
 
     Corresponds to EOG, ECG, EMG and BIO
     """
@@ -723,11 +734,9 @@ def _pick_aux_channels(info, exclude='bads'):
 
 
 def _pick_data_or_ica(info):
-    """Convenience function for picking only data or ICA channels."""
-    ch_names = [c['ch_name'] for c in info['chs']]
-    if 'ICA ' in ','.join(ch_names):
+    """Pick only data or ICA channels."""
+    if any(ch_name.startswith('ICA') for ch_name in info['ch_names']):
         picks = pick_types(info, exclude=[], misc=True)
     else:
-        picks = _pick_data_channels(info, exclude=[],
-                                    with_ref_meg=False)
+        picks = _pick_data_channels(info, exclude=[], with_ref_meg=True)
     return picks
diff --git a/mne/io/proc_history.py b/mne/io/proc_history.py
index 5ce1038..eece441 100644
--- a/mne/io/proc_history.py
+++ b/mne/io/proc_history.py
@@ -16,8 +16,7 @@ from .write import (start_block, end_block, write_int, write_float,
 from .tag import find_tag
 from .constants import FIFF
 from ..externals.six import text_type, string_types
-from ..utils import warn
-
+from ..utils import warn, logger
 
 _proc_keys = ['parent_file_id', 'block_id', 'parent_block_id',
               'date', 'experimenter', 'creator']
@@ -33,49 +32,49 @@ _proc_casters = [dict, dict, dict,
                  np.array, text_type, text_type]
 
 
-def _read_proc_history(fid, tree, info):
-    """Read processing history from fiff file
+def _read_proc_history(fid, tree):
+    """Read processing history from fiff file.
 
     This function reads the SSS info, the CTC correction and the
     calibaraions from the SSS processing logs inside af a raw file
-    (C.f. Maxfilter v2.2 manual (October 2010), page 21):
-
-    104 = {                 900 = proc. history
-      104 = {               901 = proc. record
-        103 = block ID
-        204 = date
-        212 = scientist
-        113 = creator program
-        104 = {             502 = SSS info
-          264 = SSS task
-          263 = SSS coord frame
-          265 = SSS origin
-          266 = SSS ins.order
-          267 = SSS outs.order
-          268 = SSS nr chnls
-          269 = SSS components
-          278 = SSS nfree
-          243 = HPI g limit    0.98
-          244 = HPI dist limit 0.005
-        105 = }             502 = SSS info
-        104 = {             504 = MaxST info
-          264 = SSS task
-          272 = SSST subspace correlation
-          279 = SSST buffer length
-        105 = }
-        104 = {             501 = CTC correction
-          103 = block ID
-          204 = date
-          113 = creator program
-          800 = CTC matrix
-          3417 = proj item chs
-        105 = }             501 = CTC correction
-        104 = {             503 = SSS finecalib.
-          270 = SSS cal chnls
-          271 = SSS cal coeff
-        105 = }             503 = SSS finecalib.
-      105 = }               901 = proc. record
-    105 = }                 900 = proc. history
+    (C.f. Maxfilter v2.2 manual (October 2010), page 21)::
+
+        104 = {                 900 = proc. history
+          104 = {               901 = proc. record
+            103 = block ID
+            204 = date
+            212 = scientist
+            113 = creator program
+            104 = {             502 = SSS info
+              264 = SSS task
+              263 = SSS coord frame
+              265 = SSS origin
+              266 = SSS ins.order
+              267 = SSS outs.order
+              268 = SSS nr chnls
+              269 = SSS components
+              278 = SSS nfree
+              243 = HPI g limit    0.98
+              244 = HPI dist limit 0.005
+            105 = }             502 = SSS info
+            104 = {             504 = MaxST info
+              264 = SSS task
+              272 = SSST subspace correlation
+              279 = SSST buffer length
+            105 = }
+            104 = {             501 = CTC correction
+              103 = block ID
+              204 = date
+              113 = creator program
+              800 = CTC matrix
+              3417 = proj item chs
+            105 = }             501 = CTC correction
+            104 = {             503 = SSS finecalib.
+              270 = SSS cal chnls
+              271 = SSS cal coeff
+            105 = }             503 = SSS finecalib.
+          105 = }               901 = proc. record
+        105 = }                 900 = proc. history
     """
     proc_history = dir_tree_find(tree, FIFF.FIFFB_PROCESSING_HISTORY)
     out = list()
@@ -105,14 +104,11 @@ def _read_proc_history(fid, tree, info):
                 record['smartshield'] = ss
             if len(record['max_info']) > 0:
                 out.append(record)
-        if len(proc_records) > 0:
-            info['proc_history'] = out
+    return out
 
 
 def _write_proc_history(fid, info):
-    """Write processing history to file"""
-    if 'proc_history' not in info:
-        return
+    """Write processing history to file."""
     if len(info['proc_history']) > 0:
         start_block(fid, FIFF.FIFFB_PROCESSING_HISTORY)
         for record in info['proc_history']:
@@ -171,7 +167,7 @@ _sss_cal_casters = (np.array, np.array)
 
 
 def _read_ctc(fname):
-    """Read cross-talk correction matrix"""
+    """Read cross-talk correction matrix."""
     if not isinstance(fname, string_types) or not op.isfile(fname):
         raise ValueError('fname must be a file that exists, not %s' % fname)
     f, tree, _ = fiff_open(fname)
@@ -190,7 +186,7 @@ def _read_ctc(fname):
 
 
 def _read_maxfilter_record(fid, tree):
-    """Read maxfilter processing record from file"""
+    """Read maxfilter processing record from file."""
     sss_info_block = dir_tree_find(tree, FIFF.FIFFB_SSS_INFO)  # 502
     sss_info = dict()
     if len(sss_info_block) > 0:
@@ -262,7 +258,7 @@ def _read_maxfilter_record(fid, tree):
 
 
 def _write_maxfilter_record(fid, record):
-    """Write maxfilter processing record to file"""
+    """Write maxfilter processing record to file."""
     sss_info = record['sss_info']
     if len(sss_info) > 0:
         start_block(fid, FIFF.FIFFB_SSS_INFO)
@@ -304,9 +300,50 @@ def _write_maxfilter_record(fid, record):
 
 
 def _get_sss_rank(sss):
-    """Get SSS rank"""
+    """Get SSS rank."""
     inside = sss['sss_info']['in_order']
     nfree = (inside + 1) ** 2 - 1
     nfree -= (len(sss['sss_info']['components'][:nfree]) -
               sss['sss_info']['components'][:nfree].sum())
     return nfree
+
+
+def _get_rank_sss(inst):
+    """Look up rank from SSS data.
+
+    .. note::
+        Throws an error if SSS has not been applied.
+
+    Parameters
+    ----------
+    inst : instance of Raw, Epochs or Evoked, or Info
+        Any MNE object with an .info attribute
+
+    Returns
+    -------
+    rank : int
+        The numerical rank as predicted by the number of SSS
+        components.
+    """
+    if not isinstance(inst, dict):
+        info = inst.info
+    else:
+        info = inst
+
+    max_infos = list()
+    for proc_info in info.get('proc_history', list()):
+        max_info = proc_info.get('max_info')
+        if max_info is not None:
+            if len(max_info) > 0:
+                max_infos.append(max_info)
+            elif len(max_info) > 1:
+                logger.info('found multiple SSS records. Using the first.')
+            elif len(max_info) == 0:
+                raise ValueError(
+                    'Did not find any SSS record. You should use data-based '
+                    'rank estimate instead')
+    if len(max_infos) > 0:
+        max_info = max_infos[0]
+    else:
+        raise ValueError('There is no `max_info` here. Sorry.')
+    return _get_sss_rank(max_info)
diff --git a/mne/io/proj.py b/mne/io/proj.py
index edcf77f..00ede04 100644
--- a/mne/io/proj.py
+++ b/mne/io/proj.py
@@ -18,24 +18,103 @@ from .constants import FIFF
 from .pick import pick_types
 from .write import (write_int, write_float, write_string, write_name_list,
                     write_float_matrix, end_block, start_block)
-from ..utils import logger, verbose, warn, deprecated
+from ..utils import logger, verbose, warn
 from ..externals.six import string_types
 
 
 class Projection(dict):
-    """Projection vector
+    """Projection vector.
 
     A basic class to proj a meaningful print for projection vectors.
     """
-    def __repr__(self):
+
+    def __repr__(self):  # noqa: D105
         s = "%s" % self['desc']
         s += ", active : %s" % self['active']
         s += ", n_channels : %s" % self['data']['ncol']
         return "<Projection  |  %s>" % s
 
+    # Can't use copy_ function here b/c of circular import
+    def plot_topomap(self, layout=None, cmap=None, sensors=True,
+                     colorbar=False, res=64, size=1, show=True,
+                     outlines='head', contours=6, image_interp='bilinear',
+                     axes=None, info=None):
+        """Plot topographic maps of SSP projections.
+
+        Parameters
+        ----------
+        layout : None | Layout | list of Layout
+            Layout instance specifying sensor positions (does not need to be
+            specified for Neuromag data). Or a list of Layout if projections
+            are from different sensor types.
+        cmap : matplotlib colormap | (colormap, bool) | 'interactive' | None
+            Colormap to use. If tuple, the first value indicates the colormap to
+            use and the second value is a boolean defining interactivity. In
+            interactive mode (only works if ``colorbar=True``) the colors are
+            adjustable by clicking and dragging the colorbar with left and right
+            mouse button. Left mouse button moves the scale up and down and right
+            mouse button adjusts the range. Hitting space bar resets the range. Up
+            and down arrows can be used to change the colormap. If None (default),
+            'Reds' is used for all positive data, otherwise defaults to 'RdBu_r'.
+            If 'interactive', translates to (None, True).
+        sensors : bool | str
+            Add markers for sensor locations to the plot. Accepts matplotlib plot
+            format string (e.g., 'r+' for red plusses). If True, a circle will be
+            used (via .add_artist). Defaults to True.
+        colorbar : bool
+            Plot a colorbar.
+        res : int
+            The resolution of the topomap image (n pixels along each side).
+        size : scalar
+            Side length of the topomaps in inches (only applies when plotting
+            multiple topomaps at a time).
+        show : bool
+            Show figure if True.
+        outlines : 'head' | 'skirt' | dict | None
+            The outlines to be drawn. If 'head', the default head scheme will be
+            drawn. If 'skirt' the head scheme will be drawn, but sensors are
+            allowed to be plotted outside of the head circle. If dict, each key
+            refers to a tuple of x and y positions, the values in 'mask_pos' will
+            serve as image mask, and the 'autoshrink' (bool) field will trigger
+            automated shrinking of the positions due to points outside the outline.
+            Alternatively, a matplotlib patch object can be passed for advanced
+            masking options, either directly or as a function that returns patches
+            (required for multi-axis plots). If None, nothing will be drawn.
+            Defaults to 'head'.
+        contours : int | array of float
+            The number of contour lines to draw. If 0, no contours will be drawn.
+            When an integer, matplotlib ticker locator is used to find suitable
+            values for the contour thresholds (may sometimes be inaccurate, use
+            array for accuracy). If an array, the values represent the levels for
+            the contours. Defaults to 6.
+        image_interp : str
+            The image interpolation to be used. All matplotlib options are
+            accepted.
+        axes : instance of Axes | list | None
+            The axes to plot to. If list, the list must be a list of Axes of
+            the same length as the number of projectors. If instance of Axes,
+            there must be only one projector. Defaults to None.
+        info : instance of Info | None
+            The measurement information to use to determine the layout.
+            If not None, ``layout`` must be None.
+
+        Returns
+        -------
+        fig : instance of matplotlib figure
+            Figure distributing one image per channel across sensor topography.
+
+        Notes
+        -----
+        .. versionadded:: 0.15.0
+        """  # noqa: E501
+        from ..viz.topomap import plot_projs_topomap
+        return plot_projs_topomap([self], layout, cmap, sensors, colorbar,
+                                  res, size, show, outlines,
+                                  contours, image_interp, axes, info)
+
 
 class ProjMixin(object):
-    """Mixin class for Raw, Evoked, Epochs
+    """Mixin class for Raw, Evoked, Epochs.
 
     Notes
     -----
@@ -59,14 +138,16 @@ class ProjMixin(object):
     apply_proj(). The reason is that you want to reject with projs although
     it's not stored in proj mode.
     """
+
     @property
     def proj(self):
+        """Whether or not projections are active."""
         return (len(self.info['projs']) > 0 and
                 all(p['active'] for p in self.info['projs']))
 
     @verbose
     def add_proj(self, projs, remove_existing=False, verbose=None):
-        """Add SSP projection vectors
+        """Add SSP projection vectors.
 
         Parameters
         ----------
@@ -75,7 +156,9 @@ class ProjMixin(object):
         remove_existing : bool
             Remove the projection vectors currently in the file.
         verbose : bool, str, int, or None
-            If not None, override default verbose level (see mne.verbose).
+            If not None, override default verbose level (see
+            :func:`mne.verbose` and :ref:`Logging documentation <tut_logging>`
+            for more).
 
         Returns
         -------
@@ -105,21 +188,6 @@ class ProjMixin(object):
                                              check_active=False, sort=False)
         return self
 
-    @deprecated('This function is deprecated and will be removed in 0.14. '
-                'Use set_eeg_reference() instead.')
-    def add_eeg_average_proj(self):
-        """Add an average EEG reference projector if one does not exist."""
-        if _needs_eeg_average_ref_proj(self.info):
-            # Don't set as active, since we haven't applied it
-            eeg_proj = make_eeg_average_ref_proj(self.info, activate=False)
-            self.add_proj(eeg_proj)
-        elif self.info.get('custom_ref_applied', False):
-            raise RuntimeError('Cannot add an average EEG reference '
-                               'projection since a custom reference has been '
-                               'applied to the data earlier.')
-
-        return self
-
     def apply_proj(self):
         """Apply the signal space projection (SSP) operators to the data.
 
@@ -148,15 +216,16 @@ class ProjMixin(object):
         self : instance of Raw | Epochs | Evoked
             The instance.
         """
-        from ..epochs import _BaseEpochs
-        from .base import _BaseRaw
+        from ..epochs import BaseEpochs
+        from ..evoked import Evoked
+        from .base import BaseRaw
         if self.info['projs'] is None or len(self.info['projs']) == 0:
             logger.info('No projector specified for this dataset. '
                         'Please consider the method self.add_proj.')
             return self
 
         # Exit delayed mode if you apply proj
-        if isinstance(self, _BaseEpochs) and self._do_delayed_proj:
+        if isinstance(self, BaseEpochs) and self._do_delayed_proj:
             logger.info('Leaving delayed SSP mode.')
             self._do_delayed_proj = False
 
@@ -173,45 +242,46 @@ class ProjMixin(object):
                         ' Doing nothing.')
             return self
         self._projector, self.info = _projector, info
-        if isinstance(self, _BaseRaw):
+        if isinstance(self, (BaseRaw, Evoked)):
             if self.preload:
                 self._data = np.dot(self._projector, self._data)
-        elif isinstance(self, _BaseEpochs):
+        else:  # BaseEpochs
             if self.preload:
                 for ii, e in enumerate(self._data):
                     self._data[ii] = self._project_epoch(e)
             else:
                 self.load_data()  # will automatically apply
-        else:  # Evoked
-            self.data = np.dot(self._projector, self.data)
         logger.info('SSP projectors applied...')
         return self
 
-    def del_proj(self, idx):
-        """Remove SSP projection vector
+    def del_proj(self, idx='all'):
+        """Remove SSP projection vector.
 
         Note: The projection vector can only be removed if it is inactive
               (has not been applied to the data).
 
         Parameters
         ----------
-        idx : int
-            Index of the projector to remove.
+        idx : int | list of int | str
+            Index of the projector to remove. Can also be "all" (default)
+            to remove all projectors.
 
         Returns
         -------
         self : instance of Raw | Epochs | Evoked
         """
-        if self.info['projs'][idx]['active']:
+        if isinstance(idx, string_types) and idx == 'all':
+            idx = list(range(len(self.info['projs'])))
+        idx = np.atleast_1d(np.array(idx, int)).ravel()
+        if any(self.info['projs'][ii]['active'] for ii in idx):
             raise ValueError('Cannot remove projectors that have already '
                              'been applied')
-
-        self.info['projs'].pop(idx)
-
+        self.info['projs'] = [p for pi, p in enumerate(self.info['projs'])
+                              if pi not in idx]
         return self
 
     def plot_projs_topomap(self, ch_type=None, layout=None, axes=None):
-        """Plot SSP vector
+        """Plot SSP vector.
 
         Parameters
         ----------
@@ -259,8 +329,7 @@ class ProjMixin(object):
 
 
 def _proj_equal(a, b, check_active=True):
-    """ Test if two projectors are equal """
-
+    """Test if two projectors are equal."""
     equal = ((a['active'] == b['active'] or not check_active) and
              a['kind'] == b['kind'] and
              a['desc'] == b['desc'] and
@@ -283,11 +352,12 @@ def _read_proj(fid, node, verbose=None):
     node : tree node
         The node of the tree where to look.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
-    projs: dict
+    projs : list of Projection
         The list of projections.
     """
     projs = list()
@@ -434,8 +504,22 @@ def _write_proj(fid, projs):
 
 ###############################################################################
 # Utils
+
+def _check_projs(projs, copy=True):
+    """Check that projs is a list of Projection."""
+    if not isinstance(projs, (list, tuple)):
+        raise TypeError('projs must be a list or tuple, got %s'
+                        % (type(projs),))
+    for pi, p in enumerate(projs):
+        if not isinstance(p, Projection):
+            raise TypeError('All entries in projs list must be Projection '
+                            'instances, but projs[%d] is type %s'
+                            % (pi, type(p)))
+    return deepcopy(projs) if copy else projs
+
+
 def make_projector(projs, ch_names, bads=(), include_active=True):
-    """Create an SSP operator from SSP projection vectors
+    """Create an SSP operator from SSP projection vectors.
 
     Parameters
     ----------
@@ -465,7 +549,7 @@ def make_projector(projs, ch_names, bads=(), include_active=True):
 
 def _make_projector(projs, ch_names, bads=(), include_active=True,
                     inplace=False):
-    """Helper to subselect projs based on ch_names and bads
+    """Subselect projs based on ch_names and bads.
 
     Use inplace=True mode to modify ``projs`` inplace so that no
     warning will be raised next time projectors are constructed with
@@ -495,6 +579,7 @@ def _make_projector(projs, ch_names, bads=(), include_active=True,
     vecs = np.zeros((nchan, nvec))
     nvec = 0
     nonzero = 0
+    bads = set(bads)
     for k, p in enumerate(projs):
         if not p['active'] or include_active:
             if (len(p['data']['col_names']) !=
@@ -506,8 +591,9 @@ def _make_projector(projs, ch_names, bads=(), include_active=True,
             # the projection vectors omitting bad channels
             sel = []
             vecsel = []
+            p_set = set(p['data']['col_names'])  # faster membership access
             for c, name in enumerate(ch_names):
-                if name in p['data']['col_names'] and name not in bads:
+                if name not in bads and name in p_set:
                     sel.append(c)
                     vecsel.append(p['data']['col_names'].index(name))
 
@@ -521,8 +607,11 @@ def _make_projector(projs, ch_names, bads=(), include_active=True,
             for v in range(p['data']['nrow']):
                 psize = sqrt(np.sum(this_vecs[:, v] * this_vecs[:, v]))
                 if psize > 0:
-                    orig_n = p['data']['data'].shape[1]
-                    if len(vecsel) < 0.9 * orig_n and not inplace:
+                    orig_n = p['data']['data'].any(axis=0).sum()
+                    # Average ref still works if channels are removed
+                    if len(vecsel) < 0.9 * orig_n and not inplace and \
+                            (p['kind'] != FIFF.FIFFV_MNE_PROJ_ITEM_EEG_AVREF or
+                             len(vecsel) == 1):
                         warn('Projection vector "%s" has magnitude %0.2f '
                              '(should be unity), applying projector with '
                              '%s/%s of the original channels available may '
@@ -559,7 +648,7 @@ def _make_projector(projs, ch_names, bads=(), include_active=True,
 
 
 def _normalize_proj(info):
-    """Helper to normalize proj after subselection to avoid warnings
+    """Normalize proj after subselection to avoid warnings.
 
     This is really only useful for tests, and might not be needed
     eventually if we change or improve our handling of projectors
@@ -571,7 +660,7 @@ def _normalize_proj(info):
 
 
 def make_projector_info(info, include_active=True):
-    """Make an SSP operator using the measurement info
+    """Make an SSP operator using the measurement info.
 
     Calls make_projector on good channels.
 
@@ -596,7 +685,7 @@ def make_projector_info(info, include_active=True):
 
 @verbose
 def activate_proj(projs, copy=True, verbose=None):
-    """Set all projections to active
+    """Set all projections to active.
 
     Useful before passing them to make_projector.
 
@@ -607,7 +696,8 @@ def activate_proj(projs, copy=True, verbose=None):
     copy : bool
         Modify projs in place or operate on a copy.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -628,7 +718,7 @@ def activate_proj(projs, copy=True, verbose=None):
 
 @verbose
 def deactivate_proj(projs, copy=True, verbose=None):
-    """Set all projections to inactive
+    """Set all projections to inactive.
 
     Useful before saving raw data without projectors applied.
 
@@ -639,7 +729,8 @@ def deactivate_proj(projs, copy=True, verbose=None):
     copy : bool
         Modify projs in place or operate on a copy.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -660,7 +751,7 @@ def deactivate_proj(projs, copy=True, verbose=None):
 
 @verbose
 def make_eeg_average_ref_proj(info, activate=True, verbose=None):
-    """Create an EEG average reference SSP projection vector
+    """Create an EEG average reference SSP projection vector.
 
     Parameters
     ----------
@@ -669,7 +760,8 @@ def make_eeg_average_ref_proj(info, activate=True, verbose=None):
     activate : bool
         If True projections are activated.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -677,9 +769,10 @@ def make_eeg_average_ref_proj(info, activate=True, verbose=None):
         The SSP/PCA projector.
     """
     if info.get('custom_ref_applied', False):
-        raise RuntimeError('Cannot add an average EEG reference projection '
-                           'since a custom reference has been applied to the '
-                           'data earlier.')
+        raise RuntimeError('A custom reference has been applied to the '
+                           'data earlier. Please use the '
+                           'mne.io.set_eeg_reference function to move from '
+                           'one EEG reference to another.')
 
     logger.info("Adding average EEG reference projection.")
     eeg_sel = pick_types(info, meg=False, eeg=True, ref_meg=False,
@@ -703,7 +796,7 @@ def make_eeg_average_ref_proj(info, activate=True, verbose=None):
 
 
 def _has_eeg_average_ref_proj(projs, check_active=False):
-    """Determine if a list of projectors has an average EEG ref
+    """Determine if a list of projectors has an average EEG ref.
 
     Optionally, set check_active=True to additionally check if the CAR
     has already been applied.
@@ -717,7 +810,7 @@ def _has_eeg_average_ref_proj(projs, check_active=False):
 
 
 def _needs_eeg_average_ref_proj(info):
-    """Determine if the EEG needs an averge EEG reference
+    """Determine if the EEG needs an averge EEG reference.
 
     This returns True if no custom reference has been applied and no average
     reference projection is present in the list of projections.
@@ -731,7 +824,7 @@ def _needs_eeg_average_ref_proj(info):
 
 @verbose
 def setup_proj(info, add_eeg_ref=True, activate=True, verbose=None):
-    """Set up projection for Raw and Epochs
+    """Set up projection for Raw and Epochs.
 
     Parameters
     ----------
@@ -743,7 +836,8 @@ def setup_proj(info, add_eeg_ref=True, activate=True, verbose=None):
     activate : bool
         If True projections are activated.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -776,7 +870,7 @@ def setup_proj(info, add_eeg_ref=True, activate=True, verbose=None):
 
 
 def _uniquify_projs(projs, check_active=True, sort=True):
-    """Aux function"""
+    """Make unique projs."""
     final_projs = []
     for proj in projs:  # flatten
         if not any(_proj_equal(p, proj, check_active) for p in final_projs):
@@ -785,7 +879,7 @@ def _uniquify_projs(projs, check_active=True, sort=True):
     my_count = count(len(final_projs))
 
     def sorter(x):
-        """sort in a nice way"""
+        """Sort in a nice way."""
         digits = [s for s in x['desc'] if s.isdigit()]
         if digits:
             sort_idx = int(digits[-1])
diff --git a/mne/io/reference.py b/mne/io/reference.py
index 4d32af0..f78c245 100644
--- a/mne/io/reference.py
+++ b/mne/io/reference.py
@@ -4,20 +4,51 @@
 #
 # License: BSD (3-clause)
 
+from copy import deepcopy
 import numpy as np
 
 from .constants import FIFF
 from .proj import _has_eeg_average_ref_proj, make_eeg_average_ref_proj
-from .pick import pick_types
-from .base import _BaseRaw
+from .proj import setup_proj
+from .pick import pick_types, pick_channels
+from .base import BaseRaw
 from ..evoked import Evoked
-from ..epochs import _BaseEpochs
-from ..utils import logger, warn
+from ..epochs import BaseEpochs
+from ..utils import logger, warn, verbose
+
+
+def _copy_channel(inst, ch_name, new_ch_name):
+    """Add a copy of a channel specified by ch_name.
+
+    Input data can be in the form of Raw, Epochs or Evoked.
+
+    The instance object is modified inplace.
+
+    Parameters
+    ----------
+    inst : instance of Raw | Epochs | Evoked
+        Data containing the EEG channels
+    ch_name : str
+        Name of the channel to copy.
+    new_ch_name : str
+        Name given to the copy of the channel.
+
+    Returns
+    -------
+    inst : instance of Raw | Epochs | Evoked
+        The data with a copy of a given channel.
+    """
+    new_inst = inst.copy().pick_channels([ch_name])
+    new_inst.rename_channels({ch_name: new_ch_name})
+    inst.add_channels([new_inst], force_update_info=True)
+    return inst
 
 
 def _apply_reference(inst, ref_from, ref_to=None):
     """Apply a custom EEG referencing scheme.
 
+    This function modifies the instance in-place.
+
     Calculates a reference signal by taking the mean of a set of channels and
     applies the reference to another set of channels. Input data can be in the
     form of Raw, Epochs or Evoked.
@@ -31,13 +62,13 @@ def _apply_reference(inst, ref_from, ref_to=None):
         empty list is specified, the data is assumed to already have a proper
         reference and MNE will not attempt any re-referencing of the data.
     ref_to : list of str | None
-        The names of the channels to apply the reference to. By default,
-        all EEG channels are chosen.
+        The names of the channels to apply the reference to. If None (which is
+        the default), all EEG channels are chosen.
 
     Returns
     -------
     inst : instance of Raw | Epochs | Evoked
-        The data with EEG channels rereferenced.
+        The data with EEG channels re-referenced.
     ref_data : array, shape (n_times,)
         Array of reference data subtracted from EEG channels.
 
@@ -45,17 +76,13 @@ def _apply_reference(inst, ref_from, ref_to=None):
     -----
     This function operates in-place.
 
-    1. Do not use this function to apply an average reference. By default, an
-       average reference projection has already been added upon loading raw
-       data.
-
-    2. If the reference is applied to any EEG channels, this function removes
+    1. If the reference is applied to any EEG channels, this function removes
        any pre-existing average reference projections.
 
-    3. During source localization, the EEG signal should have an average
+    2. During source localization, the EEG signal should have an average
        reference.
 
-    4. The data must be preloaded.
+    3. The data must be preloaded.
 
     See Also
     --------
@@ -64,7 +91,7 @@ def _apply_reference(inst, ref_from, ref_to=None):
                             reference.
     """
     # Check to see that data is preloaded
-    if not isinstance(inst, Evoked) and not inst.preload:
+    if not inst.preload:
         raise RuntimeError('Data needs to be preloaded. Use '
                            'preload=True (or string) in the constructor.')
 
@@ -74,50 +101,54 @@ def _apply_reference(inst, ref_from, ref_to=None):
         ref_to = [inst.ch_names[i] for i in eeg_idx]
 
     # After referencing, existing SSPs might not be valid anymore.
+    projs_to_remove = []
     for i, proj in enumerate(inst.info['projs']):
-        if (not proj['active'] and
-            len([ch for ch in (ref_from + ref_to)
-                 if ch in proj['data']['col_names']]) > 0):
-
-            # Remove any average reference projections, apply any other types
-            if proj['desc'] == 'Average EEG reference' or \
-                    proj['kind'] == FIFF.FIFFV_MNE_PROJ_ITEM_EEG_AVREF:
-                logger.info('Removing existing average EEG reference '
-                            'projection.')
-                del inst.info['projs'][i]
-            else:
-                logger.info(
-                    'Inactive signal space projection (SSP) operators are '
-                    'present that operate on sensors involved in the current '
-                    'referencing scheme. Applying them now. Be aware that '
-                    'after re-referencing, these operators will be invalid.')
-                inst.apply_proj()
-            break
-
-    ref_from = [inst.ch_names.index(ch) for ch in ref_from]
-    ref_to = [inst.ch_names.index(ch) for ch in ref_to]
-
-    if isinstance(inst, Evoked):
-        data = inst.data
-    else:
-        data = inst._data
+        # Remove any average reference projections
+        if proj['desc'] == 'Average EEG reference' or \
+                proj['kind'] == FIFF.FIFFV_MNE_PROJ_ITEM_EEG_AVREF:
+            logger.info('Removing existing average EEG reference '
+                        'projection.')
+            # Don't remove the projection right away, but do this at the end of
+            # this loop.
+            projs_to_remove.append(i)
+
+        # Inactive SSPs may block re-referencing
+        elif (not proj['active'] and
+              len([ch for ch in (ref_from + ref_to)
+                   if ch in proj['data']['col_names']]) > 0):
+
+            raise RuntimeError(
+                'Inactive signal space projection (SSP) operators are '
+                'present that operate on sensors involved in the desired '
+                'referencing scheme. These projectors need to be applied '
+                'using the apply_proj() method function before the desired '
+                'reference can be set.'
+            )
+
+    for i in projs_to_remove:
+        del inst.info['projs'][i]
+
+    # Need to call setup_proj after changing the projs:
+    inst._projector, _ = \
+        setup_proj(inst.info, add_eeg_ref=False, activate=False)
 
     # Compute reference
     if len(ref_from) > 0:
-        ref_data = data[..., ref_from, :].mean(-2)
+        ref_from = pick_channels(inst.ch_names, ref_from)
+        ref_to = pick_channels(inst.ch_names, ref_to)
 
-        if isinstance(inst, _BaseEpochs):
-            data[:, ref_to, :] -= ref_data[:, np.newaxis, :]
-        else:
-            data[ref_to] -= ref_data
+        data = inst._data
+        ref_data = data[..., ref_from, :].mean(-2, keepdims=True)
+        data[..., ref_to, :] -= ref_data
+        ref_data = ref_data[..., 0, :]
+
+        # If the reference touches EEG electrodes, note in the info that a
+        # non-CAR has been applied.
+        if len(np.intersect1d(ref_to, eeg_idx)) > 0:
+            inst.info['custom_ref_applied'] = True
     else:
         ref_data = None
 
-    # If the reference touches EEG electrodes, note in the info that a non-CAR
-    # has been applied.
-    if len(np.intersect1d(ref_to, eeg_idx)) > 0:
-        inst.info['custom_ref_applied'] = True
-
     return inst, ref_data
 
 
@@ -126,7 +157,7 @@ def add_reference_channels(inst, ref_channels, copy=True):
 
     Adds reference channels to data that were not included during recording.
     This is useful when you need to re-reference your data to different
-    channel. These added channels will consist of all zeros.
+    channels. These added channels will consist of all zeros.
 
     Parameters
     ----------
@@ -137,7 +168,7 @@ def add_reference_channels(inst, ref_channels, copy=True):
         recording. If a name is provided, a corresponding channel is added
         and its data is set to 0. This is useful for later re-referencing.
     copy : bool
-        Specifies whether the data will be copied (True) or modified in place
+        Specifies whether the data will be copied (True) or modified in-place
         (False). Defaults to True.
 
     Returns
@@ -146,7 +177,7 @@ def add_reference_channels(inst, ref_channels, copy=True):
         Data with added EEG reference channels.
     """
     # Check to see that data is preloaded
-    if not isinstance(inst, Evoked) and not inst.preload:
+    if not inst.preload:
         raise RuntimeError('Data needs to be preloaded.')
     if isinstance(ref_channels, str):
         ref_channels = [ref_channels]
@@ -164,17 +195,12 @@ def add_reference_channels(inst, ref_channels, copy=True):
     if copy:
         inst = inst.copy()
 
-    if isinstance(inst, Evoked):
-        data = inst.data
-        refs = np.zeros((len(ref_channels), data.shape[1]))
-        data = np.vstack((data, refs))
-        inst.data = data
-    elif isinstance(inst, _BaseRaw):
+    if isinstance(inst, (BaseRaw, Evoked)):
         data = inst._data
         refs = np.zeros((len(ref_channels), data.shape[1]))
         data = np.vstack((data, refs))
         inst._data = data
-    elif isinstance(inst, _BaseEpochs):
+    elif isinstance(inst, BaseEpochs):
         data = inst._data
         x, y, z = data.shape
         refs = np.zeros((x * len(ref_channels), z))
@@ -224,43 +250,93 @@ def add_reference_channels(inst, ref_channels, copy=True):
                      'loc': ref_dig_array}
         inst.info['chs'].append(chan_info)
         inst.info._update_redundant()
-    if isinstance(inst, _BaseRaw):
+    if isinstance(inst, BaseRaw):
         inst._cals = np.hstack((inst._cals, [1] * len(ref_channels)))
     inst.info._check_consistency()
     set_eeg_reference(inst, ref_channels=ref_channels, copy=False)
     return inst
 
 
-def set_eeg_reference(inst, ref_channels=None, copy=True):
-    """Rereference EEG channels to new reference channel(s).
-
-    If multiple reference channels are specified, they will be averaged. If
-    no reference channels are specified, an average reference will be applied.
+ at verbose
+def set_eeg_reference(inst, ref_channels='average', copy=True,
+                      projection=None, verbose=None):
+    """Specify which reference to use for EEG data.
+
+    By default, MNE-Python will automatically re-reference the EEG signal to
+    use an average reference (see below). Use this function to explicitly
+    specify the desired reference for EEG. This can be either an existing
+    electrode or a new virtual channel. This function will re-reference the
+    data according to the desired reference and prevent MNE-Python from
+    automatically adding an average reference projection.
+
+    Some common referencing schemes and the corresponding value for the
+    ``ref_channels`` parameter:
+
+    No re-referencing:
+        If the EEG data is already using the proper reference, set
+        ``ref_channels=[]``. This will prevent MNE-Python from automatically
+        adding an average reference projection.
+
+    Average reference:
+        A new virtual reference electrode is created by averaging the current
+        EEG signal by setting ``ref_channels='average'``. Bad EEG channels are
+        automatically excluded if they are properly set in ``info['bads']``.
+
+    A single electrode:
+        Set ``ref_channels`` to a list containing the name of the channel that
+        will act as the new reference, for example ``ref_channels=['Cz']``.
+
+    The mean of multiple electrodes:
+        A new virtual reference electrode is created by computing the average
+        of the current EEG signal recorded from two or more selected channels.
+        Set ``ref_channels`` to a list of channel names, indicating which
+        channels to use. For example, to apply an average mastoid reference,
+        when using the 10-20 naming scheme, set ``ref_channels=['M1', 'M2']``.
+
+    .. note:: In case of ``ref_channels='average'`` in combination with
+              ``projection=True``, the reference is added as a projection and
+              it is not applied automatically. For it to take effect, apply
+              with method :meth:`apply_proj <mne.io.Raw.apply_proj>`. Other
+              references are directly applied (this behavior will change in MNE
+              0.16).
 
     Parameters
     ----------
     inst : instance of Raw | Epochs | Evoked
         Instance of Raw or Epochs with EEG channels and reference channel(s).
-    ref_channels : list of str | None
-        The names of the channels to use to construct the reference. If
-        None (default), an average reference will be added as an SSP
-        projector but not immediately applied to the data. If an empty list
-        is specified, the data is assumed to already have a proper reference
-        and MNE will not attempt any re-referencing of the data. Defaults
-        to an average reference (None).
+    ref_channels : list of str | str
+        The name(s) of the channel(s) used to construct the reference. To apply
+        an average reference, specify ``'average'`` here (default). If an empty
+        list is specified, the data is assumed to already have a proper
+        reference and MNE will not attempt any re-referencing of the data.
+        Defaults to an average reference.
     copy : bool
-        Specifies whether the data will be copied (True) or modified in place
+        Specifies whether the data will be copied (True) or modified in-place
         (False). Defaults to True.
+    projection : bool | None
+        If ``ref_channels='average'`` this argument specifies if the average
+        reference should be computed as a projection (True) or not (False). If
+        ``projection=True``, the average reference is added as a projection and
+        is not applied to the data (it can be applied afterwards with the
+        ``apply_proj`` method). If ``projection=False``, the average
+        reference is directly applied to the data. Defaults to None, which
+        means ``projection=True``, but will change to ``projection=False`` in
+        the next release.
+        If ``ref_channels`` is not ``'average'``, ``projection`` must be set to
+        ``False`` (the default in this case).
+    verbose : bool, str, int, or None
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
     inst : instance of Raw | Epochs | Evoked
-        Data with EEG channels re-referenced. For ``ref_channels=None``,
-        an average projector will be added instead of directly subtarcting
-        data.
+        Data with EEG channels re-referenced. If ``ref_channels='average'`` and
+        ``projection=True`` a projection will be added instead of directly
+        re-referencing the data.
     ref_data : array
-        Array of reference data subtracted from EEG channels. This will
-        be None for an average reference.
+        Array of reference data subtracted from EEG channels. This will be
+        ``None`` if ``ref_channels='average'`` and ``projection=True``.
 
     Notes
     -----
@@ -270,8 +346,11 @@ def set_eeg_reference(inst, ref_channels=None, copy=True):
     2. During source localization, the EEG signal should have an average
        reference.
 
-    3. In order to apply a reference other than an average reference, the data
-       must be preloaded.
+    3. In order to apply a reference, the data must be preloaded. This is not
+       necessary if ``ref_channels='average'`` and ``projection=True``.
+
+    4. For an average reference, bad EEG channels are automatically excluded if
+       they are properly set in ``info['bads']``.
 
     .. versionadded:: 0.9.0
 
@@ -280,24 +359,79 @@ def set_eeg_reference(inst, ref_channels=None, copy=True):
     set_bipolar_reference : Convenience function for creating bipolar
                             references.
     """
+    if not isinstance(inst, (BaseRaw, BaseEpochs, Evoked)):
+        raise ValueError('Setting a reference is only supported for instances '
+                         'of Raw, Epochs or Evoked.')
+
     if ref_channels is None:
-        # CAR requested
+        warn('Setting ref_channels=None to compute an average reference is '
+             'deprecated and will be replaced by ref_channels="average" in '
+             '0.16.', DeprecationWarning)
+        ref_channels = 'average'
+
+    if projection is None and ref_channels == 'average':
+        warn('The behavior of set_eeg_reference will change in 0.16 when '
+             'ref_channels="average". Currently, a projection is '
+             'computed, which has to be applied manually with the apply_proj '
+             'method. In 0.16, the average reference will be directly applied.'
+             ' Set projection=True if you want to retain the old behavior, or '
+             'set projection=False if you want the new behavior.',
+             DeprecationWarning)
+        if ref_channels == 'average':
+            projection = True
+        else:
+            projection = False
+
+    if ref_channels != 'average' and projection:
+        raise ValueError('Setting projection=True is only supported for '
+                         'ref_channels="average".')
+
+    if ref_channels == 'average' and projection:  # average reference projector
         if _has_eeg_average_ref_proj(inst.info['projs']):
             warn('An average reference projection was already added. The data '
                  'has been left untouched.')
             return inst, None
         else:
-            inst.add_proj(make_eeg_average_ref_proj(inst.info, activate=False))
+            # Creating an average reference may fail. In this case, make
+            # sure that the custom_ref_applied flag is left untouched.
+            custom_ref_applied = inst.info['custom_ref_applied']
+            try:
+                inst.info['custom_ref_applied'] = False
+                inst.add_proj(make_eeg_average_ref_proj(inst.info,
+                              activate=False))
+                # If the data has been preloaded, projections will no
+                # longer be automatically applied.
+                if inst.preload:
+                    logger.info('Average reference projection was added, '
+                                'but has not been applied yet. Use the '
+                                'apply_proj method to apply it.')
+            except Exception:
+                inst.info['custom_ref_applied'] = custom_ref_applied
+                raise
             return inst, None
+
+    inst = inst.copy() if copy else inst
+
+    if ref_channels == 'average' and not projection:  # apply average reference
+        logger.info('Applying average reference.')
+        eeg_idx = pick_types(inst.info, eeg=True, meg=False, ref_meg=False)
+        ref_from = [inst.ch_names[i] for i in eeg_idx]
+        return _apply_reference(inst, ref_from)
+
+    if ref_channels == []:
+        logger.info('EEG data marked as already having the desired reference. '
+                    'Preventing automatic future re-referencing to an average '
+                    'reference.')
     else:
         logger.info('Applying a custom EEG reference.')
-        inst = inst.copy() if copy else inst
-        return _apply_reference(inst, ref_channels)
 
+    return _apply_reference(inst, ref_channels)
 
+
+ at verbose
 def set_bipolar_reference(inst, anode, cathode, ch_name=None, ch_info=None,
-                          copy=True):
-    """Rereference selected channels using a bipolar referencing scheme.
+                          copy=True, verbose=None):
+    """Re-reference selected channels using a bipolar referencing scheme.
 
     A bipolar reference takes the difference between two channels (the anode
     minus the cathode) and adds it as a new virtual channel. The original
@@ -331,6 +465,9 @@ def set_bipolar_reference(inst, anode, cathode, ch_name=None, ch_info=None,
     copy : bool
         Whether to operate on a copy of the data (True) or modify it in-place
         (False). Defaults to True.
+    verbose : bool, str, int, or None
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -360,7 +497,8 @@ def set_bipolar_reference(inst, anode, cathode, ch_name=None, ch_info=None,
         cathode = [cathode]
 
     if len(anode) != len(cathode):
-        raise ValueError('Number of anodes must equal the number of cathodes.')
+        raise ValueError('Number of anodes (got %d) must equal the number '
+                         'of cathodes (got %d).' % (len(anode), len(cathode)))
 
     if ch_name is None:
         ch_name = ['%s-%s' % ac for ac in zip(anode, cathode)]
@@ -368,7 +506,7 @@ def set_bipolar_reference(inst, anode, cathode, ch_name=None, ch_info=None,
         ch_name = [ch_name]
     if len(ch_name) != len(anode):
         raise ValueError('Number of channel names must equal the number of '
-                         'anodes/cathodes.')
+                         'anodes/cathodes (got %d).' % len(ch_name))
 
     # Check for duplicate channel names (it is allowed to give the name of the
     # anode or cathode channel, as they will be replaced).
@@ -387,30 +525,40 @@ def set_bipolar_reference(inst, anode, cathode, ch_name=None, ch_info=None,
                          'number of anodes/cathodes.')
 
     # Merge specified and anode channel information dictionaries
-    new_ch_info = []
+    new_chs = []
     for an, ci in zip(anode, ch_info):
-        new_info = inst.info['chs'][inst.ch_names.index(an)].copy()
+        an_idx = inst.ch_names.index(an)
+        this_chs = deepcopy(inst.info['chs'][an_idx])
 
         # Set channel location and coil type
-        new_info['loc'] = np.zeros(12)
-        new_info['coil_type'] = FIFF.FIFFV_COIL_EEG_BIPOLAR
+        this_chs['loc'] = np.zeros(12)
+        this_chs['coil_type'] = FIFF.FIFFV_COIL_EEG_BIPOLAR
 
-        new_info.update(ci)
-        new_ch_info.append(new_info)
+        this_chs.update(ci)
+        new_chs.append(this_chs)
 
     if copy:
         inst = inst.copy()
 
-    # Perform bipolar referencing
-    for an, ca, name, info in zip(anode, cathode, ch_name, new_ch_info):
+    rem_ca = list(cathode)
+    for i, (an, ca, name, chs) in enumerate(
+            zip(anode, cathode, ch_name, new_chs)):
+        if an in anode[i + 1:]:
+            # Make a copy of anode if it's still needed later
+            # otherwise it's modified inplace
+            _copy_channel(inst, an, 'TMP')
+            an = 'TMP'
         _apply_reference(inst, [ca], [an])
         an_idx = inst.ch_names.index(an)
-        inst.info['chs'][an_idx] = info
+        inst.info['chs'][an_idx] = chs
         inst.info['chs'][an_idx]['ch_name'] = name
         logger.info('Bipolar channel added as "%s".' % name)
         inst.info._update_redundant()
+        if an in rem_ca:
+            idx = rem_ca.index(an)
+            del rem_ca[idx]
 
-    # Drop cathode channels
-    inst.drop_channels(cathode)
+    # Drop remaining cathode channels
+    inst.drop_channels(rem_ca)
 
     return inst
diff --git a/mne/io/tag.py b/mne/io/tag.py
index 689e90d..1934618 100644
--- a/mne/io/tag.py
+++ b/mne/io/tag.py
@@ -19,7 +19,7 @@ from ..externals.jdcal import jd2jcal
 # HELPERS
 
 class Tag(object):
-    """Tag in FIF tree structure
+    """Tag in FIF tree structure.
 
     Parameters
     ----------
@@ -35,7 +35,7 @@ class Tag(object):
         Position of Tag is the original file.
     """
 
-    def __init__(self, kind, type_, size, next, pos=None):
+    def __init__(self, kind, type_, size, next, pos=None):  # noqa: D102
         self.kind = int(kind)
         self.type = int(type_)
         self.size = int(size)
@@ -44,7 +44,7 @@ class Tag(object):
         self.pos = int(self.pos)
         self.data = None
 
-    def __repr__(self):
+    def __repr__(self):  # noqa: D105
         out = ("kind: %s - type: %s - size: %s - next: %s - pos: %s"
                % (self.kind, self.type, self.size, self.next, self.pos))
         if hasattr(self, 'data'):
@@ -52,7 +52,7 @@ class Tag(object):
         out += "\n"
         return out
 
-    def __cmp__(self, tag):
+    def __cmp__(self, tag):  # noqa: D105
         return int(self.kind == tag.kind and
                    self.type == tag.type and
                    self.size == tag.size and
@@ -62,7 +62,7 @@ class Tag(object):
 
 
 def read_big(fid, size=None):
-    """Function to read large chunks of data (>16MB) Windows-friendly
+    """Read large chunks of data (>16MB) Windows-friendly.
 
     Parameters
     ----------
@@ -136,8 +136,7 @@ def read_big(fid, size=None):
 
 
 def read_tag_info(fid):
-    """Read Tag info (or header)
-    """
+    """Read Tag info (or header)."""
     tag = _read_tag_header(fid)
     if tag is None:
         return None
@@ -149,7 +148,7 @@ def read_tag_info(fid):
 
 
 def _fromstring_rows(fid, tag_size, dtype=None, shape=None, rlims=None):
-    """Helper for getting a range of rows from a large tag"""
+    """Get a range of rows from a large tag."""
     if shape is not None:
         item_size = np.dtype(dtype).itemsize
         if not len(shape) == 2:
@@ -181,22 +180,22 @@ def _fromstring_rows(fid, tag_size, dtype=None, shape=None, rlims=None):
 
 
 def _loc_to_coil_trans(loc):
-    """Helper to convert loc vector to coil_trans"""
-    # deal with nasty OSX Anaconda bug by casting to float64
-    loc = loc.astype(np.float64)
-    coil_trans = np.concatenate([loc.reshape(4, 3).T[:, [1, 2, 3, 0]],
-                                 np.array([0, 0, 0, 1]).reshape(1, 4)])
+    """Convert loc vector to coil_trans."""
+    coil_trans = np.zeros((4, 4))
+    coil_trans[:3, 3] = loc[:3]
+    coil_trans[:3, :3] = np.reshape(loc[3:], (3, 3)).T
+    coil_trans[-1, -1] = 1.
     return coil_trans
 
 
 def _coil_trans_to_loc(coil_trans):
-    """Helper to convert coil_trans to loc"""
+    """Convert coil_trans to loc."""
     coil_trans = coil_trans.astype(np.float64)
     return np.roll(coil_trans.T[:, :3], 1, 0).flatten()
 
 
 def _loc_to_eeg_loc(loc):
-    """Helper to convert a loc to an EEG loc"""
+    """Convert a loc to an EEG loc."""
     if loc[3:6].any():
         return np.array([loc[0:3], loc[3:6]]).T
     else:
@@ -219,7 +218,7 @@ _data_type = 65535      # ffff
 
 
 def _read_tag_header(fid):
-    """Read only the header of a Tag"""
+    """Read only the header of a Tag."""
     s = fid.read(4 * 4)
     if len(s) == 0:
         return None
@@ -228,7 +227,7 @@ def _read_tag_header(fid):
 
 
 def _read_matrix(fid, tag, shape, rlims, matrix_coding):
-    """Read a matrix (dense or sparse) tag"""
+    """Read a matrix (dense or sparse) tag."""
     matrix_coding = matrix_coding >> 16
 
     # This should be easy to implement (see _fromstring_rows)
@@ -326,20 +325,20 @@ def _read_matrix(fid, tag, shape, rlims, matrix_coding):
 
 
 def _read_simple(fid, tag, shape, rlims, dtype):
-    """Read simple datatypes from tag (typically used with partial)"""
+    """Read simple datatypes from tag (typically used with partial)."""
     return _fromstring_rows(fid, tag.size, dtype=dtype, shape=shape,
                             rlims=rlims)
 
 
 def _read_string(fid, tag, shape, rlims):
-    """Read a string tag"""
+    """Read a string tag."""
     # Always decode to unicode.
     d = _fromstring_rows(fid, tag.size, dtype='>c', shape=shape, rlims=rlims)
     return text_type(d.tostring().decode('utf-8', 'ignore'))
 
 
 def _read_complex_float(fid, tag, shape, rlims):
-    """Read complex float tag"""
+    """Read complex float tag."""
     # data gets stored twice as large
     if shape is not None:
         shape = (shape[0], shape[1] * 2)
@@ -349,7 +348,7 @@ def _read_complex_float(fid, tag, shape, rlims):
 
 
 def _read_complex_double(fid, tag, shape, rlims):
-    """Read complex double tag"""
+    """Read complex double tag."""
     # data gets stored twice as large
     if shape is not None:
         shape = (shape[0], shape[1] * 2)
@@ -359,7 +358,7 @@ def _read_complex_double(fid, tag, shape, rlims):
 
 
 def _read_id_struct(fid, tag, shape, rlims):
-    """Read ID struct tag"""
+    """Read ID struct tag."""
     return dict(
         version=int(np.fromstring(fid.read(4), dtype=">i4")),
         machid=np.fromstring(fid.read(8), dtype=">i4"),
@@ -368,7 +367,7 @@ def _read_id_struct(fid, tag, shape, rlims):
 
 
 def _read_dig_point_struct(fid, tag, shape, rlims):
-    """Read dig point struct tag"""
+    """Read dig point struct tag."""
     return dict(
         kind=int(np.fromstring(fid.read(4), dtype=">i4")),
         ident=int(np.fromstring(fid.read(4), dtype=">i4")),
@@ -377,7 +376,7 @@ def _read_dig_point_struct(fid, tag, shape, rlims):
 
 
 def _read_coord_trans_struct(fid, tag, shape, rlims):
-    """Read coord trans struct tag"""
+    """Read coord trans struct tag."""
     from ..transforms import Transform
     fro = int(np.fromstring(fid.read(4), dtype=">i4"))
     to = int(np.fromstring(fid.read(4), dtype=">i4"))
@@ -398,7 +397,7 @@ _coord_dict = {
 
 
 def _read_ch_info_struct(fid, tag, shape, rlims):
-    """Read channel info struct tag"""
+    """Read channel info struct tag."""
     d = dict(
         scanno=int(np.fromstring(fid.read(4), dtype=">i4")),
         logno=int(np.fromstring(fid.read(4), dtype=">i4")),
@@ -422,7 +421,7 @@ def _read_ch_info_struct(fid, tag, shape, rlims):
 
 
 def _read_old_pack(fid, tag, shape, rlims):
-    """Read old pack tag"""
+    """Read old pack tag."""
     offset = float(np.fromstring(fid.read(4), dtype=">f4"))
     scale = float(np.fromstring(fid.read(4), dtype=">f4"))
     data = np.fromstring(fid.read(tag.size - 8), dtype=">i2")
@@ -432,14 +431,15 @@ def _read_old_pack(fid, tag, shape, rlims):
 
 
 def _read_dir_entry_struct(fid, tag, shape, rlims):
-    """Read dir entry struct tag"""
+    """Read dir entry struct tag."""
     return [_read_tag_header(fid) for _ in range(tag.size // 16 - 1)]
 
 
 def _read_julian(fid, tag, shape, rlims):
-    """Read julian tag"""
+    """Read julian tag."""
     return jd2jcal(int(np.fromstring(fid.read(4), dtype=">i4")))
 
+
 # Read types call dict
 _call_dict = {
     FIFF.FIFFT_STRING: _read_string,
@@ -470,7 +470,7 @@ for key, dtype in _simple_dict.items():
 
 
 def read_tag(fid, pos=None, shape=None, rlims=None):
-    """Read a Tag from a file at a given position
+    """Read a Tag from a file at a given position.
 
     Parameters
     ----------
@@ -514,7 +514,7 @@ def read_tag(fid, pos=None, shape=None, rlims=None):
 
 
 def find_tag(fid, node, findkind):
-    """Find Tag in an open FIF file descriptor
+    """Find Tag in an open FIF file descriptor.
 
     Parameters
     ----------
@@ -538,8 +538,7 @@ def find_tag(fid, node, findkind):
 
 
 def has_tag(node, kind):
-    """Does the node contains a Tag of a given kind?
-    """
+    """Check if the node contains a Tag of a given kind."""
     for d in node['directory']:
         if d.kind == kind:
             return True
diff --git a/mne/io/tests/test_apply_function.py b/mne/io/tests/test_apply_function.py
index 1fb935b..155fbdf 100644
--- a/mne/io/tests/test_apply_function.py
+++ b/mne/io/tests/test_apply_function.py
@@ -3,11 +3,12 @@
 # License: BSD (3-clause)
 
 import numpy as np
-from nose.tools import assert_equal, assert_raises
+from nose.tools import assert_equal, assert_raises, assert_true
+import pytest
 
 from mne import create_info
 from mne.io import RawArray
-from mne.utils import logger, catch_logging, slow_test, run_tests_if_main
+from mne.utils import logger, catch_logging, run_tests_if_main
 
 
 def bad_1(x):
@@ -23,7 +24,7 @@ def printer(x):
     return x
 
 
- at slow_test
+ at pytest.mark.slowtest
 def test_apply_function_verbose():
     """Test apply function verbosity
     """
@@ -33,20 +34,18 @@ def test_apply_function_verbose():
     raw = RawArray(np.zeros((n_chan, n_times)),
                    create_info(ch_names, 1., 'mag'))
     # test return types in both code paths (parallel / 1 job)
-    assert_raises(TypeError, raw.apply_function, bad_1,
-                  None, None, 1)
-    assert_raises(ValueError, raw.apply_function, bad_2,
-                  None, None, 1)
-    assert_raises(TypeError, raw.apply_function, bad_1,
-                  None, None, 2)
-    assert_raises(ValueError, raw.apply_function, bad_2,
-                  None, None, 2)
+    assert_raises(TypeError, raw.apply_function, bad_1)
+    assert_raises(ValueError, raw.apply_function, bad_2)
+    assert_raises(TypeError, raw.apply_function, bad_1, n_jobs=2)
+    assert_raises(ValueError, raw.apply_function, bad_2, n_jobs=2)
 
     # check our arguments
     with catch_logging() as sio:
-        raw.apply_function(printer, None, None, 1, verbose=False)
+        out = raw.apply_function(printer, verbose=False)
         assert_equal(len(sio.getvalue()), 0)
-        raw.apply_function(printer, None, None, 1, verbose=True)
+        assert_true(out is raw)
+        raw.apply_function(printer, verbose=True)
         assert_equal(sio.getvalue().count('\n'), n_chan)
 
+
 run_tests_if_main()
diff --git a/mne/io/tests/test_compensator.py b/mne/io/tests/test_compensator.py
index 80d8dbf..01d0365 100644
--- a/mne/io/tests/test_compensator.py
+++ b/mne/io/tests/test_compensator.py
@@ -19,7 +19,7 @@ ctf_comp_fname = op.join(base_dir, 'test_ctf_comp_raw.fif')
 def test_compensation():
     """Test compensation."""
     tempdir = _TempDir()
-    raw = read_raw_fif(ctf_comp_fname, compensation=None, add_eeg_ref=False)
+    raw = read_raw_fif(ctf_comp_fname)
     assert_equal(get_current_comp(raw.info), 3)
     comp1 = make_compensator(raw.info, 3, 1, exclude_comp_chs=False)
     assert_true(comp1.shape == (340, 340))
@@ -40,12 +40,12 @@ def test_compensation():
             assert_allclose(np.dot(comp2, comp1), desired, atol=1e-12)
 
     # make sure that changing the comp doesn't modify the original data
-    raw2 = read_raw_fif(ctf_comp_fname, add_eeg_ref=False)
+    raw2 = read_raw_fif(ctf_comp_fname)
     raw2.apply_gradient_compensation(2)
     assert_equal(get_current_comp(raw2.info), 2)
     fname = op.join(tempdir, 'ctf-raw.fif')
     raw2.save(fname)
-    raw2 = read_raw_fif(fname, add_eeg_ref=False)
+    raw2 = read_raw_fif(fname)
     assert_equal(raw2.compensation_grade, 2)
     raw2.apply_gradient_compensation(3)
     assert_equal(raw2.compensation_grade, 3)
@@ -64,13 +64,12 @@ def test_compensation_mne():
 
     def make_evoked(fname, comp):
         """Make evoked data."""
-        raw = read_raw_fif(fname, add_eeg_ref=False)
+        raw = read_raw_fif(fname)
         if comp is not None:
             raw.apply_gradient_compensation(comp)
         picks = pick_types(raw.info, meg=True, ref_meg=True)
         events = np.array([[0, 0, 1]], dtype=np.int)
-        evoked = Epochs(raw, events, 1, 0, 20e-3, picks=picks,
-                        add_eeg_ref=False).average()
+        evoked = Epochs(raw, events, 1, 0, 20e-3, picks=picks).average()
         return evoked
 
     def compensate_mne(fname, comp):
diff --git a/mne/io/tests/test_meas_info.py b/mne/io/tests/test_meas_info.py
index 1656319..9ab8b17 100644
--- a/mne/io/tests/test_meas_info.py
+++ b/mne/io/tests/test_meas_info.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-
+import warnings
 import os.path as op
 
 from nose.tools import assert_false, assert_equal, assert_raises, assert_true
@@ -17,6 +17,8 @@ from mne.io.meas_info import (Info, create_info, _write_dig_points,
 from mne.utils import _TempDir, run_tests_if_main
 from mne.channels.montage import read_montage, read_dig_montage
 
+warnings.simplefilter("always")  # ensure we can verify expected warnings
+
 base_dir = op.join(op.dirname(__file__), 'data')
 fiducials_fname = op.join(base_dir, 'fsaverage-fiducials.fif')
 raw_fname = op.join(base_dir, 'test_raw.fif')
@@ -84,6 +86,7 @@ def test_make_info():
     assert_array_equal(ch_pos, m.pos)
     idents = [p['ident'] for p in info['dig']]
     assert_true(FIFF.FIFFV_POINT_NASION in idents)
+    assert_array_equal(info['meas_date'], [0, 0])
 
 
 def test_fiducials_io():
@@ -110,17 +113,15 @@ def test_fiducials_io():
 
 def test_info():
     """Test info object."""
-    raw = read_raw_fif(raw_fname, add_eeg_ref=False)
+    raw = read_raw_fif(raw_fname)
     event_id, tmin, tmax = 1, -0.2, 0.5
     events = read_events(event_name)
     event_id = int(events[0, 2])
     epochs = Epochs(raw, events[:1], event_id, tmin, tmax, picks=None,
-                    baseline=(None, 0), add_eeg_ref=False)
+                    baseline=(None, 0))
 
     evoked = epochs.average()
 
-    events = read_events(event_name)
-
     # Test subclassing was successful.
     info = Info(a=7, b='aaaaa')
     assert_true('a' in info)
@@ -134,6 +135,7 @@ def test_info():
         info_str = '%s' % obj.info
         assert_equal(len(info_str.split('\n')), len(obj.info.keys()) + 2)
         assert_true(all(k in info_str for k in obj.info.keys()))
+        assert '2002-12-03 19:01:10 GMT' in repr(obj.info), repr(obj.info)
 
     # Test read-only fields
     info = raw.info.copy()
@@ -209,20 +211,20 @@ def test_io_dig_points():
 
 def test_make_dig_points():
     """Test application of Polhemus HSP to info."""
-    dig_points = _read_dig_points(hsp_fname)
+    extra_points = _read_dig_points(hsp_fname)
     info = create_info(ch_names=['Test Ch'], sfreq=1000., ch_types=None)
     assert_false(info['dig'])
 
-    info['dig'] = _make_dig_points(dig_points=dig_points)
+    info['dig'] = _make_dig_points(extra_points=extra_points)
     assert_true(info['dig'])
     assert_allclose(info['dig'][0]['r'], [-.10693, .09980, .06881])
 
-    dig_points = _read_dig_points(elp_fname)
-    nasion, lpa, rpa = dig_points[:3]
+    elp_points = _read_dig_points(elp_fname)
+    nasion, lpa, rpa = elp_points[:3]
     info = create_info(ch_names=['Test Ch'], sfreq=1000., ch_types=None)
     assert_false(info['dig'])
 
-    info['dig'] = _make_dig_points(nasion, lpa, rpa, dig_points[3:], None)
+    info['dig'] = _make_dig_points(nasion, lpa, rpa, elp_points[3:], None)
     assert_true(info['dig'])
     idx = [d['ident'] for d in info['dig']].index(FIFF.FIFFV_POINT_NASION)
     assert_array_equal(info['dig'][idx]['r'],
@@ -231,9 +233,9 @@ def test_make_dig_points():
     assert_raises(ValueError, _make_dig_points, None, lpa[:2])
     assert_raises(ValueError, _make_dig_points, None, None, rpa[:2])
     assert_raises(ValueError, _make_dig_points, None, None, None,
-                  dig_points[:, :2])
+                  elp_points[:, :2])
     assert_raises(ValueError, _make_dig_points, None, None, None, None,
-                  dig_points[:, :2])
+                  elp_points[:, :2])
 
 
 def test_redundant():
@@ -315,6 +317,13 @@ def test_check_consistency():
     info2['lowpass'] = 'foo'
     assert_raises(ValueError, info2._check_consistency)
 
+    info2 = info.copy()
+    info2['filename'] = 'foo'
+    with warnings.catch_warnings(record=True) as w:
+        info2._check_consistency()
+    assert_equal(len(w), 1)
+    assert_true(all('filename' in str(ww.message) for ww in w))
+
     # Silent type conversion to float
     info2 = info.copy()
     info2['sfreq'] = 1
@@ -330,13 +339,20 @@ def test_check_consistency():
     info2['chs'][2]['ch_name'] = 'b'
     assert_raises(RuntimeError, info2._check_consistency)
 
+    # Duplicates appended with running numbers
+    with warnings.catch_warnings(record=True) as w:
+        info3 = create_info(ch_names=['a', 'b', 'b', 'c', 'b'], sfreq=1000.)
+    assert_equal(len(w), 1)
+    assert_true(all('Channel names are not' in '%s' % ww.message for ww in w))
+    assert_array_equal(info3['ch_names'], ['a', 'b-0', 'b-1', 'c', 'b-2'])
+
 
 def test_anonymize():
-    """Checks that sensitive information can be anonymized."""
+    """Test that sensitive information can be anonymized."""
     assert_raises(ValueError, anonymize_info, 'foo')
 
     # Fake some subject data
-    raw = read_raw_fif(raw_fname, add_eeg_ref=False)
+    raw = read_raw_fif(raw_fname)
     raw.info['subject_info'] = dict(id=1, his_id='foobar', last_name='bar',
                                     first_name='bar', birthday=(1987, 4, 8),
                                     sex=0, hand=1)
@@ -345,7 +361,7 @@ def test_anonymize():
     orig_meas_id = raw.info['meas_id']['secs']
     # Test instance method
     events = read_events(event_name)
-    epochs = Epochs(raw, events[:1], 2, 0., 0.1, add_eeg_ref=False)
+    epochs = Epochs(raw, events[:1], 2, 0., 0.1)
     for inst in [raw, epochs]:
         assert_true('subject_info' in inst.info.keys())
         assert_true(inst.info['subject_info'] is not None)
@@ -356,19 +372,24 @@ def test_anonymize():
         assert_true('subject_info' not in inst.info.keys())
         assert_equal(inst.info['file_id']['secs'], 0)
         assert_equal(inst.info['meas_id']['secs'], 0)
-        assert_equal(inst.info['meas_date'], [0, 0])
+        assert_array_equal(inst.info['meas_date'], [0, 0])
 
     # When we write out with raw.save, these get overwritten with the
     # new save time
     tempdir = _TempDir()
     out_fname = op.join(tempdir, 'test_subj_info_raw.fif')
     raw.save(out_fname, overwrite=True)
-    raw = read_raw_fif(out_fname, add_eeg_ref=False)
+    raw = read_raw_fif(out_fname)
     assert_true(raw.info.get('subject_info') is None)
     assert_array_equal(raw.info['meas_date'], [0, 0])
     # XXX mne.io.write.write_id necessarily writes secs
     assert_true(raw.info['file_id']['secs'] != orig_file_id)
     assert_true(raw.info['meas_id']['secs'] != orig_meas_id)
 
+    # Test no error for incomplete info
+    info = raw.info.copy()
+    info.pop('file_id')
+    anonymize_info(info)
+
 
 run_tests_if_main()
diff --git a/mne/io/tests/test_pick.py b/mne/io/tests/test_pick.py
index 89bac51..215d41f 100644
--- a/mne/io/tests/test_pick.py
+++ b/mne/io/tests/test_pick.py
@@ -8,7 +8,8 @@ import numpy as np
 
 from mne import (pick_channels_regexp, pick_types, Epochs,
                  read_forward_solution, rename_channels,
-                 pick_info, pick_channels, __file__, create_info)
+                 pick_info, pick_channels, create_info)
+from mne import __file__ as _root_init_fname
 from mne.io import (read_raw_fif, RawArray, read_raw_bti, read_raw_kit,
                     read_info)
 from mne.io.pick import (channel_indices_by_type, channel_type,
@@ -25,8 +26,7 @@ fname_mc = op.join(data_path, 'SSS', 'test_move_anon_movecomp_raw_sss.fif')
 
 
 def test_pick_refs():
-    """Test picking of reference sensors
-    """
+    """Test picking of reference sensors."""
     infos = list()
     # KIT
     kit_dir = op.join(io_dir, 'kit', 'tests', 'data')
@@ -46,7 +46,7 @@ def test_pick_refs():
     infos.append(raw_bti.info)
     # CTF
     fname_ctf_raw = op.join(io_dir, 'tests', 'data', 'test_ctf_comp_raw.fif')
-    raw_ctf = read_raw_fif(fname_ctf_raw, add_eeg_ref=False)
+    raw_ctf = read_raw_fif(fname_ctf_raw)
     raw_ctf.apply_gradient_compensation(2)
     infos.append(raw_ctf.info)
     for info in infos:
@@ -109,14 +109,14 @@ def test_pick_seeg_ecog():
         assert_equal(channel_type(info, i), types[i])
     raw = RawArray(np.zeros((len(names), 10)), info)
     events = np.array([[1, 0, 0], [2, 0, 0]])
-    epochs = Epochs(raw, events, {'event': 0}, -1e-5, 1e-5, add_eeg_ref=False)
+    epochs = Epochs(raw, events, {'event': 0}, -1e-5, 1e-5)
     evoked = epochs.average(pick_types(epochs.info, meg=True, seeg=True))
     e_seeg = evoked.copy().pick_types(meg=False, seeg=True)
     for l, r in zip(e_seeg.ch_names, [names[4], names[5], names[7]]):
         assert_equal(l, r)
     # Deal with constant debacle
     raw = read_raw_fif(op.join(io_dir, 'tests', 'data',
-                               'test_chpi_raw_sss.fif'), add_eeg_ref=False)
+                               'test_chpi_raw_sss.fif'))
     assert_equal(len(pick_types(raw.info, meg=False, seeg=True, ecog=True)), 0)
 
 
@@ -210,7 +210,7 @@ def test_pick_forward_seeg_ecog():
 
 
 def test_picks_by_channels():
-    """Test creating pick_lists"""
+    """Test creating pick_lists."""
     rng = np.random.RandomState(909)
 
     test_data = rng.random_sample((4, 2000))
@@ -237,6 +237,9 @@ def test_picks_by_channels():
     sfreq = 250.0
     info = create_info(ch_names=ch_names, sfreq=sfreq, ch_types=ch_types)
     raw = RawArray(test_data, info)
+    # This acts as a set, not an order
+    assert_array_equal(pick_channels(info['ch_names'], ['MEG 002', 'MEG 001']),
+                       [0, 1])
 
     # Make sure checks for list input work.
     assert_raises(ValueError, pick_channels, ch_names, 'MEG 001')
@@ -254,11 +257,11 @@ def test_picks_by_channels():
 
 
 def test_clean_info_bads():
-    """Test cleaning info['bads'] when bad_channels are excluded """
+    """Test cleaning info['bads'] when bad_channels are excluded."""
 
-    raw_file = op.join(op.dirname(__file__), 'io', 'tests', 'data',
+    raw_file = op.join(op.dirname(_root_init_fname), 'io', 'tests', 'data',
                        'test_raw.fif')
-    raw = read_raw_fif(raw_file, add_eeg_ref=False)
+    raw = read_raw_fif(raw_file)
 
     # select eeg channels
     picks_eeg = pick_types(raw.info, meg=False, eeg=True)
diff --git a/mne/io/tests/test_proc_history.py b/mne/io/tests/test_proc_history.py
index aa4a58c..36316fa 100644
--- a/mne/io/tests/test_proc_history.py
+++ b/mne/io/tests/test_proc_history.py
@@ -4,9 +4,9 @@
 
 import numpy as np
 import os.path as op
-from mne.io import read_info
+from mne.io import read_info, read_raw_fif
 from mne.io.constants import FIFF
-from mne.io.proc_history import _get_sss_rank
+from mne.io.proc_history import _get_rank_sss
 from nose.tools import assert_true, assert_equal
 
 base_dir = op.join(op.dirname(__file__), 'data')
@@ -40,7 +40,8 @@ def test_maxfilter_io():
 
 def test_maxfilter_get_rank():
     """Test maxfilter rank lookup."""
-    mf = read_info(raw_fname)['proc_history'][0]['max_info']
+    raw = read_raw_fif(raw_fname)
+    mf = raw.info['proc_history'][0]['max_info']
     rank1 = mf['sss_info']['nfree']
-    rank2 = _get_sss_rank(mf)
+    rank2 = _get_rank_sss(raw)
     assert_equal(rank1, rank2)
diff --git a/mne/io/tests/test_raw.py b/mne/io/tests/test_raw.py
index b46c148..a4144a5 100644
--- a/mne/io/tests/test_raw.py
+++ b/mne/io/tests/test_raw.py
@@ -49,8 +49,8 @@ def _test_raw_reader(reader, test_preloading=True, **kwargs):
         other_raws = [reader(preload=buffer_fname, **kwargs),
                       reader(preload=False, **kwargs)]
         for sl_time in slices:
+            data1, times1 = raw[picks, sl_time]
             for other_raw in other_raws:
-                data1, times1 = raw[picks, sl_time]
                 data2, times2 = other_raw[picks, sl_time]
                 assert_allclose(data1, data2)
                 assert_allclose(times1, times2)
@@ -63,8 +63,9 @@ def _test_raw_reader(reader, test_preloading=True, **kwargs):
 
     # Test saving and reading
     out_fname = op.join(tempdir, 'test_raw.fif')
+    raw = concatenate_raws([raw])
     raw.save(out_fname, tmax=raw.times[-1], overwrite=True, buffer_size_sec=1)
-    raw3 = read_raw_fif(out_fname, add_eeg_ref=False)
+    raw3 = read_raw_fif(out_fname)
     assert_equal(set(raw.info.keys()), set(raw3.info.keys()))
     assert_allclose(raw3[0:20][0], full_data[0:20], rtol=1e-6,
                     atol=1e-20)  # atol is very small but > 0
@@ -84,6 +85,9 @@ def _test_raw_reader(reader, test_preloading=True, **kwargs):
     assert_equal(concat_raw.n_times, 2 * raw.n_times)
     assert_equal(concat_raw.first_samp, first_samp)
     assert_equal(concat_raw.last_samp - last_samp + first_samp, last_samp + 1)
+    idx = np.where(concat_raw.annotations.description == 'BAD boundary')[0]
+    assert_array_almost_equal([(last_samp - first_samp) / raw.info['sfreq']],
+                              concat_raw.annotations.onset[idx], decimal=2)
     return raw
 
 
@@ -107,10 +111,10 @@ def _test_concat(reader, *args):
             for last_preload in (True, False):
                 t_crops = raw.times[np.argmin(np.abs(raw.times - 0.5)) +
                                     [0, 1]]
-                raw1 = raw.copy().crop(0, t_crops[0], copy=False)
+                raw1 = raw.copy().crop(0, t_crops[0])
                 if preloads[0]:
                     raw1.load_data()
-                raw2 = raw.copy().crop(t_crops[1], None, copy=False)
+                raw2 = raw.copy().crop(t_crops[1], None)
                 if preloads[1]:
                     raw2.load_data()
                 raw1.append(raw2)
@@ -124,7 +128,7 @@ def test_time_index():
     """Test indexing of raw times."""
     raw_fname = op.join(op.dirname(__file__), '..', '..', 'io', 'tests',
                         'data', 'test_raw.fif')
-    raw = read_raw_fif(raw_fname, add_eeg_ref=False)
+    raw = read_raw_fif(raw_fname)
 
     # Test original (non-rounding) indexing behavior
     orig_inds = raw.time_as_index(raw.times)
diff --git a/mne/io/tests/test_reference.py b/mne/io/tests/test_reference.py
index 1cddc25..5f28b36 100644
--- a/mne/io/tests/test_reference.py
+++ b/mne/io/tests/test_reference.py
@@ -11,13 +11,13 @@ import numpy as np
 from nose.tools import assert_true, assert_equal, assert_raises
 from numpy.testing import assert_array_equal, assert_allclose
 
-from mne import (pick_channels, pick_types, Evoked, Epochs, read_events,
+from mne import (pick_channels, pick_types, Epochs, read_events,
                  set_eeg_reference, set_bipolar_reference,
                  add_reference_channels)
-from mne.epochs import _BaseEpochs
+from mne.epochs import BaseEpochs
 from mne.io import read_raw_fif
 from mne.io.constants import FIFF
-from mne.io.proj import _has_eeg_average_ref_proj
+from mne.io.proj import _has_eeg_average_ref_proj, Projection
 from mne.io.reference import _apply_reference
 from mne.datasets import testing
 from mne.utils import run_tests_if_main
@@ -41,15 +41,12 @@ def _test_reference(raw, reref, ref_data, ref_from):
     picks_ref = [raw.ch_names.index(ch) for ch in ref_from]
 
     # Get data
-    if isinstance(raw, Evoked):
-        _data = raw.data
-        _reref = reref.data
-    else:
-        _data = raw._data
-        _reref = reref._data
+    _data = raw._data
+    _reref = reref._data
 
     # Check that the ref has been properly computed
-    assert_array_equal(ref_data, _data[..., picks_ref, :].mean(-2))
+    if ref_data is not None:
+        assert_array_equal(ref_data, _data[..., picks_ref, :].mean(-2))
 
     # Get the raw EEG data and other channel data
     raw_eeg_data = _data[..., picks_eeg, :]
@@ -59,21 +56,22 @@ def _test_reference(raw, reref, ref_data, ref_from):
     reref_eeg_data = _reref[..., picks_eeg, :]
     reref_other_data = _reref[..., picks_other, :]
 
-    # Undo rereferencing of EEG channels
-    if isinstance(raw, _BaseEpochs):
-        unref_eeg_data = reref_eeg_data + ref_data[:, np.newaxis, :]
-    else:
-        unref_eeg_data = reref_eeg_data + ref_data
-
-    # Check that both EEG data and other data is the same
-    assert_allclose(raw_eeg_data, unref_eeg_data, 1e-6, atol=1e-15)
+    # Check that non-EEG channels are untouched
     assert_allclose(raw_other_data, reref_other_data, 1e-6, atol=1e-15)
 
+    # Undo rereferencing of EEG channels if possible
+    if ref_data is not None:
+        if isinstance(raw, BaseEpochs):
+            unref_eeg_data = reref_eeg_data + ref_data[:, np.newaxis, :]
+        else:
+            unref_eeg_data = reref_eeg_data + ref_data
+        assert_allclose(raw_eeg_data, unref_eeg_data, 1e-6, atol=1e-15)
+
 
 @testing.requires_testing_data
 def test_apply_reference():
     """Test base function for rereferencing."""
-    raw = read_raw_fif(fif_fname, preload=True, add_eeg_ref=False)
+    raw = read_raw_fif(fif_fname, preload=True)
 
     # Rereference raw data by creating a copy of original data
     reref, ref_data = _apply_reference(
@@ -84,20 +82,20 @@ def test_apply_reference():
     # The CAR reference projection should have been removed by the function
     assert_true(not _has_eeg_average_ref_proj(reref.info['projs']))
 
-    # Test that disabling the reference does not break anything
-    reref, ref_data = _apply_reference(raw, [])
-    assert_array_equal(raw._data, reref._data)
-
     # Test that data is modified in place when copy=False
     reref, ref_data = _apply_reference(raw, ['EEG 001', 'EEG 002'])
     assert_true(raw is reref)
 
+    # Test that disabling the reference does not change anything
+    reref, ref_data = _apply_reference(raw.copy(), [])
+    assert_array_equal(raw._data, reref._data)
+
     # Test re-referencing Epochs object
-    raw = read_raw_fif(fif_fname, preload=False, add_eeg_ref=False)
+    raw = read_raw_fif(fif_fname, preload=False)
     events = read_events(eve_fname)
     picks_eeg = pick_types(raw.info, meg=False, eeg=True)
     epochs = Epochs(raw, events=events, event_id=1, tmin=-0.2, tmax=0.5,
-                    picks=picks_eeg, preload=True, add_eeg_ref=False)
+                    picks=picks_eeg, preload=True)
     reref, ref_data = _apply_reference(
         epochs.copy(), ref_from=['EEG 001', 'EEG 002'])
     assert_true(reref.info['custom_ref_applied'])
@@ -110,28 +108,64 @@ def test_apply_reference():
     assert_true(reref.info['custom_ref_applied'])
     _test_reference(evoked, reref, ref_data, ['EEG 001', 'EEG 002'])
 
-    # Test invalid input
-    raw_np = read_raw_fif(fif_fname, preload=False, add_eeg_ref=False)
+    # Referencing needs data to be preloaded
+    raw_np = read_raw_fif(fif_fname, preload=False)
     assert_raises(RuntimeError, _apply_reference, raw_np, ['EEG 001'])
 
+    # Test having inactive SSP projections that deal with channels involved
+    # during re-referencing
+    raw = read_raw_fif(fif_fname, preload=True)
+    raw.add_proj(
+        Projection(
+            active=False,
+            data=dict(
+                col_names=['EEG 001', 'EEG 002'],
+                row_names=None,
+                data=np.array([[1, 1]]),
+                ncol=2,
+                nrow=1
+            ),
+            desc='test',
+            kind=1,
+        )
+    )
+    # Projection concerns channels mentioned in projector
+    assert_raises(RuntimeError, _apply_reference, raw, ['EEG 001'])
+
+    # Projection does not concern channels mentioned in projector, no error
+    _apply_reference(raw, ['EEG 003'], ['EEG 004'])
+
 
 @testing.requires_testing_data
 def test_set_eeg_reference():
     """Test rereference eeg data."""
-    raw = read_raw_fif(fif_fname, preload=True, add_eeg_ref=False)
+    raw = read_raw_fif(fif_fname, preload=True)
     raw.info['projs'] = []
 
-    # Test setting an average reference
+    # Test setting an average reference projection
     assert_true(not _has_eeg_average_ref_proj(raw.info['projs']))
-    reref, ref_data = set_eeg_reference(raw)
+    reref, ref_data = set_eeg_reference(raw, projection=True)
     assert_true(_has_eeg_average_ref_proj(reref.info['projs']))
+    assert_true(not reref.info['projs'][0]['active'])
     assert_true(ref_data is None)
+    reref.apply_proj()
+    eeg_chans = [raw.ch_names[ch]
+                 for ch in pick_types(raw.info, meg=False, eeg=True)]
+    _test_reference(raw, reref, ref_data,
+                    [ch for ch in eeg_chans if ch not in raw.info['bads']])
 
     # Test setting an average reference when one was already present
-    with warnings.catch_warnings(record=True):  # weight tables
-        reref, ref_data = set_eeg_reference(raw, copy=False)
+    with warnings.catch_warnings(record=True):
+        reref, ref_data = set_eeg_reference(raw, copy=False, projection=True)
     assert_true(ref_data is None)
 
+    # Test setting an average reference on non-preloaded data
+    raw_nopreload = read_raw_fif(fif_fname, preload=False)
+    raw_nopreload.info['projs'] = []
+    reref, ref_data = set_eeg_reference(raw_nopreload, projection=True)
+    assert_true(_has_eeg_average_ref_proj(reref.info['projs']))
+    assert_true(not reref.info['projs'][0]['active'])
+
     # Rereference raw data by creating a copy of original data
     reref, ref_data = set_eeg_reference(raw, ['EEG 001', 'EEG 002'], copy=True)
     assert_true(reref.info['custom_ref_applied'])
@@ -142,11 +176,60 @@ def test_set_eeg_reference():
                                         copy=False)
     assert_true(raw is reref)
 
+    # Test moving from custom to average reference
+    reref, ref_data = set_eeg_reference(raw, ['EEG 001', 'EEG 002'])
+    reref, _ = set_eeg_reference(reref, projection=True)
+    assert_true(_has_eeg_average_ref_proj(reref.info['projs']))
+    assert_equal(reref.info['custom_ref_applied'], False)
+
+    # When creating an average reference fails, make sure the
+    # custom_ref_applied flag remains untouched.
+    reref = raw.copy()
+    reref.info['custom_ref_applied'] = True
+    reref.pick_types(eeg=False)  # Cause making average ref fail
+    assert_raises(ValueError, set_eeg_reference, reref, projection=True)
+    assert_true(reref.info['custom_ref_applied'])
+
+    # Test moving from average to custom reference
+    reref, ref_data = set_eeg_reference(raw, projection=True)
+    reref, _ = set_eeg_reference(reref, ['EEG 001', 'EEG 002'])
+    assert_true(not _has_eeg_average_ref_proj(reref.info['projs']))
+    assert_equal(reref.info['custom_ref_applied'], True)
+
+    # Test that disabling the reference does not change anything
+    reref, _ = set_eeg_reference(raw, [])
+    assert_array_equal(raw._data, reref._data)
+
+    # make sure ref_channels=[] removes average reference projectors
+    reref, _ = set_eeg_reference(raw, 'average', projection=True)
+    assert_true(_has_eeg_average_ref_proj(reref.info['projs']))
+    reref, _ = set_eeg_reference(reref, [])
+    assert_true(not _has_eeg_average_ref_proj(reref.info['projs']))
+
+    # Test that average reference gives identical results when calculated
+    # via SSP projection (projection=True) or directly (projection=False)
+    raw.info['projs'] = []
+    reref_1, _ = set_eeg_reference(raw.copy(), projection=True)
+    reref_1.apply_proj()
+    reref_2, _ = set_eeg_reference(raw.copy(), projection=False)
+    assert_allclose(reref_1._data, reref_2._data, rtol=1e-6, atol=1e-15)
+
+    # Test average reference without projection
+    reref, ref_data = set_eeg_reference(raw.copy(), ref_channels="average",
+                                        projection=False)
+    _test_reference(raw, reref, ref_data, eeg_chans)
+
+    # projection=True only works for ref_channels='average'
+    assert_raises(ValueError, set_eeg_reference, raw, [], True, True)
+    assert_raises(ValueError, set_eeg_reference, raw, ['EEG 001'], True, True)
+
 
 @testing.requires_testing_data
 def test_set_bipolar_reference():
     """Test bipolar referencing."""
-    raw = read_raw_fif(fif_fname, preload=True, add_eeg_ref=False)
+    raw = read_raw_fif(fif_fname, preload=True)
+    raw.apply_proj()
+
     reref = set_bipolar_reference(raw, 'EEG 001', 'EEG 002', 'bipolar',
                                   {'kind': FIFF.FIFFV_EOG_CH,
                                    'extra': 'some extra value'})
@@ -181,6 +264,13 @@ def test_set_bipolar_reference():
     reref = set_bipolar_reference(raw, 'EEG 001', 'EEG 002')
     assert_true('EEG 001-EEG 002' in reref.ch_names)
 
+    # Minimalist call with twice the same anode
+    reref = set_bipolar_reference(raw,
+                                  ['EEG 001', 'EEG 001', 'EEG 002'],
+                                  ['EEG 002', 'EEG 003', 'EEG 003'])
+    assert_true('EEG 001-EEG 002' in reref.ch_names)
+    assert_true('EEG 001-EEG 003' in reref.ch_names)
+
     # Set multiple references at once
     reref = set_bipolar_reference(
         raw,
@@ -199,9 +289,10 @@ def test_set_bipolar_reference():
     # Test creating a bipolar reference that doesn't involve EEG channels:
     # it should not set the custom_ref_applied flag
     reref = set_bipolar_reference(raw, 'MEG 0111', 'MEG 0112',
-                                  ch_info={'kind': FIFF.FIFFV_MEG_CH})
+                                  ch_info={'kind': FIFF.FIFFV_MEG_CH},
+                                  verbose='error')
     assert_true(not reref.info['custom_ref_applied'])
-    assert_true('MEG 0111-MEG 0112' in reref.ch_names)
+    assert_true('MEG 0111-MEG 0112'[:15] in reref.ch_names)
 
     # Test a battery of invalid inputs
     assert_raises(ValueError, set_bipolar_reference, raw,
@@ -234,7 +325,7 @@ def _check_channel_names(inst, ref_names):
 @testing.requires_testing_data
 def test_add_reference():
     """Test adding a reference."""
-    raw = read_raw_fif(fif_fname, preload=True, add_eeg_ref=False)
+    raw = read_raw_fif(fif_fname, preload=True)
     picks_eeg = pick_types(raw.info, meg=False, eeg=True)
     # check if channel already exists
     assert_raises(ValueError, add_reference_channels,
@@ -261,7 +352,7 @@ def test_add_reference():
     assert_array_equal(ref_data, 0)
 
     # add reference channel to Raw when no digitization points exist
-    raw = read_raw_fif(fif_fname, add_eeg_ref=False).crop(0, 1).load_data()
+    raw = read_raw_fif(fif_fname).crop(0, 1).load_data()
     picks_eeg = pick_types(raw.info, meg=False, eeg=True)
     del raw.info['dig']
 
@@ -296,21 +387,18 @@ def test_add_reference():
     assert_array_equal(ref_data, 0)
 
     # add reference channel to epochs
-    raw = read_raw_fif(fif_fname, preload=True, add_eeg_ref=False)
+    raw = read_raw_fif(fif_fname, preload=True)
     events = read_events(eve_fname)
     picks_eeg = pick_types(raw.info, meg=False, eeg=True)
     epochs = Epochs(raw, events=events, event_id=1, tmin=-0.2, tmax=0.5,
-                    picks=picks_eeg, preload=True, add_eeg_ref=False)
+                    picks=picks_eeg, preload=True)
     # default: proj=True, after which adding a Ref channel is prohibited
     assert_raises(RuntimeError, add_reference_channels, epochs, 'Ref')
 
     # create epochs in delayed mode, allowing removal of CAR when re-reffing
     epochs = Epochs(raw, events=events, event_id=1, tmin=-0.2, tmax=0.5,
-                    picks=picks_eeg, preload=True, proj='delayed',
-                    add_eeg_ref=False)
+                    picks=picks_eeg, preload=True, proj='delayed')
     epochs_ref = add_reference_channels(epochs, 'Ref', copy=True)
-    # CAR after custom reference is an Error
-    assert_raises(RuntimeError, epochs_ref.set_eeg_reference)
 
     assert_equal(epochs_ref._data.shape[1], epochs._data.shape[1] + 1)
     _check_channel_names(epochs_ref, 'Ref')
@@ -322,13 +410,12 @@ def test_add_reference():
                        epochs_ref.get_data()[:, picks_eeg, :])
 
     # add two reference channels to epochs
-    raw = read_raw_fif(fif_fname, preload=True, add_eeg_ref=False)
+    raw = read_raw_fif(fif_fname, preload=True)
     events = read_events(eve_fname)
     picks_eeg = pick_types(raw.info, meg=False, eeg=True)
     # create epochs in delayed mode, allowing removal of CAR when re-reffing
     epochs = Epochs(raw, events=events, event_id=1, tmin=-0.2, tmax=0.5,
-                    picks=picks_eeg, preload=True, proj='delayed',
-                    add_eeg_ref=False)
+                    picks=picks_eeg, preload=True, proj='delayed')
     with warnings.catch_warnings(record=True):  # multiple set zero
         epochs_ref = add_reference_channels(epochs, ['M1', 'M2'], copy=True)
     assert_equal(epochs_ref._data.shape[1], epochs._data.shape[1] + 2)
@@ -344,13 +431,12 @@ def test_add_reference():
                        epochs_ref.get_data()[:, picks_eeg, :])
 
     # add reference channel to evoked
-    raw = read_raw_fif(fif_fname, preload=True, add_eeg_ref=False)
+    raw = read_raw_fif(fif_fname, preload=True)
     events = read_events(eve_fname)
     picks_eeg = pick_types(raw.info, meg=False, eeg=True)
     # create epochs in delayed mode, allowing removal of CAR when re-reffing
     epochs = Epochs(raw, events=events, event_id=1, tmin=-0.2, tmax=0.5,
-                    picks=picks_eeg, preload=True, proj='delayed',
-                    add_eeg_ref=False)
+                    picks=picks_eeg, preload=True, proj='delayed')
     evoked = epochs.average()
     evoked_ref = add_reference_channels(evoked, 'Ref', copy=True)
     assert_equal(evoked_ref.data.shape[0], evoked.data.shape[0] + 1)
@@ -363,13 +449,12 @@ def test_add_reference():
                        evoked_ref.data[picks_eeg, :])
 
     # add two reference channels to evoked
-    raw = read_raw_fif(fif_fname, preload=True, add_eeg_ref=False)
+    raw = read_raw_fif(fif_fname, preload=True)
     events = read_events(eve_fname)
     picks_eeg = pick_types(raw.info, meg=False, eeg=True)
     # create epochs in delayed mode, allowing removal of CAR when re-reffing
     epochs = Epochs(raw, events=events, event_id=1, tmin=-0.2, tmax=0.5,
-                    picks=picks_eeg, preload=True, proj='delayed',
-                    add_eeg_ref=False)
+                    picks=picks_eeg, preload=True, proj='delayed')
     evoked = epochs.average()
     with warnings.catch_warnings(record=True):  # multiple set zero
         evoked_ref = add_reference_channels(evoked, ['M1', 'M2'], copy=True)
@@ -384,8 +469,9 @@ def test_add_reference():
                        evoked_ref.data[picks_eeg, :])
 
     # Test invalid inputs
-    raw_np = read_raw_fif(fif_fname, preload=False, add_eeg_ref=False)
+    raw_np = read_raw_fif(fif_fname, preload=False)
     assert_raises(RuntimeError, add_reference_channels, raw_np, ['Ref'])
     assert_raises(ValueError, add_reference_channels, raw, 1)
 
+
 run_tests_if_main()
diff --git a/mne/io/tree.py b/mne/io/tree.py
index dccfd4e..e32e803 100644
--- a/mne/io/tree.py
+++ b/mne/io/tree.py
@@ -13,7 +13,7 @@ from ..utils import logger, verbose
 
 
 def dir_tree_find(tree, kind):
-    """Find nodes of the given kind from a directory tree structure
+    """Find nodes of the given kind from a directory tree structure.
 
     Parameters
     ----------
@@ -45,8 +45,7 @@ def dir_tree_find(tree, kind):
 
 @verbose
 def make_dir_tree(fid, directory, start=0, indent=0, verbose=None):
-    """Create the directory tree structure
-    """
+    """Create the directory tree structure."""
     FIFF_BLOCK_START = 104
     FIFF_BLOCK_END = 105
     FIFF_FILE_ID = 100
@@ -118,8 +117,7 @@ def make_dir_tree(fid, directory, start=0, indent=0, verbose=None):
 # Writing
 
 def copy_tree(fidin, in_id, nodes, fidout):
-    """Copies directory subtrees from fidin to fidout"""
-
+    """Copy directory subtrees from fidin to fidout."""
     if len(nodes) <= 0:
         return
 
diff --git a/mne/io/utils.py b/mne/io/utils.py
index cf8dcaa..deefe45 100644
--- a/mne/io/utils.py
+++ b/mne/io/utils.py
@@ -15,8 +15,7 @@ from .constants import FIFF
 
 
 def _find_channels(ch_names, ch_type='EOG'):
-    """Helper to find EOG channel.
-    """
+    """Find EOG channel."""
     substrings = (ch_type,)
     substrings = [s.upper() for s in substrings]
     if ch_type == 'EOG':
@@ -27,7 +26,7 @@ def _find_channels(ch_names, ch_type='EOG'):
 
 
 def _mult_cal_one(data_view, one, idx, cals, mult):
-    """Take a chunk of raw data, multiply by mult or cals, and store"""
+    """Take a chunk of raw data, multiply by mult or cals, and store."""
     one = np.asarray(one, dtype=data_view.dtype)
     assert data_view.shape[1] == one.shape[1]
     if mult is not None:
@@ -43,7 +42,7 @@ def _mult_cal_one(data_view, one, idx, cals, mult):
 
 
 def _blk_read_lims(start, stop, buf_len):
-    """Helper to deal with indexing in the middle of a data block
+    """Deal with indexing in the middle of a data block.
 
     Parameters
     ----------
@@ -108,7 +107,7 @@ def _blk_read_lims(start, stop, buf_len):
 
         >>> data[d_lims[ii, 0]:d_lims[ii, 1]] = this_data[r_lims[ii, 0]:r_lims[ii, 1]]  # doctest: +SKIP
 
-    """  # noqa
+    """  # noqa: E501
     # this is used to deal with indexing in the middle of a sampling period
     assert all(isinstance(x, int) for x in (start, stop, buf_len))
     block_start_idx = (start // buf_len)
@@ -144,7 +143,7 @@ def _blk_read_lims(start, stop, buf_len):
 def _read_segments_file(raw, data, idx, fi, start, stop, cals, mult,
                         dtype='<i2', n_channels=None, offset=0,
                         trigger_ch=None):
-    """Read a chunk of raw data"""
+    """Read a chunk of raw data."""
     if n_channels is None:
         n_channels = raw.info['nchan']
     n_bytes = np.dtype(dtype).itemsize
@@ -184,7 +183,7 @@ def read_str(fid, count=1):
 
 
 def _create_chs(ch_names, cals, ch_coil, ch_kind, eog, ecg, emg, misc):
-    """Helper for initializing info['chs'] for eeg channels."""
+    """Initialize info['chs'] for eeg channels."""
     chs = list()
     for idx, ch_name in enumerate(ch_names):
         if ch_name in eog or idx in eog:
@@ -213,7 +212,7 @@ def _create_chs(ch_names, cals, ch_coil, ch_kind, eog, ecg, emg, misc):
 
 
 def _synthesize_stim_channel(events, n_samples):
-    """Synthesize a stim channel from events read from an event file
+    """Synthesize a stim channel from events read from an event file.
 
     Parameters
     ----------
diff --git a/mne/io/write.py b/mne/io/write.py
index 997db75..a0056e9 100644
--- a/mne/io/write.py
+++ b/mne/io/write.py
@@ -19,6 +19,7 @@ from ..externals.six import string_types, b
 
 
 def _write(fid, data, kind, data_size, FIFFT_TYPE, dtype):
+    """Write data."""
     if isinstance(data, np.ndarray):
         data_size *= data.size
 
@@ -47,49 +48,49 @@ def _get_split_size(split_size):
 
 
 def write_int(fid, kind, data):
-    """Writes a 32-bit integer tag to a fif file"""
+    """Write a 32-bit integer tag to a fif file."""
     data_size = 4
     data = np.array(data, dtype='>i4').T
     _write(fid, data, kind, data_size, FIFF.FIFFT_INT, '>i4')
 
 
 def write_double(fid, kind, data):
-    """Writes a double-precision floating point tag to a fif file"""
+    """Write a double-precision floating point tag to a fif file."""
     data_size = 8
     data = np.array(data, dtype='>f8').T
     _write(fid, data, kind, data_size, FIFF.FIFFT_DOUBLE, '>f8')
 
 
 def write_float(fid, kind, data):
-    """Writes a single-precision floating point tag to a fif file"""
+    """Write a single-precision floating point tag to a fif file."""
     data_size = 4
     data = np.array(data, dtype='>f4').T
     _write(fid, data, kind, data_size, FIFF.FIFFT_FLOAT, '>f4')
 
 
 def write_dau_pack16(fid, kind, data):
-    """Writes a dau_pack16 tag to a fif file"""
+    """Write a dau_pack16 tag to a fif file."""
     data_size = 2
     data = np.array(data, dtype='>i2').T
     _write(fid, data, kind, data_size, FIFF.FIFFT_DAU_PACK16, '>i2')
 
 
 def write_complex64(fid, kind, data):
-    """Writes a 64 bit complex floating point tag to a fif file"""
+    """Write a 64 bit complex floating point tag to a fif file."""
     data_size = 8
     data = np.array(data, dtype='>c8').T
     _write(fid, data, kind, data_size, FIFF.FIFFT_COMPLEX_FLOAT, '>c8')
 
 
 def write_complex128(fid, kind, data):
-    """Writes a 128 bit complex floating point tag to a fif file"""
+    """Write a 128 bit complex floating point tag to a fif file."""
     data_size = 16
     data = np.array(data, dtype='>c16').T
     _write(fid, data, kind, data_size, FIFF.FIFFT_COMPLEX_FLOAT, '>c16')
 
 
 def write_julian(fid, kind, data):
-    """Writes a Julian-formatted date to a FIF file"""
+    """Write a Julian-formatted date to a FIF file."""
     assert len(data) == 3
     data_size = 4
     jd = np.sum(jcal2jd(*data))
@@ -98,7 +99,7 @@ def write_julian(fid, kind, data):
 
 
 def write_string(fid, kind, data):
-    """Writes a string tag"""
+    """Write a string tag."""
     str_data = data.encode('utf-8')  # Use unicode or bytes depending on Py2/3
     data_size = len(str_data)  # therefore compute size here
     my_dtype = '>a'  # py2/3 compatible on writing -- don't ask me why
@@ -107,7 +108,7 @@ def write_string(fid, kind, data):
 
 
 def write_name_list(fid, kind, data):
-    """Writes a colon-separated list of names
+    """Write a colon-separated list of names.
 
     Parameters
     ----------
@@ -117,7 +118,7 @@ def write_name_list(fid, kind, data):
 
 
 def write_float_matrix(fid, kind, mat):
-    """Writes a single-precision floating-point matrix tag"""
+    """Write a single-precision floating-point matrix tag."""
     FIFFT_MATRIX = 1 << 30
     FIFFT_MATRIX_FLOAT = FIFF.FIFFT_FLOAT | FIFFT_MATRIX
 
@@ -137,7 +138,7 @@ def write_float_matrix(fid, kind, mat):
 
 
 def write_double_matrix(fid, kind, mat):
-    """Writes a double-precision floating-point matrix tag"""
+    """Write a double-precision floating-point matrix tag."""
     FIFFT_MATRIX = 1 << 30
     FIFFT_MATRIX_DOUBLE = FIFF.FIFFT_DOUBLE | FIFFT_MATRIX
 
@@ -157,7 +158,7 @@ def write_double_matrix(fid, kind, mat):
 
 
 def write_int_matrix(fid, kind, mat):
-    """Writes integer 32 matrix tag"""
+    """Write integer 32 matrix tag."""
     FIFFT_MATRIX = 1 << 30
     FIFFT_MATRIX_INT = FIFF.FIFFT_INT | FIFFT_MATRIX
 
@@ -178,7 +179,7 @@ def write_int_matrix(fid, kind, mat):
 
 
 def get_machid():
-    """Get (mostly) unique machine ID
+    """Get (mostly) unique machine ID.
 
     Returns
     -------
@@ -197,7 +198,7 @@ def get_machid():
 
 
 def get_new_file_id():
-    """Helper to create a new file ID tag"""
+    """Create a new file ID tag."""
     secs, usecs = divmod(time.time(), 1.)
     secs, usecs = int(secs), int(usecs * 1e6)
     return {'machid': get_machid(), 'version': FIFF.FIFFC_VERSION,
@@ -205,7 +206,7 @@ def get_new_file_id():
 
 
 def write_id(fid, kind, id_=None):
-    """Writes fiff id"""
+    """Write fiff id."""
     id_ = _generate_meas_id() if id_ is None else id_
 
     data_size = 5 * 4                       # The id comprises five integers
@@ -222,17 +223,17 @@ def write_id(fid, kind, id_=None):
 
 
 def start_block(fid, kind):
-    """Writes a FIFF_BLOCK_START tag"""
+    """Write a FIFF_BLOCK_START tag."""
     write_int(fid, FIFF.FIFF_BLOCK_START, kind)
 
 
 def end_block(fid, kind):
-    """Writes a FIFF_BLOCK_END tag"""
+    """Write a FIFF_BLOCK_END tag."""
     write_int(fid, FIFF.FIFF_BLOCK_END, kind)
 
 
 def start_file(fname, id_=None):
-    """Opens a fif file for writing and writes the compulsory header tags
+    """Open a fif file for writing and writes the compulsory header tags.
 
     Parameters
     ----------
@@ -264,7 +265,7 @@ def start_file(fname, id_=None):
 
 
 def check_fiff_length(fid, close=True):
-    """Ensure our file hasn't grown too large to work properly"""
+    """Ensure our file hasn't grown too large to work properly."""
     if fid.tell() > 2147483648:  # 2 ** 31, FIFF uses signed 32-bit locations
         if close:
             fid.close()
@@ -273,7 +274,7 @@ def check_fiff_length(fid, close=True):
 
 
 def end_file(fid):
-    """Writes the closing tags to a fif file and closes the file"""
+    """Write the closing tags to a fif file and closes the file."""
     data_size = 0
     fid.write(np.array(FIFF.FIFF_NOP, dtype='>i4').tostring())
     fid.write(np.array(FIFF.FIFFT_VOID, dtype='>i4').tostring())
@@ -284,7 +285,7 @@ def end_file(fid):
 
 
 def write_coord_trans(fid, trans):
-    """Writes a coordinate transformation structure"""
+    """Write a coordinate transformation structure."""
     data_size = 4 * 2 * 12 + 4 * 2
     fid.write(np.array(FIFF.FIFF_COORD_TRANS, dtype='>i4').tostring())
     fid.write(np.array(FIFF.FIFFT_COORD_TRANS_STRUCT, dtype='>i4').tostring())
@@ -308,7 +309,7 @@ def write_coord_trans(fid, trans):
 
 
 def write_ch_info(fid, ch):
-    """Writes a channel information record to a fif file"""
+    """Write a channel information record to a fif file."""
     data_size = 4 * 13 + 4 * 7 + 16
 
     fid.write(np.array(FIFF.FIFF_CH_INFO, dtype='>i4').tostring())
@@ -330,33 +331,34 @@ def write_ch_info(fid, ch):
     fid.write(np.array(ch['unit_mul'], dtype='>i4').tostring())
 
     #   Finally channel name
-    if len(ch['ch_name']):
-        ch_name = ch['ch_name'][:15]
-    else:
-        ch_name = ch['ch_name']
-
+    ch_name = ch['ch_name'][:15]
     fid.write(np.array(ch_name, dtype='>c').tostring())
-    if len(ch_name) < 16:
-        fid.write(b('\0') * (16 - len(ch_name)))
-
-
-def write_dig_point(fid, dig):
-    """Writes a digitizer data point into a fif file"""
-    data_size = 5 * 4
-
-    fid.write(np.array(FIFF.FIFF_DIG_POINT, dtype='>i4').tostring())
-    fid.write(np.array(FIFF.FIFFT_DIG_POINT_STRUCT, dtype='>i4').tostring())
-    fid.write(np.array(data_size, dtype='>i4').tostring())
-    fid.write(np.array(FIFF.FIFFV_NEXT_SEQ, dtype='>i4').tostring())
-
-    #   Start writing fiffDigPointRec
-    fid.write(np.array(dig['kind'], dtype='>i4').tostring())
-    fid.write(np.array(dig['ident'], dtype='>i4').tostring())
-    fid.write(np.array(dig['r'][:3], dtype='>f4').tostring())
+    fid.write(b('\0') * (16 - len(ch_name)))
+
+
+def write_dig_points(fid, dig, block=False, coord_frame=None):
+    """Write a set of digitizer data points into a fif file."""
+    if dig is not None:
+        data_size = 5 * 4
+        if block:
+            start_block(fid, FIFF.FIFFB_ISOTRAK)
+        if coord_frame is not None:
+            write_int(fid, FIFF.FIFF_MNE_COORD_FRAME, coord_frame)
+        for d in dig:
+            fid.write(np.array(FIFF.FIFF_DIG_POINT, '>i4').tostring())
+            fid.write(np.array(FIFF.FIFFT_DIG_POINT_STRUCT, '>i4').tostring())
+            fid.write(np.array(data_size, dtype='>i4').tostring())
+            fid.write(np.array(FIFF.FIFFV_NEXT_SEQ, '>i4').tostring())
+            #   Start writing fiffDigPointRec
+            fid.write(np.array(d['kind'], '>i4').tostring())
+            fid.write(np.array(d['ident'], '>i4').tostring())
+            fid.write(np.array(d['r'][:3], '>f4').tostring())
+        if block:
+            end_block(fid, FIFF.FIFFB_ISOTRAK)
 
 
 def write_float_sparse_rcs(fid, kind, mat):
-    """Writes a single-precision floating-point matrix tag"""
+    """Write a single-precision floating-point matrix tag."""
     FIFFT_MATRIX = 16416 << 16
     FIFFT_MATRIX_FLOAT_RCS = FIFF.FIFFT_FLOAT | FIFFT_MATRIX
 
@@ -379,7 +381,7 @@ def write_float_sparse_rcs(fid, kind, mat):
 
 
 def _generate_meas_id():
-    """Helper to generate a new meas_id dict"""
+    """Generate a new meas_id dict."""
     id_ = dict()
     id_['version'] = FIFF.FIFFC_VERSION
     id_['machid'] = get_machid()
@@ -388,7 +390,7 @@ def _generate_meas_id():
 
 
 def _date_now():
-    """Helper to get date in secs, usecs"""
+    """Get date in secs, usecs."""
     now = time.time()
     # Get date in secs/usecs (as in `fill_measurement_info` in
     # mne/forward/forward.py)
diff --git a/mne/label.py b/mne/label.py
index 1b51761..f94447e 100644
--- a/mne/label.py
+++ b/mne/label.py
@@ -14,8 +14,7 @@ import re
 import numpy as np
 from scipy import linalg, sparse
 
-from .utils import (get_subjects_dir, _check_subject, logger, verbose, warn,
-                    _check_copy_dep)
+from .utils import get_subjects_dir, _check_subject, logger, verbose, warn
 from .source_estimate import (morph_data, SourceEstimate, _center_of_mass,
                               spatial_src_connectivity)
 from .source_space import add_source_space_distances
@@ -28,7 +27,7 @@ from .externals.six.moves import zip, xrange
 
 
 def _blend_colors(color_1, color_2):
-    """Blend two colors in HSV space
+    """Blend two colors in HSV space.
 
     Parameters
     ----------
@@ -70,7 +69,7 @@ def _blend_colors(color_1, color_2):
 
 
 def _split_colors(color, n):
-    """Create n colors in HSV space that occupy a gradient in value
+    """Create n colors in HSV space that occupy a gradient in value.
 
     Parameters
     ----------
@@ -102,7 +101,7 @@ def _split_colors(color, n):
 
 
 def _n_colors(n, bytes_=False, cmap='hsv'):
-    """Produce a list of n unique RGBA color tuples based on a colormap
+    """Produce a list of n unique RGBA color tuples based on a colormap.
 
     Parameters
     ----------
@@ -139,7 +138,7 @@ def _n_colors(n, bytes_=False, cmap='hsv'):
 
 
 class Label(object):
-    """A FreeSurfer/MNE label with vertices restricted to one hemisphere
+    """A FreeSurfer/MNE label with vertices restricted to one hemisphere.
 
     Labels can be combined with the ``+`` operator:
 
@@ -170,7 +169,8 @@ class Label(object):
     color : None | matplotlib color
         Default label color and alpha (e.g., ``(1., 0., 0., 1.)`` for red).
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Attributes
     ----------
@@ -195,14 +195,15 @@ class Label(object):
     vertices : array, len = n_pos
         Vertex indices (0 based)
     """
+
     @verbose
     def __init__(self, vertices, pos=None, values=None, hemi=None, comment="",
                  name=None, filename=None, subject=None, color=None,
-                 verbose=None):
+                 verbose=None):  # noqa: D102
         # check parameters
         if not isinstance(hemi, string_types):
             raise ValueError('hemi must be a string, not %s' % type(hemi))
-        vertices = np.asarray(vertices)
+        vertices = np.asarray(vertices, int)
         if np.any(np.diff(vertices.astype(int)) <= 0):
             raise ValueError('Vertices must be ordered in increasing order.')
 
@@ -239,7 +240,7 @@ class Label(object):
         self.name = name
         self.filename = filename
 
-    def __setstate__(self, state):
+    def __setstate__(self, state):  # noqa: D105
         self.vertices = state['vertices']
         self.pos = state['pos']
         self.values = state['values']
@@ -251,7 +252,7 @@ class Label(object):
         self.name = state['name']
         self.filename = state['filename']
 
-    def __getstate__(self):
+    def __getstate__(self):  # noqa: D105
         out = dict(vertices=self.vertices,
                    pos=self.pos,
                    values=self.values,
@@ -264,16 +265,18 @@ class Label(object):
                    filename=self.filename)
         return out
 
-    def __repr__(self):
+    def __repr__(self):  # noqa: D105
         name = 'unknown, ' if self.subject is None else self.subject + ', '
         name += repr(self.name) if self.name is not None else "unnamed"
         n_vert = len(self)
         return "<Label  |  %s, %s : %i vertices>" % (name, self.hemi, n_vert)
 
     def __len__(self):
+        """Return the number of vertices."""
         return len(self.vertices)
 
     def __add__(self, other):
+        """Add BiHemiLabels."""
         if isinstance(other, BiHemiLabel):
             return other + self
         elif isinstance(other, Label):
@@ -343,6 +346,7 @@ class Label(object):
         return label
 
     def __sub__(self, other):
+        """Subtract BiHemiLabels."""
         if isinstance(other, BiHemiLabel):
             if self.hemi == 'lh':
                 return self - other.lh
@@ -370,7 +374,7 @@ class Label(object):
                      self.color, self.verbose)
 
     def save(self, filename):
-        """Write to disk as FreeSurfer \*.label file
+        r"""Write to disk as FreeSurfer \*.label file.
 
         Parameters
         ----------
@@ -395,7 +399,7 @@ class Label(object):
         return cp.deepcopy(self)
 
     def fill(self, src, name=None):
-        """Fill the surface between sources for a label defined in source space
+        """Fill the surface between sources for a source space label.
 
         Parameters
         ----------
@@ -416,6 +420,8 @@ class Label(object):
             including intermediate surface vertices.
         """
         # find source space patch info
+        if len(self.vertices) == 0:
+            return self.copy()
         if self.hemi == 'lh':
             hemi_src = src[0]
         elif self.hemi == 'rh':
@@ -452,8 +458,8 @@ class Label(object):
 
     @verbose
     def smooth(self, subject=None, smooth=2, grade=None,
-               subjects_dir=None, n_jobs=1, copy=None, verbose=None):
-        """Smooth the label
+               subjects_dir=None, n_jobs=1, verbose=None):
+        """Smooth the label.
 
         Useful for filling in labels made in a
         decimated source space for display.
@@ -483,13 +489,10 @@ class Label(object):
             Path to SUBJECTS_DIR if it is not set in the environment.
         n_jobs : int
             Number of jobs to run in parallel
-        copy : bool
-            This parameter has been deprecated and will be removed in 0.14.
-            Use inst.copy() instead.
-            Whether to return a new instance or modify in place.
         verbose : bool, str, int, or None
-            If not None, override default verbose level (see mne.verbose).
-            Defaults to self.verbose.
+            If not None, override default verbose level (see
+            :func:`mne.verbose` and :ref:`Logging documentation <tut_logging>`
+            for more). Defaults to self.verbose.
 
         Returns
         -------
@@ -504,12 +507,12 @@ class Label(object):
         """
         subject = _check_subject(self.subject, subject)
         return self.morph(subject, subject, smooth, grade, subjects_dir,
-                          n_jobs, copy=copy)
+                          n_jobs)
 
     @verbose
     def morph(self, subject_from=None, subject_to=None, smooth=5, grade=None,
-              subjects_dir=None, n_jobs=1, copy=None, verbose=None):
-        """Morph the label
+              subjects_dir=None, n_jobs=1, verbose=None):
+        """Morph the label.
 
         Useful for transforming a label from one subject to another.
 
@@ -539,12 +542,10 @@ class Label(object):
             Path to SUBJECTS_DIR if it is not set in the environment.
         n_jobs : int
             Number of jobs to run in parallel.
-        copy : bool
-            This parameter has been deprecated and will be removed in 0.14.
-            Use inst.copy() instead.
-            Whether to return a new instance or modify in place.
         verbose : bool, str, int, or None
-            If not None, override default verbose level (see mne.verbose).
+            If not None, override default verbose level (see
+            :func:`mne.verbose` and :ref:`Logging documentation <tut_logging>`
+            for more).
 
         Returns
         -------
@@ -582,19 +583,18 @@ class Label(object):
                          smooth=smooth, subjects_dir=subjects_dir,
                          warn=False, n_jobs=n_jobs)
         inds = np.nonzero(stc.data)[0]
-        label = _check_copy_dep(self, copy)
-        label.values = stc.data[inds, :].ravel()
-        label.pos = np.zeros((len(inds), 3))
-        if label.hemi == 'lh':
-            label.vertices = stc.vertices[0][inds]
+        self.values = stc.data[inds, :].ravel()
+        self.pos = np.zeros((len(inds), 3))
+        if self.hemi == 'lh':
+            self.vertices = stc.vertices[0][inds]
         else:
-            label.vertices = stc.vertices[1][inds]
-        label.subject = subject_to
-        return label
+            self.vertices = stc.vertices[1][inds]
+        self.subject = subject_to
+        return self
 
     def split(self, parts=2, subject=None, subjects_dir=None,
               freesurfer=False):
-        """Split the Label into two or more parts
+        """Split the Label into two or more parts.
 
         Parameters
         ----------
@@ -641,7 +641,7 @@ class Label(object):
                              "('contiguous'). Got %s)" % type(parts))
 
     def get_vertices_used(self, vertices=None):
-        """Get the source space's vertices inside the label
+        """Get the source space's vertices inside the label.
 
         Parameters
         ----------
@@ -661,7 +661,7 @@ class Label(object):
         return label_verts
 
     def get_tris(self, tris, vertices=None):
-        """Get the source space's triangles inside the label
+        """Get the source space's triangles inside the label.
 
         Parameters
         ----------
@@ -699,7 +699,7 @@ class Label(object):
 
     def center_of_mass(self, subject=None, restrict_vertices=False,
                        subjects_dir=None, surf='sphere'):
-        """Compute the center of mass of the label
+        """Compute the center of mass of the label.
 
         This function computes the spatial center of mass on the surface
         as in [1]_.
@@ -749,13 +749,17 @@ class Label(object):
         subject = _check_subject(self.subject, subject)
         if np.any(self.values < 0):
             raise ValueError('Cannot compute COM with negative values')
+        if np.all(self.values == 0):
+            raise ValueError('Cannot compute COM with all values == 0. For '
+                             'structural labels, consider setting to ones via '
+                             'label.values[:] = 1.')
         vertex = _center_of_mass(self.vertices, self.values, self.hemi, surf,
                                  subject, subjects_dir, restrict_vertices)
         return vertex
 
 
 class BiHemiLabel(object):
-    """A freesurfer/MNE label with vertices in both hemispheres
+    """A freesurfer/MNE label with vertices in both hemispheres.
 
     Parameters
     ----------
@@ -783,7 +787,7 @@ class BiHemiLabel(object):
 
     """
 
-    def __init__(self, lh, rh, name=None, color=None):
+    def __init__(self, lh, rh, name=None, color=None):  # noqa: D102
         if lh.subject != rh.subject:
             raise ValueError('lh.subject (%s) and rh.subject (%s) must '
                              'agree' % (lh.subject, rh.subject))
@@ -794,16 +798,18 @@ class BiHemiLabel(object):
         self.color = color
         self.hemi = 'both'
 
-    def __repr__(self):
+    def __repr__(self):  # noqa: D105
         temp = "<BiHemiLabel  |  %s, lh : %i vertices,  rh : %i vertices>"
         name = 'unknown, ' if self.subject is None else self.subject + ', '
         name += repr(self.name) if self.name is not None else "unnamed"
         return temp % (name, len(self.lh), len(self.rh))
 
     def __len__(self):
+        """Return the number of vertices."""
         return len(self.lh) + len(self.rh)
 
     def __add__(self, other):
+        """Add labels."""
         if isinstance(other, Label):
             if other.hemi == 'lh':
                 lh = self.lh + other
@@ -822,6 +828,7 @@ class BiHemiLabel(object):
         return BiHemiLabel(lh, rh, name, color)
 
     def __sub__(self, other):
+        """Subtract labels."""
         if isinstance(other, Label):
             if other.hemi == 'lh':
                 lh = self.lh - other
@@ -845,7 +852,7 @@ class BiHemiLabel(object):
 
 
 def read_label(filename, subject=None, color=None):
-    """Read FreeSurfer Label file
+    """Read FreeSurfer Label file.
 
     Parameters
     ----------
@@ -923,7 +930,7 @@ def read_label(filename, subject=None, color=None):
 
 @verbose
 def write_label(filename, label, verbose=None):
-    """Write a FreeSurfer label
+    """Write a FreeSurfer label.
 
     Parameters
     ----------
@@ -932,7 +939,8 @@ def write_label(filename, label, verbose=None):
     label : Label
         The label object to save.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Notes
     -----
@@ -967,8 +975,7 @@ def write_label(filename, label, verbose=None):
 
 
 def _prep_label_split(label, subject=None, subjects_dir=None):
-    """Helper to get label and subject information prior to label spliting"""
-
+    """Get label and subject information prior to label spliting."""
     # If necessary, find the label
     if isinstance(label, BiHemiLabel):
         raise TypeError("Can only split labels restricted to one hemisphere.")
@@ -992,7 +999,7 @@ def _prep_label_split(label, subject=None, subjects_dir=None):
 
 
 def _split_label_contig(label_to_split, subject=None, subjects_dir=None):
-    """Split label into contiguous regions (i.e., connected components)
+    """Split label into contiguous regions (i.e., connected components).
 
     Parameters
     ----------
@@ -1056,7 +1063,7 @@ def _split_label_contig(label_to_split, subject=None, subjects_dir=None):
     labels = []
     for div, name, color in zip(label_divs, names, colors):
         # Get indices of dipoles within this division of the label
-        verts = np.array(sorted(list(div)))
+        verts = np.array(sorted(list(div)), int)
         vert_indices = np.in1d(verts_arr, verts, assume_unique=True)
 
         # Set label attributes
@@ -1073,7 +1080,7 @@ def _split_label_contig(label_to_split, subject=None, subjects_dir=None):
 
 def split_label(label, parts=2, subject=None, subjects_dir=None,
                 freesurfer=False):
-    """Split a Label into two or more parts
+    """Split a Label into two or more parts.
 
     Parameters
     ----------
@@ -1107,7 +1114,6 @@ def split_label(label, parts=2, subject=None, subjects_dir=None,
     projecting all label vertex coordinates onto this axis and dividing them at
     regular spatial intervals.
     """
-
     label, subject, subjects_dir = _prep_label_split(label, subject,
                                                      subjects_dir)
 
@@ -1204,13 +1210,13 @@ def split_label(label, parts=2, subject=None, subjects_dir=None,
 
 
 def label_sign_flip(label, src):
-    """Compute sign for label averaging
+    """Compute sign for label averaging.
 
     Parameters
     ----------
-    label : Label
+    label : Label | BiHemiLabel
         A label.
-    src : list of dict
+    src : SourceSpaces
         The source space over which the label is defined.
 
     Returns
@@ -1225,23 +1231,31 @@ def label_sign_flip(label, src):
     rh_vertno = src[1]['vertno']
 
     # get source orientations
-    if label.hemi == 'lh':
-        vertno_sel = np.intersect1d(lh_vertno, label.vertices)
-        if len(vertno_sel) == 0:
-            return np.array([], int)
-        ori = src[0]['nn'][vertno_sel]
-    elif label.hemi == 'rh':
-        vertno_sel = np.intersect1d(rh_vertno, label.vertices)
-        if len(vertno_sel) == 0:
-            return np.array([], int)
-        ori = src[1]['nn'][vertno_sel]
-    else:
-        raise Exception("Unknown hemisphere type")
+    ori = list()
+    if label.hemi in ('lh', 'both'):
+        vertices = label.vertices if label.hemi == 'lh' else label.lh.vertices
+        vertno_sel = np.intersect1d(lh_vertno, vertices)
+        ori.append(src[0]['nn'][vertno_sel])
+    if label.hemi in ('rh', 'both'):
+        vertices = label.vertices if label.hemi == 'rh' else label.rh.vertices
+        vertno_sel = np.intersect1d(rh_vertno, vertices)
+        ori.append(src[1]['nn'][vertno_sel])
+    if len(ori) == 0:
+        raise Exception('Unknown hemisphere type "%s"' % (label.hemi,))
+    ori = np.concatenate(ori, axis=0)
+    if len(ori) == 0:
+        return np.array([], int)
 
     _, _, Vh = linalg.svd(ori, full_matrices=False)
 
+    # The sign of Vh is ambiguous, so we should align to the max-positive
+    # (outward) direction
+    dots = np.dot(ori, Vh[0])
+    if np.mean(dots) < 0:
+        dots *= -1
+
     # Comparing to the direction of the first right singular vector
-    flip = np.sign(np.dot(ori, Vh[0]))
+    flip = np.sign(dots)
     return flip
 
 
@@ -1380,7 +1394,7 @@ def stc_to_label(stc, src=None, smooth=True, connected=False,
 
 
 def _verts_within_dist(graph, sources, max_dist):
-    """Find all vertices wihin a maximum geodesic distance from source
+    """Find all vertices wihin a maximum geodesic distance from source.
 
     Parameters
     ----------
@@ -1424,15 +1438,14 @@ def _verts_within_dist(graph, sources, max_dist):
                         verts_added.append(j)
         verts_added_last = verts_added
 
-    verts = np.sort(np.array(list(dist_map.keys()), dtype=np.int))
-    dist = np.array([dist_map[v] for v in verts])
+    verts = np.sort(np.array(list(dist_map.keys()), int))
+    dist = np.array([dist_map[v] for v in verts], int)
 
     return verts, dist
 
 
 def _grow_labels(seeds, extents, hemis, names, dist, vert, subject):
-    """Helper for parallelization of grow_labels
-    """
+    """Parallelize grow_labels."""
     labels = []
     for seed, extent, hemi, name in zip(seeds, extents, hemis, names):
         label_verts, label_dist = _verts_within_dist(dist[hemi], seed, extent)
@@ -1457,7 +1470,7 @@ def _grow_labels(seeds, extents, hemis, names, dist, vert, subject):
 
 def grow_labels(subject, seeds, extents, hemis, subjects_dir=None, n_jobs=1,
                 overlap=True, names=None, surface='white'):
-    """Generate circular labels in source space with region growing
+    """Generate circular labels in source space with region growing.
 
     This function generates a number of labels in source space by growing
     regions starting from the vertices defined in "seeds". For each seed, a
@@ -1578,8 +1591,7 @@ def grow_labels(subject, seeds, extents, hemis, subjects_dir=None, n_jobs=1,
 
 def _grow_nonoverlapping_labels(subject, seeds_, extents_, hemis, vertices_,
                                 graphs, names_):
-    """Grow labels while ensuring that they don't overlap
-    """
+    """Grow labels while ensuring that they don't overlap."""
     labels = []
     for hemi in set(hemis):
         hemi_index = (hemis == hemi)
@@ -1725,7 +1737,7 @@ def _read_annot(fname):
 
 
 def _get_annot_fname(annot_fname, subject, hemi, parc, subjects_dir):
-    """Helper function to get the .annot filenames and hemispheres"""
+    """Get the .annot filenames and hemispheres."""
     if annot_fname is not None:
         # we use use the .annot file specified by the user
         hemis = [op.basename(annot_fname)[:2]]
@@ -1753,7 +1765,7 @@ def _get_annot_fname(annot_fname, subject, hemi, parc, subjects_dir):
 def read_labels_from_annot(subject, parc='aparc', hemi='both',
                            surf_name='white', annot_fname=None, regexp=None,
                            subjects_dir=None, verbose=None):
-    """Read labels from a FreeSurfer annotation file
+    """Read labels from a FreeSurfer annotation file.
 
     Note: Only cortical labels will be returned.
 
@@ -1778,14 +1790,15 @@ def read_labels_from_annot(subject, parc='aparc', hemi='both',
     subjects_dir : string, or None
         Path to SUBJECTS_DIR if it is not set in the environment.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
     labels : list of Label
         The labels, sorted by label name (ascending).
     """
-    logger.info('Reading labels from parcellation..')
+    logger.info('Reading labels from parcellation...')
 
     subjects_dir = get_subjects_dir(subjects_dir)
 
@@ -1822,7 +1835,7 @@ def read_labels_from_annot(subject, parc='aparc', hemi='both',
             if (regexp is not None) and not r_.match(name):
                 continue
             pos = vert_pos[vertices, :]
-            values = np.zeros(len(vertices))
+            values = np.ones(len(vertices))
             label_rgba = tuple(label_rgba / 255.)
             label = Label(vertices, pos, values, hemi, name=name,
                           subject=subject, color=label_rgba)
@@ -1840,7 +1853,6 @@ def read_labels_from_annot(subject, parc='aparc', hemi='both',
             msg += ' Maybe the regular expression %r did not match?' % regexp
         raise RuntimeError(msg)
 
-    logger.info('[done]')
     return labels
 
 
@@ -1859,7 +1871,6 @@ def _write_annot(fname, annot, ctab, names):
     names : list of str
         List of region names to be stored in the annot file
     """
-
     with open(fname, 'wb') as fid:
         n_verts = len(annot)
         np.array(n_verts, dtype='>i4').tofile(fid)
@@ -1899,7 +1910,7 @@ def _write_annot(fname, annot, ctab, names):
 def write_labels_to_annot(labels, subject=None, parc=None, overwrite=False,
                           subjects_dir=None, annot_fname=None,
                           colormap='hsv', hemi='both', verbose=None):
-    """Create a FreeSurfer annotation from a list of labels
+    r"""Create a FreeSurfer annotation from a list of labels.
 
     Parameters
     ----------
@@ -1923,14 +1934,15 @@ def write_labels_to_annot(labels, subject=None, parc=None, overwrite=False,
         The hemisphere(s) for which to write \*.annot files (only applies if
         annot_fname is not specified; default is 'both').
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Notes
     -----
     Vertices that are not covered by any of the labels are assigned to a label
     named "unknown".
     """
-    logger.info('Writing labels to parcellation..')
+    logger.info('Writing labels to parcellation...')
 
     subjects_dir = get_subjects_dir(subjects_dir)
 
@@ -2114,5 +2126,3 @@ def write_labels_to_annot(labels, subject=None, parc=None, overwrite=False,
     for fname, annot, ctab, hemi_names in to_save:
         logger.info('   writing %d labels to %s' % (len(hemi_names), fname))
         _write_annot(fname, annot, ctab, hemi_names)
-
-    logger.info('[done]')
diff --git a/mne/minimum_norm/__init__.py b/mne/minimum_norm/__init__.py
index b48f805..082fa36 100644
--- a/mne/minimum_norm/__init__.py
+++ b/mne/minimum_norm/__init__.py
@@ -1,4 +1,4 @@
-"""Linear inverse solvers based on L2 Minimum Norm Estimates (MNE)"""
+"""Linear inverse solvers based on L2 Minimum Norm Estimates (MNE)."""
 
 from .inverse import (InverseOperator, read_inverse_operator, apply_inverse,
                       apply_inverse_raw, make_inverse_operator,
diff --git a/mne/minimum_norm/inverse.py b/mne/minimum_norm/inverse.py
index 2de13b5..f0aa95d 100644
--- a/mne/minimum_norm/inverse.py
+++ b/mne/minimum_norm/inverse.py
@@ -14,15 +14,15 @@ from ..io.open import fiff_open
 from ..io.tag import find_tag
 from ..io.matrix import (_read_named_matrix, _transpose_named_matrix,
                          write_named_matrix)
-from ..io.proj import _read_proj, make_projector, _write_proj
-from ..io.proj import _needs_eeg_average_ref_proj
+from ..io.proj import (_read_proj, make_projector, _write_proj,
+                       _needs_eeg_average_ref_proj)
 from ..io.tree import dir_tree_find
 from ..io.write import (write_int, write_float_matrix, start_file,
                         start_block, end_block, end_file, write_float,
                         write_coord_trans, write_string)
 
 from ..io.pick import channel_type, pick_info, pick_types
-from ..cov import prepare_noise_cov, _read_cov, _write_cov, Covariance
+from ..cov import _get_whitener, _read_cov, _write_cov, Covariance
 from ..forward import (compute_depth_prior, _read_forward_meas_info,
                        write_forward_meas_info, is_fixed_orient,
                        compute_orient_prior, convert_forward_solution)
@@ -32,20 +32,17 @@ from ..source_space import (_read_source_spaces_from_tree,
 from ..transforms import _ensure_trans, transform_surface_to
 from ..source_estimate import _make_stc
 from ..utils import check_fname, logger, verbose, warn
-from functools import reduce
 
 
 class InverseOperator(dict):
-    """InverseOperator class to represent info from inverse operator
-    """
+    """InverseOperator class to represent info from inverse operator."""
 
     def copy(self):
-        """Return a copy of the InverseOperator"""
+        """Return a copy of the InverseOperator."""
         return InverseOperator(deepcopy(self))
 
-    def __repr__(self):
-        """Summarize inverse info instead of printing all"""
-
+    def __repr__(self):  # noqa: D105
+        """Summarize inverse info instead of printing all."""
         entr = '<InverseOperator'
 
         nchan = len(pick_types(self['info'], meg=True, eeg=False))
@@ -53,18 +50,8 @@ class InverseOperator(dict):
         nchan = len(pick_types(self['info'], meg=False, eeg=True))
         entr += ' | ' + 'EEG channels: %d' % nchan
 
-        # XXX TODO: This and the __repr__ in SourceSpaces should call a
-        # function _get_name_str() in source_space.py
-        if self['src'][0]['type'] == 'surf':
-            entr += (' | Source space: Surface with %d vertices'
-                     % self['nsource'])
-        elif self['src'][0]['type'] == 'vol':
-            entr += (' | Source space: Volume with %d grid points'
-                     % self['nsource'])
-        elif self['src'][0]['type'] == 'discrete':
-            entr += (' | Source space: Discrete with %d dipoles'
-                     % self['nsource'])
-
+        entr += (' | Source space: %s with %d sources'
+                 % (self['src'].kind,  self['nsource']))
         source_ori = {FIFF.FIFFV_MNE_UNKNOWN_ORI: 'Unknown',
                       FIFF.FIFFV_MNE_FIXED_ORI: 'Fixed',
                       FIFF.FIFFV_MNE_FREE_ORI: 'Free'}
@@ -75,14 +62,15 @@ class InverseOperator(dict):
 
 
 def _pick_channels_inverse_operator(ch_names, inv):
-    """Gives the indices of the data channel to be used knowing
-    an inverse operator
+    """Return data channel indices to be used knowing an inverse operator.
+
+    Unlike ``pick_channels``, this respects the order of ch_names.
     """
-    sel = []
+    sel = list()
     for name in inv['noise_cov'].ch_names:
-        if name in ch_names:
+        try:
             sel.append(ch_names.index(name))
-        else:
+        except ValueError:
             raise ValueError('The inverse operator was computed with '
                              'channel %s which is not present in '
                              'the data. You should compute a new inverse '
@@ -93,14 +81,15 @@ def _pick_channels_inverse_operator(ch_names, inv):
 
 @verbose
 def read_inverse_operator(fname, verbose=None):
-    """Read the inverse operator decomposition from a FIF file
+    """Read the inverse operator decomposition from a FIF file.
 
     Parameters
     ----------
     fname : string
         The name of the FIF file, which ends with -inv.fif or -inv.fif.gz.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -314,7 +303,7 @@ def read_inverse_operator(fname, verbose=None):
 
 @verbose
 def write_inverse_operator(fname, inv, verbose=None):
-    """Write an inverse operator to a FIF file
+    """Write an inverse operator to a FIF file.
 
     Parameters
     ----------
@@ -323,7 +312,8 @@ def write_inverse_operator(fname, inv, verbose=None):
     inv : dict
         The inverse operator.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     See Also
     --------
@@ -436,7 +426,7 @@ def write_inverse_operator(fname, inv, verbose=None):
 
 
 def combine_xyz(vec, square=False):
-    """Compute the three Cartesian components of a vector or matrix together
+    """Compute the three Cartesian components of a vector or matrix together.
 
     Parameters
     ----------
@@ -466,8 +456,7 @@ def combine_xyz(vec, square=False):
 
 
 def _check_ch_names(inv, info):
-    """Check that channels in inverse operator are measurements"""
-
+    """Check that channels in inverse operator are measurements."""
     inv_ch_names = inv['eigen_fields']['col_names']
 
     if inv['noise_cov'].ch_names != inv_ch_names:
@@ -475,10 +464,7 @@ def _check_ch_names(inv, info):
                          'match noise covariance channels.')
     data_ch_names = info['ch_names']
 
-    missing_ch_names = list()
-    for ch_name in inv_ch_names:
-        if ch_name not in data_ch_names:
-            missing_ch_names.append(ch_name)
+    missing_ch_names = sorted(set(inv_ch_names) - set(data_ch_names))
     n_missing = len(missing_ch_names)
     if n_missing > 0:
         raise ValueError('%d channels in inverse operator ' % n_missing +
@@ -487,7 +473,7 @@ def _check_ch_names(inv, info):
 
 @verbose
 def prepare_inverse_operator(orig, nave, lambda2, method, verbose=None):
-    """Prepare an inverse operator for actually computing the inverse
+    """Prepare an inverse operator for actually computing the inverse.
 
     Parameters
     ----------
@@ -500,7 +486,8 @@ def prepare_inverse_operator(orig, nave, lambda2, method, verbose=None):
     method : "MNE" | "dSPM" | "sLORETA"
         Use mininum norm, dSPM or sLORETA.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -617,7 +604,6 @@ def prepare_inverse_operator(orig, nave, lambda2, method, verbose=None):
             #   per source location
             #
             noise_norm = combine_xyz(noise_norm[:, None]).ravel()
-
         inv['noisenorm'] = 1.0 / np.abs(noise_norm)
         logger.info('[done]')
     else:
@@ -628,13 +614,42 @@ def prepare_inverse_operator(orig, nave, lambda2, method, verbose=None):
 
 @verbose
 def _assemble_kernel(inv, label, method, pick_ori, verbose=None):
-    #
-    #   Simple matrix multiplication followed by combination of the
-    #   current components
-    #
-    #   This does all the data transformations to compute the weights for the
-    #   eigenleads
-    #
+    """Assemble the kernel.
+
+    Simple matrix multiplication followed by combination of the current
+    components. This does all the data transformations to compute the weights
+    for the eigenleads.
+
+    Parameters
+    ----------
+    inv : instance of InverseOperator
+        The inverse operator to use. This object contains the matrices that
+        will be multiplied to assemble the kernel.
+    label : Label | None
+        Restricts the source estimates to a given label. If None,
+        source estimates will be computed for the entire source space.
+    method : "MNE" | "dSPM" | "sLORETA"
+        Use mininum norm, dSPM or sLORETA.
+    pick_ori : None | "normal" | "vector"
+        Which orientation to pick (only matters in the case of 'normal').
+
+    Returns
+    -------
+    K : array, shape (n_vertices, n_channels) | (3 * n_vertices, n_channels)
+        The kernel matrix. Multiply this with the data to obtain the source
+        estimate.
+    noise_norm : array, shape (n_vertices, n_samples) | (3 * n_vertices, n_samples)
+        Normalization to apply to the source estimate in order to obtain dSPM
+        or LORETA solutions.
+    vertices : list of length 2
+        Vertex numbers for lh and rh hemispheres that correspond to the
+        vertices in the source estimate. When the label parameter has been
+        set, these correspond to the vertices in the label. Otherwise, all
+        vertex numbers are returned.
+    source_nn : array, shape (3 * n_vertices, 3)
+        The direction in carthesian coordicates of the direction of the source
+        dipoles.
+    """  # noqa: E501
     eigen_leads = inv['eigen_leads']['data']
     source_cov = inv['source_cov']['data'][:, None]
     if method != "MNE":
@@ -642,6 +657,7 @@ def _assemble_kernel(inv, label, method, pick_ori, verbose=None):
 
     src = inv['src']
     vertno = _get_vertno(src)
+    source_nn = inv['source_nn']
 
     if label is not None:
         vertno, src_sel = label_src_vertno_sel(label, inv['src'])
@@ -656,6 +672,7 @@ def _assemble_kernel(inv, label, method, pick_ori, verbose=None):
 
         eigen_leads = eigen_leads[src_sel]
         source_cov = source_cov[src_sel]
+        source_nn = source_nn[src_sel]
 
     if pick_ori == "normal":
         if not inv['source_ori'] == FIFF.FIFFV_MNE_FREE_ORI:
@@ -671,10 +688,10 @@ def _assemble_kernel(inv, label, method, pick_ori, verbose=None):
         eigen_leads = eigen_leads[2::3]
         source_cov = source_cov[2::3]
 
-    trans = inv['reginv'][:, None] * reduce(np.dot,
-                                            [inv['eigen_fields']['data'],
-                                             inv['whitener'],
-                                             inv['proj']])
+    trans = np.dot(inv['eigen_fields']['data'],
+                   np.dot(inv['whitener'], inv['proj']))
+    trans *= inv['reginv'][:, None]
+
     #
     #   Transformation into current distributions by weighting the eigenleads
     #   with the weights computed above
@@ -695,43 +712,84 @@ def _assemble_kernel(inv, label, method, pick_ori, verbose=None):
     if method == "MNE":
         noise_norm = None
 
-    return K, noise_norm, vertno
+    return K, noise_norm, vertno, source_nn
 
 
 def _check_method(method):
+    """Check the method."""
     if method not in ["MNE", "dSPM", "sLORETA"]:
         raise ValueError('method parameter should be "MNE" or "dSPM" '
                          'or "sLORETA".')
-    return method
 
 
-def _check_ori(pick_ori):
-    if pick_ori is not None and pick_ori != 'normal':
-        raise RuntimeError('pick_ori must be None or "normal", not %s'
-                           % pick_ori)
-    return pick_ori
+def _check_ori(pick_ori, source_ori):
+    """Check pick_ori."""
+    if pick_ori not in [None, 'normal', 'vector']:
+        raise RuntimeError('pick_ori must be None, "normal" or "vector", not '
+                           '%s' % pick_ori)
+    if pick_ori == 'vector' and source_ori != FIFF.FIFFV_MNE_FREE_ORI:
+        raise RuntimeError('pick_ori="vector" cannot be combined with an '
+                           'inverse operator with fixed orientations.')
+
+
+def _check_loose_forward(loose, forward, loose_as_fixed=(0., None)):
+    """Check the compatibility between loose and forward."""
+    if loose is None:
+        loose = 0. if None in loose_as_fixed else 1.
+        warn('loose=None is deprecated and will be removed in 0.16, '
+             'use loose=0 for fixed constraint and loose=1 for '
+             'free orientations, using loose=%s' % loose, DeprecationWarning)
+
+    src_kind = forward['src'].kind
+    if src_kind != 'surface':
+        if loose == 'auto':
+            loose = 1.
+        if loose != 1:
+            raise ValueError('loose parameter has to be 1 or "auto" for '
+                             'non-surface source space (Got loose=%s for %s '
+                             'source space).' % (loose, src_kind))
+    else:  # surface
+        if loose == 'auto':
+            loose = 0.2
+        # put the forward solution in fixed orientation if it's not already
+        if loose == 0. and not is_fixed_orient(forward):
+            forward = convert_forward_solution(forward, force_fixed=True,
+                                               use_cps=True)
+        elif loose < 1. and not forward['surf_ori']:
+            forward = convert_forward_solution(forward, surf_ori=True,
+                                               use_cps=True)
+
+    assert loose is not None
+    loose = float(loose)
+    if loose < 0 or loose > 1:
+        raise ValueError('loose must be between 0 and 1, got %s' % loose)
+
+    if loose == 0. and not is_fixed_orient(forward):
+        forward = convert_forward_solution(forward, force_fixed=True,
+                                           use_cps=True)
+
+    return loose, forward
 
 
 def _check_reference(inst):
-    """Aux funcion"""
+    """Check for EEG ref."""
     if _needs_eeg_average_ref_proj(inst.info):
         raise ValueError('EEG average reference is mandatory for inverse '
-                         'modeling, use add_eeg_ref method.')
+                         'modeling, use set_eeg_reference method.')
     if inst.info['custom_ref_applied']:
         raise ValueError('Custom EEG reference is not allowed for inverse '
                          'modeling.')
 
 
 def _subject_from_inverse(inverse_operator):
-    """Get subject id from inverse operator"""
+    """Get subject id from inverse operator."""
     return inverse_operator['src'][0].get('subject_his_id', None)
 
 
 @verbose
-def apply_inverse(evoked, inverse_operator, lambda2=1. / 9.,
-                  method="dSPM", pick_ori=None,
-                  prepared=False, label=None, verbose=None):
-    """Apply inverse operator to evoked data
+def apply_inverse(evoked, inverse_operator, lambda2=1. / 9., method="dSPM",
+                  pick_ori=None, prepared=False, label=None, verbose=None):
+    """Apply inverse operator to evoked data.
 
     Parameters
     ----------
@@ -744,21 +802,26 @@ def apply_inverse(evoked, inverse_operator, lambda2=1. / 9.,
         The regularization parameter.
     method : "MNE" | "dSPM" | "sLORETA"
         Use mininum norm, dSPM or sLORETA.
-    pick_ori : None | "normal"
+    pick_ori : None | "normal" | "vector"
         If "normal", rather than pooling the orientations by taking the norm,
         only the radial component is kept. This is only implemented
         when working with loose orientations.
+        If "vector", no pooling of the orientations is done and the vector
+        result will be returned in the form of a
+        :class:`mne.VectorSourceEstimate` object. This is only implemented when
+        working with loose orientations.
     prepared : bool
         If True, do not call `prepare_inverse_operator`.
     label : Label | None
         Restricts the source estimates to a given label. If None,
         source estimates will be computed for the entire source space.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
-    stc : SourceEstimate | VolSourceEstimate
+    stc : SourceEstimate | VectorSourceEstimate | VolSourceEstimate
         The source estimates
 
     See Also
@@ -767,8 +830,8 @@ def apply_inverse(evoked, inverse_operator, lambda2=1. / 9.,
     apply_inverse_epochs : Apply inverse operator to epochs object
     """
     _check_reference(evoked)
-    method = _check_method(method)
-    pick_ori = _check_ori(pick_ori)
+    _check_method(method)
+    _check_ori(pick_ori, inverse_operator['source_ori'])
     #
     #   Set up the inverse according to the parameters
     #
@@ -786,26 +849,29 @@ def apply_inverse(evoked, inverse_operator, lambda2=1. / 9.,
     sel = _pick_channels_inverse_operator(evoked.ch_names, inv)
     logger.info('Picked %d channels from the data' % len(sel))
     logger.info('Computing inverse...')
-    K, noise_norm, vertno = _assemble_kernel(inv, label, method, pick_ori)
+    K, noise_norm, vertno, source_nn = _assemble_kernel(inv, label, method,
+                                                        pick_ori)
     sol = np.dot(K, evoked.data[sel])  # apply imaging kernel
 
     is_free_ori = (inverse_operator['source_ori'] ==
-                   FIFF.FIFFV_MNE_FREE_ORI and pick_ori is None)
+                   FIFF.FIFFV_MNE_FREE_ORI and pick_ori != 'normal')
 
-    if is_free_ori:
+    if is_free_ori and pick_ori != 'vector':
         logger.info('combining the current components...')
         sol = combine_xyz(sol)
 
     if noise_norm is not None:
         logger.info('(dSPM)...')
+        if is_free_ori and pick_ori == 'vector':
+            noise_norm = noise_norm.repeat(3, axis=0)
         sol *= noise_norm
 
     tstep = 1.0 / evoked.info['sfreq']
     tmin = float(evoked.times[0])
     subject = _subject_from_inverse(inverse_operator)
 
-    stc = _make_stc(sol, vertices=vertno, tmin=tmin, tstep=tstep,
-                    subject=subject)
+    stc = _make_stc(sol, vertno, tmin=tmin, tstep=tstep, subject=subject,
+                    vector=(pick_ori == 'vector'), source_nn=source_nn)
     logger.info('[done]')
 
     return stc
@@ -816,7 +882,7 @@ def apply_inverse_raw(raw, inverse_operator, lambda2, method="dSPM",
                       label=None, start=None, stop=None, nave=1,
                       time_func=None, pick_ori=None, buffer_size=None,
                       prepared=False, verbose=None):
-    """Apply inverse operator to Raw data
+    """Apply inverse operator to Raw data.
 
     Parameters
     ----------
@@ -841,10 +907,14 @@ def apply_inverse_raw(raw, inverse_operator, lambda2, method="dSPM",
         Set to 1 on raw data.
     time_func : callable
         Linear function applied to sensor space time series.
-    pick_ori : None | "normal"
+    pick_ori : None | "normal" | "vector"
         If "normal", rather than pooling the orientations by taking the norm,
         only the radial component is kept. This is only implemented
         when working with loose orientations.
+        If "vector", no pooling of the orientations is done and the vector
+        result will be returned in the form of a
+        :class:`mne.VectorSourceEstimate` object. This does not work when using
+        an inverse operator with fixed orientations.
     buffer_size : int (or None)
         If not None, the computation of the inverse and the combination of the
         current components is performed in segments of length buffer_size
@@ -856,11 +926,12 @@ def apply_inverse_raw(raw, inverse_operator, lambda2, method="dSPM",
     prepared : bool
         If True, do not call `prepare_inverse_operator`.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
-    stc : SourceEstimate | VolSourceEstimate
+    stc : SourceEstimate | VectorSourceEstimate | VolSourceEstimate
         The source estimates.
 
     See Also
@@ -869,8 +940,8 @@ def apply_inverse_raw(raw, inverse_operator, lambda2, method="dSPM",
     apply_inverse : Apply inverse operator to evoked object
     """
     _check_reference(raw)
-    method = _check_method(method)
-    pick_ori = _check_ori(pick_ori)
+    _check_method(method)
+    _check_ori(pick_ori, inverse_operator['source_ori'])
 
     _check_ch_names(inverse_operator, raw.info)
 
@@ -893,10 +964,11 @@ def apply_inverse_raw(raw, inverse_operator, lambda2, method="dSPM",
     if time_func is not None:
         data = time_func(data)
 
-    K, noise_norm, vertno = _assemble_kernel(inv, label, method, pick_ori)
+    K, noise_norm, vertno, source_nn = _assemble_kernel(inv, label, method,
+                                                        pick_ori)
 
     is_free_ori = (inverse_operator['source_ori'] ==
-                   FIFF.FIFFV_MNE_FREE_ORI and pick_ori is None)
+                   FIFF.FIFFV_MNE_FREE_ORI and pick_ori != 'normal')
 
     if buffer_size is not None and is_free_ori:
         # Process the data in segments to conserve memory
@@ -906,29 +978,34 @@ def apply_inverse_raw(raw, inverse_operator, lambda2, method="dSPM",
 
         # Allocate space for inverse solution
         n_times = data.shape[1]
-        sol = np.empty((K.shape[0] // 3, n_times),
-                       dtype=(K[0, 0] * data[0, 0]).dtype)
+
+        n_dipoles = K.shape[0] if pick_ori == 'vector' else K.shape[0] // 3
+        sol = np.empty((n_dipoles, n_times), dtype=np.result_type(K, data))
 
         for pos in range(0, n_times, buffer_size):
-            sol[:, pos:pos + buffer_size] = \
-                combine_xyz(np.dot(K, data[:, pos:pos + buffer_size]))
+            sol_chunk = np.dot(K, data[:, pos:pos + buffer_size])
+            if pick_ori != 'vector':
+                sol_chunk = combine_xyz(sol_chunk)
+            sol[:, pos:pos + buffer_size] = sol_chunk
 
             logger.info('segment %d / %d done..'
                         % (pos / buffer_size + 1, n_seg))
     else:
         sol = np.dot(K, data)
-        if is_free_ori:
+        if is_free_ori and pick_ori != 'vector':
             logger.info('combining the current components...')
             sol = combine_xyz(sol)
 
     if noise_norm is not None:
+        if pick_ori == 'vector' and is_free_ori:
+            noise_norm = noise_norm.repeat(3, axis=0)
         sol *= noise_norm
 
     tmin = float(times[0])
     tstep = 1.0 / raw.info['sfreq']
     subject = _subject_from_inverse(inverse_operator)
-    stc = _make_stc(sol, vertices=vertno, tmin=tmin, tstep=tstep,
-                    subject=subject)
+    stc = _make_stc(sol, vertno, tmin=tmin, tstep=tstep, subject=subject,
+                    vector=(pick_ori == 'vector'), source_nn=source_nn)
     logger.info('[done]')
 
     return stc
@@ -937,9 +1014,9 @@ def apply_inverse_raw(raw, inverse_operator, lambda2, method="dSPM",
 def _apply_inverse_epochs_gen(epochs, inverse_operator, lambda2, method='dSPM',
                               label=None, nave=1, pick_ori=None,
                               prepared=False, verbose=None):
-    """ see apply_inverse_epochs """
-    method = _check_method(method)
-    pick_ori = _check_ori(pick_ori)
+    """Generate inverse solutions for epochs. Used in apply_inverse_epochs."""
+    _check_method(method)
+    _check_ori(pick_ori, inverse_operator['source_ori'])
 
     _check_ch_names(inverse_operator, epochs.info)
 
@@ -956,13 +1033,17 @@ def _apply_inverse_epochs_gen(epochs, inverse_operator, lambda2, method='dSPM',
     sel = _pick_channels_inverse_operator(epochs.ch_names, inv)
     logger.info('Picked %d channels from the data' % len(sel))
     logger.info('Computing inverse...')
-    K, noise_norm, vertno = _assemble_kernel(inv, label, method, pick_ori)
+    K, noise_norm, vertno, source_nn = _assemble_kernel(inv, label, method,
+                                                        pick_ori)
 
     tstep = 1.0 / epochs.info['sfreq']
     tmin = epochs.times[0]
 
     is_free_ori = (inverse_operator['source_ori'] ==
-                   FIFF.FIFFV_MNE_FREE_ORI and pick_ori is None)
+                   FIFF.FIFFV_MNE_FREE_ORI and pick_ori != 'normal')
+
+    if pick_ori == 'vector' and noise_norm is not None:
+        noise_norm = noise_norm.repeat(3, axis=0)
 
     if not is_free_ori and noise_norm is not None:
         # premultiply kernel with noise normalization
@@ -974,21 +1055,22 @@ def _apply_inverse_epochs_gen(epochs, inverse_operator, lambda2, method='dSPM',
         if is_free_ori:
             # Compute solution and combine current components (non-linear)
             sol = np.dot(K, e[sel])  # apply imaging kernel
-            if is_free_ori:
-                logger.info('combining the current components...')
+
+            logger.info('combining the current components...')
+            if pick_ori != 'vector':
                 sol = combine_xyz(sol)
 
-                if noise_norm is not None:
-                    sol *= noise_norm
+            if noise_norm is not None:
+                sol *= noise_norm
         else:
             # Linear inverse: do computation here or delayed
-            if len(sel) < K.shape[0]:
+            if len(sel) < K.shape[1]:
                 sol = (K, e[sel])
             else:
                 sol = np.dot(K, e[sel])
 
-        stc = _make_stc(sol, vertices=vertno, tmin=tmin, tstep=tstep,
-                        subject=subject)
+        stc = _make_stc(sol, vertno, tmin=tmin, tstep=tstep, subject=subject,
+                        vector=(pick_ori == 'vector'), source_nn=source_nn)
 
         yield stc
 
@@ -998,9 +1080,8 @@ def _apply_inverse_epochs_gen(epochs, inverse_operator, lambda2, method='dSPM',
 @verbose
 def apply_inverse_epochs(epochs, inverse_operator, lambda2, method="dSPM",
                          label=None, nave=1, pick_ori=None,
-                         return_generator=False,
-                         prepared=False, verbose=None):
-    """Apply inverse operator to Epochs
+                         return_generator=False, prepared=False, verbose=None):
+    """Apply inverse operator to Epochs.
 
     Parameters
     ----------
@@ -1019,21 +1100,26 @@ def apply_inverse_epochs(epochs, inverse_operator, lambda2, method="dSPM",
     nave : int
         Number of averages used to regularize the solution.
         Set to 1 on single Epoch by default.
-    pick_ori : None | "normal"
+    pick_ori : None | "normal" | "vector"
         If "normal", rather than pooling the orientations by taking the norm,
         only the radial component is kept. This is only implemented
         when working with loose orientations.
+        If "vector", no pooling of the orientations is done and the vector
+        result will be returned in the form of a
+        :class:`mne.VectorSourceEstimate` object. This does not work when using
+        an inverse operator with fixed orientations.
     return_generator : bool
         Return a generator object instead of a list. This allows iterating
         over the stcs without having to keep them all in memory.
     prepared : bool
         If True, do not call `prepare_inverse_operator`.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
-    stc : list of SourceEstimate or VolSourceEstimate
+    stc : list of (SourceEstimate | VectorSourceEstimate | VolSourceEstimate)
         The source estimates for all epochs.
 
     See Also
@@ -1041,7 +1127,6 @@ def apply_inverse_epochs(epochs, inverse_operator, lambda2, method="dSPM",
     apply_inverse_raw : Apply inverse operator to raw object
     apply_inverse : Apply inverse operator to evoked object
     """
-    _check_reference(epochs)
     stcs = _apply_inverse_epochs_gen(epochs, inverse_operator, lambda2,
                                      method=method, label=label, nave=nave,
                                      pick_ori=pick_ori, verbose=verbose,
@@ -1106,8 +1191,7 @@ def _xyz2lf(Lf_xyz, normals):
 @verbose
 def _prepare_forward(forward, info, noise_cov, pca=False, rank=None,
                      verbose=None):
-    """Util function to prepare forward solution for inverse solvers
-    """
+    """Prepare forward solution for inverse solvers."""
     # fwd['sol']['row_names'] may be different order from fwd['info']['chs']
     fwd_sol_ch_names = forward['sol']['row_names']
     ch_names = [c['ch_name'] for c in info['chs']
@@ -1124,25 +1208,8 @@ def _prepare_forward(forward, info, noise_cov, pca=False, rank=None,
     n_chan = len(ch_names)
     logger.info("Computing inverse operator with %d channels." % n_chan)
 
-    #
-    #   Handle noise cov
-    #
-    noise_cov = prepare_noise_cov(noise_cov, info, ch_names, rank)
-
-    #   Omit the zeroes due to projection
-    eig = noise_cov['eig']
-    nzero = (eig > 0)
-    n_nzero = sum(nzero)
-
-    if pca:
-        #   Rows of eigvec are the eigenvectors
-        whitener = noise_cov['eigvec'][nzero] / np.sqrt(eig[nzero])[:, None]
-        logger.info('Reducing data rank to %d' % n_nzero)
-    else:
-        whitener = np.zeros((n_chan, n_chan), dtype=np.float)
-        whitener[nzero, nzero] = 1.0 / np.sqrt(eig[nzero])
-        #   Rows of eigvec are the eigenvectors
-        whitener = np.dot(whitener, noise_cov['eigvec'])
+    whitener, noise_cov, n_nzero = _get_whitener(noise_cov, info, ch_names,
+                                                 rank, pca)
 
     gain = forward['sol']['data']
 
@@ -1155,16 +1222,14 @@ def _prepare_forward(forward, info, noise_cov, pca=False, rank=None,
     info_idx = [info['ch_names'].index(name) for name in ch_names]
     fwd_info = pick_info(info, info_idx)
 
-    logger.info('Total rank is %d' % n_nzero)
-
     return fwd_info, gain, noise_cov, whitener, n_nzero
 
 
 @verbose
-def make_inverse_operator(info, forward, noise_cov, loose=0.2, depth=0.8,
-                          fixed=False, limit_depth_chs=True, rank=None,
-                          verbose=None):
-    """Assemble inverse operator
+def make_inverse_operator(info, forward, noise_cov, loose='auto', depth=0.8,
+                          fixed='auto', limit_depth_chs=True, rank=None,
+                          use_cps=None, verbose=None):
+    """Assemble inverse operator.
 
     Parameters
     ----------
@@ -1175,15 +1240,21 @@ def make_inverse_operator(info, forward, noise_cov, loose=0.2, depth=0.8,
         Forward operator.
     noise_cov : instance of Covariance
         The noise covariance matrix.
-    loose : None | float in [0, 1]
+    loose : float in [0, 1] | 'auto'
         Value that weights the source variances of the dipole components
-        defining the tangent space of the cortical surfaces. Requires surface-
-        based, free orientation forward solutions.
+        that are parallel (tangential) to the cortical surface. If loose
+        is 0 then the solution is computed with fixed orientation,
+        and fixed must be True or "auto".
+        If loose is 1, it corresponds to free orientations.
+        The default value ('auto') is set to 0.2 for surface-oriented source
+        space and set to 1.0 for volumetric, discrete, or mixed source spaces,
+        unless ``fixed is True`` in which case the value 0. is used.
     depth : None | float in [0, 1]
         Depth weighting coefficients. If None, no depth weighting is performed.
-    fixed : bool
+    fixed : bool | 'auto'
         Use fixed source orientations normal to the cortical mantle. If True,
-        the loose parameter is ignored.
+        the loose parameter must be "auto" or 0. If 'auto', the loose value
+        is used.
     limit_depth_chs : bool
         If True, use only grad channels in depth weighting (equivalent to MNE
         C code). If grad chanels aren't present, only mag channels will be
@@ -1193,8 +1264,12 @@ def make_inverse_operator(info, forward, noise_cov, loose=0.2, depth=0.8,
         detected automatically. If int, the rank is specified for the MEG
         channels. A dictionary with entries 'eeg' and/or 'meg' can be used
         to specify the rank for each modality.
+    use_cps : None | bool (default None)
+        Whether to use cortical patch statistics to define normal
+        orientations. Only used when converting to surface orientation
+        (i.e., for surface source spaces and ``loose < 1``).
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`).
 
     Returns
     -------
@@ -1220,44 +1295,59 @@ def make_inverse_operator(info, forward, noise_cov, loose=0.2, depth=0.8,
         +---------------------+-----------+-----------+-----------+-----------------+--------------+
         | | Loose constraint  | 0.2       | None      | False     | False           | True         |
         +---------------------+-----------+-----------+-----------+-----------------+--------------+
-        | | Free orientation, | None      | 0.8       | False     | False           | True         |
+        | | Free orientation, | 1.0       | 0.8       | False     | False           | True         |
         | | Depth weighted    |           |           |           |                 |              |
         +---------------------+-----------+-----------+-----------+-----------------+--------------+
-        | | Free orientation  | None      | None      | False     | False           | True | False |
+        | | Free orientation  | 1.0       | None      | False     | False           | True | False |
         +---------------------+-----------+-----------+-----------+-----------------+--------------+
-        | | Fixed constraint, | None      | 0.8       | True      | False           | True         |
+        | | Fixed constraint, | 0.0       | 0.8       | True      | False           | True         |
         | | Depth weighted    |           |           |           |                 |              |
         +---------------------+-----------+-----------+-----------+-----------------+--------------+
-        | | Fixed constraint  | None      | None      | True      | True            | True         |
+        | | Fixed constraint  | 0.0       | None      | True      | True            | True         |
         +---------------------+-----------+-----------+-----------+-----------------+--------------+
 
     Also note that, if the source space (as stored in the forward solution)
     has patch statistics computed, these are used to improve the depth
     weighting. Thus slightly different results are to be expected with
     and without this information.
-    """  # noqa
+    """  # noqa: E501
     is_fixed_ori = is_fixed_orient(forward)
 
-    if fixed and loose is not None:
-        warn('When invoking make_inverse_operator with fixed=True, the loose '
-             'parameter is ignored.')
-        loose = None
-
-    if is_fixed_ori and not fixed:
-        raise ValueError('Forward operator has fixed orientation and can only '
-                         'be used to make a fixed-orientation inverse '
-                         'operator.')
+    # These gymnastics are necessary due to the redundancy between
+    # "fixed" and "loose"
+    if fixed == 'auto':
+        if loose == 'auto':
+            fixed, loose = False, 0.2
+        else:
+            fixed = True if float(loose) == 0 else False
     if fixed:
-        if depth is not None:
-            if is_fixed_ori or not forward['surf_ori']:
-                raise ValueError('For a fixed orientation inverse solution '
-                                 'with depth weighting, the forward solution '
-                                 'must be free-orientation and in surface '
-                                 'orientation')
-        elif forward['surf_ori'] is False:
-            raise ValueError('For a fixed orientation inverse solution '
-                             'without depth weighting, the forward solution '
-                             'must be in surface orientation')
+        if loose not in ['auto', 0.]:
+            raise ValueError('When invoking make_inverse_operator with '
+                             'fixed=True, loose must be 0. or "auto", '
+                             'got %s' % (loose,))
+        loose = 0.
+    if loose == 0.:
+        if fixed not in (True, 'auto'):
+            raise ValueError('If loose==0., then fixed must be True or "auto",'
+                             'got %s' % (fixed,))
+        fixed = True
+
+    if fixed and not is_fixed_ori:
+        # Here we use loose=1. because computation of depth priors is improved
+        # by operating on the free orientation forward; see code at the
+        # comment below "Deal with fixed orientation forward / inverse"
+        loose = 1.
+    if is_fixed_ori:
+        if not fixed:
+            raise ValueError(
+                'Forward operator has fixed orientation and can only '
+                'be used to make a fixed-orientation inverse '
+                'operator.')
+        if fixed and depth is not None:
+            raise ValueError(
+                'For a fixed orientation inverse solution with depth '
+                'weighting, the forward solution must be free-orientation and '
+                'in surface orientation')
 
     # depth=None can use fixed fwd, depth=0<x<1 must use free ori
     if depth is not None:
@@ -1267,18 +1357,13 @@ def make_inverse_operator(info, forward, noise_cov, loose=0.2, depth=0.8,
             raise ValueError('You need a free-orientation, surface-oriented '
                              'forward solution to do depth weighting even '
                              'when calculating a fixed-orientation inverse.')
-        if not forward['surf_ori']:
-            forward = convert_forward_solution(forward, surf_ori=True)
-        assert forward['surf_ori']
-    if loose is not None:
-        if not (0 <= loose <= 1):
-            raise ValueError('loose value should be smaller than 1 and bigger '
-                             'than 0, or None for not loose orientations.')
-        if loose < 1 and not forward['surf_ori']:
-            raise ValueError('Forward operator is not oriented in surface '
-                             'coordinates. A loose inverse operator requires '
-                             'a surface-based, free orientation forward '
-                             'operator.')
+
+    loose, forward = _check_loose_forward(loose, forward, loose_as_fixed=(0,))
+
+    if (depth is not None or loose != 1) and not forward['surf_ori']:
+        logger.info('Forward is not surface oriented, converting.')
+        forward = convert_forward_solution(forward, surf_ori=True,
+                                           use_cps=use_cps)
 
     #
     # 1. Read the bad channels
@@ -1313,7 +1398,8 @@ def make_inverse_operator(info, forward, noise_cov, loose=0.2, depth=0.8,
             # Convert to the fixed orientation forward solution now
             depth_prior = depth_prior[2::3]
             forward = convert_forward_solution(
-                forward, surf_ori=forward['surf_ori'], force_fixed=True)
+                forward, surf_ori=forward['surf_ori'], force_fixed=True,
+                use_cps=use_cps)
             is_fixed_ori = is_fixed_orient(forward)
             gain_info, gain, noise_cov, whitener, n_nzero = \
                 _prepare_forward(forward, info, noise_cov, verbose=False)
@@ -1437,7 +1523,7 @@ def make_inverse_operator(info, forward, noise_cov, loose=0.2, depth=0.8,
 
 
 def compute_rank_inverse(inv):
-    """Compute the rank of a linear inverse operator (MNE, dSPM, etc.)
+    """Compute the rank of a linear inverse operator (MNE, dSPM, etc.).
 
     Parameters
     ----------
@@ -1464,7 +1550,7 @@ def compute_rank_inverse(inv):
 
 @verbose
 def estimate_snr(evoked, inv, verbose=None):
-    """Estimate the SNR as a function of time for evoked data
+    r"""Estimate the SNR as a function of time for evoked data.
 
     Parameters
     ----------
@@ -1473,7 +1559,7 @@ def estimate_snr(evoked, inv, verbose=None):
     inv : instance of InverseOperator
         The inverse operator.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`).
 
     Returns
     -------
@@ -1528,7 +1614,7 @@ def estimate_snr(evoked, inv, verbose=None):
     of 0.001.
 
     .. versionadded:: 0.9.0
-    """  # noqa
+    """  # noqa: E501
     from scipy.stats import chi2
     _check_reference(evoked)
     _check_ch_names(inv, evoked.info)
diff --git a/mne/minimum_norm/psf_ctf.py b/mne/minimum_norm/psf_ctf.py
index 7081159..f2ec147 100644
--- a/mne/minimum_norm/psf_ctf.py
+++ b/mne/minimum_norm/psf_ctf.py
@@ -8,6 +8,7 @@ from copy import deepcopy
 import numpy as np
 from scipy import linalg
 
+from ..io.constants import FIFF
 from ..io.pick import pick_channels
 from ..utils import logger, verbose
 from ..forward import convert_forward_solution
@@ -18,7 +19,7 @@ from . import apply_inverse
 
 
 def _prepare_info(inverse_operator):
-    """Helper to get a usable dict"""
+    """Get a usable dict."""
     # in order to convert sub-leadfield matrix to evoked data type (pretending
     # it's an epoch, see in loop below), uses 'info' from inverse solution
     # because this has all the correct projector information
@@ -29,7 +30,7 @@ def _prepare_info(inverse_operator):
 
 
 def _pick_leadfield(leadfield, forward, ch_names):
-    """Helper to pick out correct lead field components"""
+    """Pick out correct lead field components."""
     # NB must pick from fwd['sol']['row_names'], not ['info']['ch_names'],
     # because ['sol']['data'] may be ordered differently from functional data
     picks_fwd = pick_channels(forward['sol']['row_names'], ch_names)
@@ -39,8 +40,8 @@ def _pick_leadfield(leadfield, forward, ch_names):
 @verbose
 def point_spread_function(inverse_operator, forward, labels, method='dSPM',
                           lambda2=1 / 9., pick_ori=None, mode='mean',
-                          n_svd_comp=1, verbose=None):
-    """Compute point-spread functions (PSFs) for linear estimators
+                          n_svd_comp=1, use_cps=None, verbose=None):
+    """Compute point-spread functions (PSFs) for linear estimators.
 
     Compute point-spread functions (PSF) in labels for a combination of inverse
     operator and forward solution. PSFs are computed for test sources that are
@@ -77,8 +78,12 @@ def point_spread_function(inverse_operator, forward, labels, method='dSPM',
         Number of SVD components for which PSFs will be computed and output
         (irrelevant for 'sum' and 'mean'). Explained variances within
         sub-leadfields are shown in screen output.
+    use_cps : None | bool (default None)
+        Whether to use cortical patch statistics to define normal
+        orientations. Only used when surf_ori and/or force_fixed are True.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -104,7 +109,7 @@ def point_spread_function(inverse_operator, forward, labels, method='dSPM',
     logger.info("About to process %d labels" % len(labels))
 
     forward = convert_forward_solution(forward, force_fixed=False,
-                                       surf_ori=True)
+                                       surf_ori=True, use_cps=use_cps)
     info = _prepare_info(inverse_operator)
     leadfield = _pick_leadfield(forward['sol']['data'][:, 2::3], forward,
                                 info['ch_names'])
@@ -194,7 +199,7 @@ def point_spread_function(inverse_operator, forward, labels, method='dSPM',
 def _get_matrix_from_inverse_operator(inverse_operator, forward, labels=None,
                                       method='dSPM', lambda2=1. / 9.,
                                       mode='mean', n_svd_comp=1):
-    """Get inverse matrix from an inverse operator
+    """Get inverse matrix from an inverse operator.
 
     Currently works only for fixed/loose orientation constraints
     For loose orientation constraint, the CTFs are computed for the radial
@@ -251,7 +256,8 @@ def _get_matrix_from_inverse_operator(inverse_operator, forward, labels=None,
     if not forward['surf_ori']:
         raise RuntimeError('Forward has to be surface oriented and '
                            'force_fixed=True.')
-    if not (forward['source_ori'] == 1):
+    if not ((forward['source_ori'] == FIFF.FIFFV_MNE_FIXED_ORI) or
+            (forward['source_ori'] == FIFF.FIFFV_MNE_FIXED_CPS_ORI)):
         raise RuntimeError('Forward has to be surface oriented and '
                            'force_fixed=True.')
 
@@ -356,8 +362,8 @@ def _get_matrix_from_inverse_operator(inverse_operator, forward, labels=None,
 @verbose
 def cross_talk_function(inverse_operator, forward, labels,
                         method='dSPM', lambda2=1 / 9., signed=False,
-                        mode='mean', n_svd_comp=1, verbose=None):
-    """Compute cross-talk functions (CTFs) for linear estimators
+                        mode='mean', n_svd_comp=1, use_cps=None, verbose=None):
+    """Compute cross-talk functions (CTFs) for linear estimators.
 
     Compute cross-talk functions (CTF) in labels for a combination of inverse
     operator and forward solution. CTFs are computed for test sources that are
@@ -392,8 +398,12 @@ def cross_talk_function(inverse_operator, forward, labels,
         Number of SVD components for which CTFs will be computed and output
         (irrelevant for 'sum' and 'mean'). Explained variances within
         sub-inverses are shown in screen output.
+    use_cps : None | bool (default None)
+        Whether to use cortical patch statistics to define normal
+        orientations. Only used when surf_ori and/or force_fixed are True.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -404,7 +414,7 @@ def cross_talk_function(inverse_operator, forward, labels,
         The last sample is the summed CTF across all labels.
     """
     forward = convert_forward_solution(forward, force_fixed=True,
-                                       surf_ori=True)
+                                       surf_ori=True, use_cps=use_cps)
 
     # get the inverse matrix corresponding to inverse operator
     out = _get_matrix_from_inverse_operator(inverse_operator, forward,
diff --git a/mne/minimum_norm/tests/test_inverse.py b/mne/minimum_norm/tests/test_inverse.py
index 0ba0374..34cb4f3 100644
--- a/mne/minimum_norm/tests/test_inverse.py
+++ b/mne/minimum_norm/tests/test_inverse.py
@@ -5,6 +5,7 @@ from numpy.testing import (assert_array_almost_equal, assert_equal,
                            assert_allclose, assert_array_equal)
 from scipy import sparse
 from nose.tools import assert_true, assert_raises
+import pytest
 import copy
 import warnings
 
@@ -14,8 +15,8 @@ from mne.event import read_events
 from mne.epochs import Epochs
 from mne.source_estimate import read_source_estimate, VolSourceEstimate
 from mne import (read_cov, read_forward_solution, read_evokeds, pick_types,
-                 pick_types_forward, make_forward_solution,
-                 convert_forward_solution, Covariance)
+                 pick_types_forward, make_forward_solution, EvokedArray,
+                 convert_forward_solution, Covariance, combine_evoked)
 from mne.io import read_raw_fif, Info
 from mne.minimum_norm.inverse import (apply_inverse, read_inverse_operator,
                                       apply_inverse_raw, apply_inverse_epochs,
@@ -24,7 +25,7 @@ from mne.minimum_norm.inverse import (apply_inverse, read_inverse_operator,
                                       compute_rank_inverse,
                                       prepare_inverse_operator)
 from mne.tests.common import assert_naming
-from mne.utils import _TempDir, run_tests_if_main, slow_test
+from mne.utils import _TempDir, run_tests_if_main
 from mne.externals import six
 
 test_path = testing.data_path(download=False)
@@ -36,6 +37,9 @@ fname_inv = op.join(s_path, 'sample_audvis_trunc-meg-eeg-oct-4-meg-inv.fif')
 fname_inv_fixed_nodepth = op.join(s_path,
                                   'sample_audvis_trunc-meg-eeg-oct-4-meg'
                                   '-nodepth-fixed-inv.fif')
+fname_inv_fixed_depth = op.join(s_path,
+                                'sample_audvis_trunc-meg-eeg-oct-4-meg'
+                                '-fixed-inv.fif')
 fname_inv_meeg_diag = op.join(s_path,
                               'sample_audvis_trunc-'
                               'meg-eeg-oct-4-meg-eeg-diagnoise-inv.fif')
@@ -59,16 +63,18 @@ lambda2 = 1.0 / snr ** 2
 last_keys = [None] * 10
 
 
-def read_forward_solution_meg(*args, **kwargs):
+def read_forward_solution_meg(fname, **kwargs):
     """Read MEG forward."""
-    fwd = read_forward_solution(*args, **kwargs)
+    fwd = convert_forward_solution(read_forward_solution(fname), copy=False,
+                                   **kwargs)
     fwd = pick_types_forward(fwd, meg=True, eeg=False)
     return fwd
 
 
-def read_forward_solution_eeg(*args, **kwargs):
+def read_forward_solution_eeg(fname, **kwargs):
     """Read EEG forward."""
-    fwd = read_forward_solution(*args, **kwargs)
+    fwd = convert_forward_solution(read_forward_solution(fname), copy=False,
+                                   **kwargs)
     fwd = pick_types_forward(fwd, meg=False, eeg=True)
     return fwd
 
@@ -111,45 +117,68 @@ def _compare(a, b):
         elif isinstance(a, np.ndarray):
             assert_array_almost_equal(a, b)
         else:
-            assert_true(a == b)
-    except Exception as exptn:
+            assert_equal(a, b)
+    except Exception:
         print(last_keys)
-        raise exptn
+        raise
 
 
 def _compare_inverses_approx(inv_1, inv_2, evoked, rtol, atol,
-                             check_depth=True):
+                             depth_atol=1e-6, ctol=0.999999,
+                             check_nn=True, check_K=True):
     """Compare inverses."""
     # depth prior
-    if check_depth:
-        if inv_1['depth_prior'] is not None:
-            assert_array_almost_equal(inv_1['depth_prior']['data'],
-                                      inv_2['depth_prior']['data'], 5)
-        else:
-            assert_true(inv_2['depth_prior'] is None)
+    if inv_1['depth_prior'] is not None:
+        assert_allclose(inv_1['depth_prior']['data'],
+                        inv_2['depth_prior']['data'], atol=depth_atol)
+    else:
+        assert_true(inv_2['depth_prior'] is None)
     # orient prior
     if inv_1['orient_prior'] is not None:
-        assert_array_almost_equal(inv_1['orient_prior']['data'],
-                                  inv_2['orient_prior']['data'])
+        assert_allclose(inv_1['orient_prior']['data'],
+                        inv_2['orient_prior']['data'], atol=1e-7)
     else:
         assert_true(inv_2['orient_prior'] is None)
     # source cov
-    assert_array_almost_equal(inv_1['source_cov']['data'],
-                              inv_2['source_cov']['data'])
-
-    # These are not as close as we'd like XXX
-    assert_array_almost_equal(np.abs(inv_1['eigen_fields']['data']),
-                              np.abs(inv_2['eigen_fields']['data']), 0)
-    assert_array_almost_equal(np.abs(inv_1['eigen_leads']['data']),
-                              np.abs(inv_2['eigen_leads']['data']), 0)
-
-    stc_1 = apply_inverse(evoked, inv_1, lambda2, "dSPM")
-    stc_2 = apply_inverse(evoked, inv_2, lambda2, "dSPM")
-
-    assert_true(stc_1.subject == stc_2.subject)
-    assert_equal(stc_1.times, stc_2.times)
-    assert_allclose(stc_1.data, stc_2.data, rtol=rtol, atol=atol)
-    assert_true(inv_1['units'] == inv_2['units'])
+    assert_allclose(inv_1['source_cov']['data'], inv_2['source_cov']['data'],
+                    atol=1e-7)
+    for key in ('units', 'eigen_leads_weighted', 'nsource', 'coord_frame'):
+        assert_equal(inv_1[key], inv_2[key], err_msg=key)
+    assert_equal(inv_1['eigen_leads']['ncol'], inv_2['eigen_leads']['ncol'])
+    K_1 = np.dot(inv_1['eigen_leads']['data'] * inv_1['sing'].astype(float),
+                 inv_1['eigen_fields']['data'])
+    K_2 = np.dot(inv_2['eigen_leads']['data'] * inv_2['sing'].astype(float),
+                 inv_2['eigen_fields']['data'])
+    # for free + surf ori, we only care about the ::2
+    # (the other two dimensions have arbitrary direction)
+    if inv_1['nsource'] * 3 == inv_1['source_nn'].shape[0]:
+        # Technically this undersamples the free-orientation, non-surf-ori
+        # inverse, but it's probably okay
+        sl = slice(2, None, 3)
+    else:
+        sl = slice(None)
+    if check_nn:
+        assert_allclose(inv_1['source_nn'][sl], inv_2['source_nn'][sl],
+                        atol=1e-4)
+    if check_K:
+        assert_allclose(np.abs(K_1[sl]), np.abs(K_2[sl]), rtol=rtol, atol=atol)
+
+    # Now let's do some practical tests, too
+    evoked = EvokedArray(np.eye(len(evoked.ch_names)), evoked.info)
+    for method in ('MNE', 'dSPM'):
+        stc_1 = apply_inverse(evoked, inv_1, lambda2, method)
+        stc_2 = apply_inverse(evoked, inv_2, lambda2, method)
+        assert_equal(stc_1.subject, stc_2.subject)
+        assert_equal(stc_1.times, stc_2.times)
+        stc_1 = stc_1.data
+        stc_2 = stc_2.data
+        norms = np.max(stc_1, axis=-1, keepdims=True)
+        stc_1 /= norms
+        stc_2 /= norms
+        corr = np.corrcoef(stc_1.ravel(), stc_2.ravel())[0, 1]
+        assert_true(corr > ctol, msg='%s < %s' % (corr, ctol))
+        assert_allclose(stc_1, stc_2, rtol=rtol, atol=atol,
+                        err_msg='%s: %s' % (method, corr))
 
 
 def _compare_io(inv_op, out_file_ext='.fif'):
@@ -174,44 +203,46 @@ def test_warn_inverse_operator():
     """Test MNE inverse warning without average EEG projection."""
     bad_info = copy.deepcopy(_get_evoked().info)
     bad_info['projs'] = list()
-    fwd_op = read_forward_solution(fname_fwd, surf_ori=True)
+    fwd_op = convert_forward_solution(read_forward_solution(fname_fwd),
+                                      surf_ori=True, copy=False)
     noise_cov = read_cov(fname_cov)
+    noise_cov['projs'].pop(-1)  # get rid of avg EEG ref proj
     with warnings.catch_warnings(record=True) as w:
         make_inverse_operator(bad_info, fwd_op, noise_cov)
     assert_equal(len(w), 1)
 
 
- at slow_test
+ at pytest.mark.slowtest
 @testing.requires_testing_data
 def test_make_inverse_operator():
-    """Test MNE inverse computation (precomputed and non-precomputed)
-    """
+    """Test MNE inverse computation (precomputed and non-precomputed)."""
     # Test old version of inverse computation starting from forward operator
     evoked = _get_evoked()
     noise_cov = read_cov(fname_cov)
     inverse_operator = read_inverse_operator(fname_inv)
-    fwd_op = read_forward_solution_meg(fname_fwd, surf_ori=True)
+    fwd_op = convert_forward_solution(read_forward_solution_meg(fname_fwd),
+                                      surf_ori=True, copy=False)
     my_inv_op = make_inverse_operator(evoked.info, fwd_op, noise_cov,
                                       loose=0.2, depth=0.8,
                                       limit_depth_chs=False)
     _compare_io(my_inv_op)
-    assert_true(inverse_operator['units'] == 'Am')
-    _compare_inverses_approx(my_inv_op, inverse_operator, evoked, 1e-2, 1e-2,
-                             check_depth=False)
+    assert_equal(inverse_operator['units'], 'Am')
+    _compare_inverses_approx(my_inv_op, inverse_operator, evoked,
+                             rtol=1e-2, atol=1e-5, depth_atol=1e-3)
     # Test MNE inverse computation starting from forward operator
     my_inv_op = make_inverse_operator(evoked.info, fwd_op, noise_cov,
                                       loose=0.2, depth=0.8)
     _compare_io(my_inv_op)
-    _compare_inverses_approx(my_inv_op, inverse_operator, evoked, 1e-2, 1e-2)
+    _compare_inverses_approx(my_inv_op, inverse_operator, evoked,
+                             rtol=1e-3, atol=1e-5)
     assert_true('dev_head_t' in my_inv_op['info'])
     assert_true('mri_head_t' in my_inv_op)
 
 
- at slow_test
+ at pytest.mark.slowtest
 @testing.requires_testing_data
 def test_inverse_operator_channel_ordering():
-    """Test MNE inverse computation is immune to channel reorderings
-    """
+    """Test MNE inverse computation is immune to channel reorderings."""
     # These are with original ordering
     evoked = _get_evoked()
     noise_cov = read_cov(fname_cov)
@@ -264,11 +295,10 @@ def test_inverse_operator_channel_ordering():
     assert_allclose(stc_1.data, stc_3.data, rtol=1e-5, atol=1e-5)
 
 
- at slow_test
+ at pytest.mark.slowtest
 @testing.requires_testing_data
 def test_apply_inverse_operator():
-    """Test MNE inverse application
-    """
+    """Test MNE inverse application."""
     inverse_operator = read_inverse_operator(fname_full)
     evoked = _get_evoked()
 
@@ -312,7 +342,7 @@ def test_apply_inverse_operator():
     assert_equal(stc_label.subject, 'sample')
     label_stc = stc.in_label(label)
     assert_true(label_stc.subject == 'sample')
-    assert_array_almost_equal(stc_label.data, label_stc.data)
+    assert_allclose(stc_label.data, label_stc.data)
 
     # Test we get errors when using custom ref or no average proj is present
     evoked.info['custom_ref_applied'] = True
@@ -324,75 +354,131 @@ def test_apply_inverse_operator():
 
 @testing.requires_testing_data
 def test_make_inverse_operator_fixed():
-    """Test MNE inverse computation (fixed orientation)
-    """
-    fwd_1 = read_forward_solution_meg(fname_fwd, surf_ori=False,
-                                      force_fixed=False)
-    fwd_2 = read_forward_solution_meg(fname_fwd, surf_ori=False,
-                                      force_fixed=True)
+    """Test MNE inverse computation (fixed orientation)."""
+    fwd = read_forward_solution_meg(fname_fwd)
     evoked = _get_evoked()
     noise_cov = read_cov(fname_cov)
 
-    # can't make depth-weighted fixed inv without surf ori fwd
-    assert_raises(ValueError, make_inverse_operator, evoked.info, fwd_1,
-                  noise_cov, depth=0.8, loose=None, fixed=True)
     # can't make fixed inv with depth weighting without free ori fwd
-    assert_raises(ValueError, make_inverse_operator, evoked.info, fwd_2,
-                  noise_cov, depth=0.8, loose=None, fixed=True)
+    fwd_fixed = convert_forward_solution(fwd, force_fixed=True,
+                                         use_cps=True)
+    assert_raises(ValueError, make_inverse_operator, evoked.info, fwd_fixed,
+                  noise_cov, depth=0.8, fixed=True)
 
     # now compare to C solution
     # note that the forward solution must not be surface-oriented
     # to get equivalency (surf_ori=True changes the normals)
-    inv_op = make_inverse_operator(evoked.info, fwd_2, noise_cov, depth=None,
-                                   loose=None, fixed=True)
+    inv_op = make_inverse_operator(evoked.info, fwd, noise_cov, depth=None,
+                                   fixed=True, use_cps=False)
+    assert 'EEG channels: 0' in repr(inv_op)
+    assert 'MEG channels: 305' in repr(inv_op)
+    del fwd_fixed
     inverse_operator_nodepth = read_inverse_operator(fname_inv_fixed_nodepth)
-    _compare_inverses_approx(inverse_operator_nodepth, inv_op, evoked, 0, 1e-2)
+    # XXX We should have this but we don't (MNE-C doesn't restrict info):
+    # assert 'EEG channels: 0' in repr(inverse_operator_nodepth)
+    assert 'MEG channels: 305' in repr(inverse_operator_nodepth)
+    _compare_inverses_approx(inverse_operator_nodepth, inv_op, evoked,
+                             rtol=1e-5, atol=1e-4)
     # Inverse has 306 channels - 6 proj = 302
     assert_true(compute_rank_inverse(inverse_operator_nodepth) == 302)
+    # Now with depth
+    fwd_surf = convert_forward_solution(fwd, surf_ori=True)  # not fixed
+    for kwargs, use_fwd in zip([dict(fixed=True), dict(loose=0.)],
+                               [fwd, fwd_surf]):  # Should be equiv.
+        inv_op_depth = make_inverse_operator(
+            evoked.info, use_fwd, noise_cov, depth=0.8, use_cps=True,
+            **kwargs)
+        inverse_operator_depth = read_inverse_operator(fname_inv_fixed_depth)
+        # Normals should be the adjusted ones
+        assert_allclose(inverse_operator_depth['source_nn'],
+                        fwd_surf['source_nn'][2::3], atol=1e-5)
+        _compare_inverses_approx(inverse_operator_depth, inv_op_depth, evoked,
+                                 rtol=1e-3, atol=1e-4)
 
 
 @testing.requires_testing_data
 def test_make_inverse_operator_free():
-    """Test MNE inverse computation (free orientation)
-    """
-    fwd_op = read_forward_solution_meg(fname_fwd, surf_ori=True)
-    fwd_1 = read_forward_solution_meg(fname_fwd, surf_ori=False,
-                                      force_fixed=False)
-    fwd_2 = read_forward_solution_meg(fname_fwd, surf_ori=False,
-                                      force_fixed=True)
+    """Test MNE inverse computation (free orientation)."""
+    fwd = read_forward_solution_meg(fname_fwd)
+    fwd_surf = convert_forward_solution(fwd, surf_ori=True)
+    fwd_fixed = convert_forward_solution(fwd, force_fixed=True,
+                                         use_cps=True)
     evoked = _get_evoked()
     noise_cov = read_cov(fname_cov)
 
     # can't make free inv with fixed fwd
-    assert_raises(ValueError, make_inverse_operator, evoked.info, fwd_2,
+    assert_raises(ValueError, make_inverse_operator, evoked.info, fwd_fixed,
                   noise_cov, depth=None)
 
-    # for free ori inv, loose=None and loose=1 should be equivalent
-    inv_1 = make_inverse_operator(evoked.info, fwd_op, noise_cov, loose=None)
-    inv_2 = make_inverse_operator(evoked.info, fwd_op, noise_cov, loose=1)
-    _compare_inverses_approx(inv_1, inv_2, evoked, 0, 1e-2)
-
     # for depth=None, surf_ori of the fwd should not matter
-    inv_3 = make_inverse_operator(evoked.info, fwd_op, noise_cov, depth=None,
-                                  loose=None)
-    inv_4 = make_inverse_operator(evoked.info, fwd_1, noise_cov, depth=None,
-                                  loose=None)
-    _compare_inverses_approx(inv_3, inv_4, evoked, 0, 1e-2)
+    inv_3 = make_inverse_operator(evoked.info, fwd_surf, noise_cov, depth=None,
+                                  loose=1.)
+    inv_4 = make_inverse_operator(evoked.info, fwd, noise_cov,
+                                  depth=None, loose=1.)
+    _compare_inverses_approx(inv_3, inv_4, evoked, rtol=1e-5, atol=1e-8,
+                             check_nn=False, check_K=False)
+
+
+ at testing.requires_testing_data
+def test_make_inverse_operator_vector():
+    """Test MNE inverse computation (vector result)."""
+    fwd_surf = read_forward_solution_meg(fname_fwd, surf_ori=True)
+    fwd_fixed = read_forward_solution_meg(fname_fwd, surf_ori=False)
+    evoked = _get_evoked()
+    noise_cov = read_cov(fname_cov)
+
+    # Make different version of the inverse operator
+    inv_1 = make_inverse_operator(evoked.info, fwd_fixed, noise_cov, loose=1)
+    inv_2 = make_inverse_operator(evoked.info, fwd_surf, noise_cov, depth=None,
+                                  use_cps=True)
+    inv_3 = make_inverse_operator(evoked.info, fwd_surf, noise_cov, fixed=True,
+                                  use_cps=True)
+    inv_4 = make_inverse_operator(evoked.info, fwd_fixed, noise_cov,
+                                  loose=.2, depth=None)
+
+    # Apply the inverse operators and check the result
+    for ii, inv in enumerate((inv_1, inv_2, inv_4)):
+        methods = ['MNE', 'dSPM', 'sLORETA'] if ii < 2 else ['MNE']
+        for method in methods:
+            stc = apply_inverse(evoked, inv, method=method)
+            stc_vec = apply_inverse(evoked, inv, pick_ori='vector',
+                                    method=method)
+            assert_allclose(stc.data, stc_vec.magnitude().data)
+
+    # Vector estimates don't work when using fixed orientations
+    assert_raises(RuntimeError, apply_inverse, evoked, inv_3,
+                  pick_ori='vector')
+
+    # When computing with vector fields, computing the difference between two
+    # evokeds and then performing the inverse should yield the same result as
+    # computing the difference between the inverses.
+    evoked0 = read_evokeds(fname_data, condition=0, baseline=(None, 0))
+    evoked0.crop(0, 0.2)
+    evoked1 = read_evokeds(fname_data, condition=1, baseline=(None, 0))
+    evoked1.crop(0, 0.2)
+    diff = combine_evoked((evoked0, evoked1), [1, -1])
+    stc_diff = apply_inverse(diff, inv_1, method='MNE')
+    stc_diff_vec = apply_inverse(diff, inv_1, method='MNE', pick_ori='vector')
+    stc_vec0 = apply_inverse(evoked0, inv_1, method='MNE', pick_ori='vector')
+    stc_vec1 = apply_inverse(evoked1, inv_1, method='MNE', pick_ori='vector')
+    assert_allclose(stc_diff_vec.data, (stc_vec0 - stc_vec1).data)
+    assert_allclose(stc_diff.data, (stc_vec0 - stc_vec1).magnitude().data)
 
 
 @testing.requires_testing_data
 def test_make_inverse_operator_diag():
-    """Test MNE inverse computation with diagonal noise cov
-    """
+    """Test MNE inverse computation with diagonal noise cov."""
     evoked = _get_evoked()
     noise_cov = read_cov(fname_cov).as_diag()
-    fwd_op = read_forward_solution(fname_fwd, surf_ori=True)
+    fwd_op = convert_forward_solution(read_forward_solution(fname_fwd),
+                                      surf_ori=True)
     inv_op = make_inverse_operator(evoked.info, fwd_op, noise_cov,
                                    loose=0.2, depth=0.8)
     _compare_io(inv_op)
     inverse_operator_diag = read_inverse_operator(fname_inv_meeg_diag)
-    # This one's only good to zero decimal places, roundoff error (?)
-    _compare_inverses_approx(inverse_operator_diag, inv_op, evoked, 0, 1e0)
+    # This one is pretty bad
+    _compare_inverses_approx(inverse_operator_diag, inv_op, evoked,
+                             rtol=1e-1, atol=1e-1, ctol=0.99, check_K=False)
     # Inverse has 366 channels - 6 proj = 360
     assert_true(compute_rank_inverse(inverse_operator_diag) == 360)
 
@@ -433,7 +519,7 @@ def test_inverse_operator_volume():
     assert_array_almost_equal(stc.times, stc2.times)
 
 
- at slow_test
+ at pytest.mark.slowtest
 @testing.requires_testing_data
 def test_io_inverse_operator():
     """Test IO of inverse_operator
@@ -472,13 +558,13 @@ def test_apply_mne_inverse_raw():
     """Test MNE with precomputed inverse operator on Raw."""
     start = 3
     stop = 10
-    raw = read_raw_fif(fname_raw, add_eeg_ref=False)
+    raw = read_raw_fif(fname_raw)
     label_lh = read_label(fname_label % 'Aud-lh')
     _, times = raw[0, start:stop]
     inverse_operator = read_inverse_operator(fname_full)
     inverse_operator = prepare_inverse_operator(inverse_operator, nave=1,
                                                 lambda2=lambda2, method="dSPM")
-    for pick_ori in [None, "normal"]:
+    for pick_ori in [None, "normal", "vector"]:
         stc = apply_inverse_raw(raw, inverse_operator, lambda2, "dSPM",
                                 label=label_lh, start=start, stop=stop, nave=1,
                                 pick_ori=pick_ori, buffer_size=None,
@@ -503,7 +589,7 @@ def test_apply_mne_inverse_raw():
 @testing.requires_testing_data
 def test_apply_mne_inverse_fixed_raw():
     """Test MNE with fixed-orientation inverse operator on Raw."""
-    raw = read_raw_fif(fname_raw, add_eeg_ref=False)
+    raw = read_raw_fif(fname_raw)
     start = 3
     stop = 10
     _, times = raw[0, start:stop]
@@ -513,8 +599,10 @@ def test_apply_mne_inverse_fixed_raw():
     fwd = read_forward_solution_meg(fname_fwd, force_fixed=False,
                                     surf_ori=True)
     noise_cov = read_cov(fname_cov)
+    assert_raises(ValueError, make_inverse_operator,
+                  raw.info, fwd, noise_cov, loose=1., fixed=True)
     inv_op = make_inverse_operator(raw.info, fwd, noise_cov,
-                                   loose=None, depth=0.8, fixed=True)
+                                   fixed=True, use_cps=True)
 
     inv_op2 = prepare_inverse_operator(inv_op, nave=1,
                                        lambda2=lambda2, method="dSPM")
@@ -546,7 +634,7 @@ def test_apply_mne_inverse_epochs():
     label_lh = read_label(fname_label % 'Aud-lh')
     label_rh = read_label(fname_label % 'Aud-rh')
     event_id, tmin, tmax = 1, -0.2, 0.5
-    raw = read_raw_fif(fname_raw, add_eeg_ref=False)
+    raw = read_raw_fif(fname_raw)
 
     picks = pick_types(raw.info, meg=True, eeg=False, stim=True, ecg=True,
                        eog=True, include=['STI 014'], exclude='bads')
@@ -555,24 +643,29 @@ def test_apply_mne_inverse_epochs():
 
     events = read_events(fname_event)[:15]
     epochs = Epochs(raw, events, event_id, tmin, tmax, picks=picks,
-                    baseline=(None, 0), reject=reject, flat=flat,
-                    add_eeg_ref=False)
-    stcs = apply_inverse_epochs(epochs, inverse_operator, lambda2, "dSPM",
-                                label=label_lh, pick_ori="normal")
-    inverse_operator = prepare_inverse_operator(inverse_operator, nave=1,
-                                                lambda2=lambda2, method="dSPM")
-    stcs2 = apply_inverse_epochs(epochs, inverse_operator, lambda2, "dSPM",
-                                 label=label_lh, pick_ori="normal",
-                                 prepared=True)
-    # test if using prepared and not prepared inverse operator give the same
-    # result
-    assert_array_almost_equal(stcs[0].data, stcs2[0].data)
-    assert_array_almost_equal(stcs[0].times, stcs2[0].times)
+                    baseline=(None, 0), reject=reject, flat=flat)
 
-    assert_true(len(stcs) == 2)
-    assert_true(3 < stcs[0].data.max() < 10)
-    assert_true(stcs[0].subject == 'sample')
+    inverse_operator = prepare_inverse_operator(inverse_operator, nave=1,
+                                                lambda2=lambda2,
+                                                method="dSPM")
+    for pick_ori in [None, "normal", "vector"]:
+        stcs = apply_inverse_epochs(epochs, inverse_operator, lambda2, "dSPM",
+                                    label=label_lh, pick_ori=pick_ori)
+        stcs2 = apply_inverse_epochs(epochs, inverse_operator, lambda2, "dSPM",
+                                     label=label_lh, pick_ori=pick_ori,
+                                     prepared=True)
+        # test if using prepared and not prepared inverse operator give the
+        # same result
+        assert_array_almost_equal(stcs[0].data, stcs2[0].data)
+        assert_array_almost_equal(stcs[0].times, stcs2[0].times)
+
+        assert_true(len(stcs) == 2)
+        assert_true(3 < stcs[0].data.max() < 10)
+        assert_true(stcs[0].subject == 'sample')
+    inverse_operator = read_inverse_operator(fname_full)
 
+    stcs = apply_inverse_epochs(epochs, inverse_operator, lambda2, "dSPM",
+                                label=label_lh, pick_ori='normal')
     data = sum(stc.data for stc in stcs) / len(stcs)
     flip = label_sign_flip(label_lh, inverse_operator['src'])
 
@@ -582,7 +675,9 @@ def test_apply_mne_inverse_epochs():
     assert_true(label_mean.max() < label_mean_flip.max())
 
     # test extracting a BiHemiLabel
-
+    inverse_operator = prepare_inverse_operator(inverse_operator, nave=1,
+                                                lambda2=lambda2,
+                                                method="dSPM")
     stcs_rh = apply_inverse_epochs(epochs, inverse_operator, lambda2, "dSPM",
                                    label=label_rh, pick_ori="normal",
                                    prepared=True)
@@ -613,7 +708,7 @@ def test_make_inverse_operator_bads():
 
     # test bads
     bad = evoked.info['bads'].pop()
-    inv_ = make_inverse_operator(evoked.info, fwd_op, noise_cov, loose=None)
+    inv_ = make_inverse_operator(evoked.info, fwd_op, noise_cov, loose=1.)
     union_good = set(noise_cov['names']) & set(evoked.ch_names)
     union_bads = set(noise_cov['bads']) & set(evoked.info['bads'])
     evoked.info['bads'].append(bad)
diff --git a/mne/minimum_norm/tests/test_psf_ctf.py b/mne/minimum_norm/tests/test_psf_ctf.py
index 78702e2..72e68b9 100644
--- a/mne/minimum_norm/tests/test_psf_ctf.py
+++ b/mne/minimum_norm/tests/test_psf_ctf.py
@@ -1,11 +1,12 @@
-
 import os.path as op
+import pytest
+
 import mne
 from mne.datasets import testing
 from mne import read_forward_solution
 from mne.minimum_norm import (read_inverse_operator,
                               point_spread_function, cross_talk_function)
-from mne.utils import slow_test, run_tests_if_main
+from mne.utils import run_tests_if_main
 
 from nose.tools import assert_true
 
@@ -24,7 +25,7 @@ snr = 3.0
 lambda2 = 1.0 / snr ** 2
 
 
- at slow_test
+ at pytest.mark.slowtest
 @testing.requires_testing_data
 def test_psf_ctf():
     """Test computation of PSFs and CTFs for linear estimators
@@ -43,7 +44,7 @@ def test_psf_ctf():
             stc_psf, psf_ev = point_spread_function(
                 inverse_operator, forward, method=method, labels=labels,
                 lambda2=lambda2, pick_ori='normal', mode=mode,
-                n_svd_comp=n_svd_comp)
+                n_svd_comp=n_svd_comp, use_cps=True)
 
             n_vert, n_samples = stc_psf.shape
             should_n_vert = (inverse_operator['src'][1]['vertno'].shape[0] +
@@ -64,7 +65,7 @@ def test_psf_ctf():
             stc_ctf = cross_talk_function(
                 inverse_operator, forward, labels, method=method,
                 lambda2=lambda2, signed=False, mode=mode,
-                n_svd_comp=n_svd_comp)
+                n_svd_comp=n_svd_comp, use_cps=True)
 
             n_vert, n_samples = stc_ctf.shape
             should_n_vert = (inverse_operator['src'][1]['vertno'].shape[0] +
diff --git a/mne/minimum_norm/tests/test_time_frequency.py b/mne/minimum_norm/tests/test_time_frequency.py
index c8c03ef..6152b85 100644
--- a/mne/minimum_norm/tests/test_time_frequency.py
+++ b/mne/minimum_norm/tests/test_time_frequency.py
@@ -1,13 +1,14 @@
 import os.path as op
 
 import numpy as np
-from numpy.testing import assert_array_almost_equal
+from numpy.testing import assert_array_almost_equal, assert_equal
 from nose.tools import assert_true
 import warnings
 
 from mne.datasets import testing
 from mne import find_events, Epochs, pick_types
 from mne.io import read_raw_fif
+from mne.io.constants import FIFF
 from mne.utils import run_tests_if_main
 from mne.label import read_label
 from mne.minimum_norm.inverse import (read_inverse_operator,
@@ -19,7 +20,7 @@ from mne.minimum_norm.time_frequency import (source_band_induced_power,
                                              compute_source_psd_epochs)
 
 
-from mne.time_frequency.multitaper import _psd_multitaper
+from mne.time_frequency.multitaper import psd_array_multitaper
 
 data_path = testing.data_path(download=False)
 fname_inv = op.join(data_path, 'MEG', 'sample',
@@ -36,7 +37,7 @@ def test_tfr_with_inverse_operator():
     tmin, tmax, event_id = -0.2, 0.5, 1
 
     # Setup for reading the raw data
-    raw = read_raw_fif(fname_data, add_eeg_ref=False)
+    raw = read_raw_fif(fname_data)
     events = find_events(raw, stim_channel='STI 014')
     inverse_operator = read_inverse_operator(fname_inv)
     inv = prepare_inverse_operator(inverse_operator, nave=1,
@@ -53,7 +54,7 @@ def test_tfr_with_inverse_operator():
     events3 = events[:3]  # take 3 events to keep the computation time low
     epochs = Epochs(raw, events3, event_id, tmin, tmax, picks=picks,
                     baseline=(None, 0), reject=dict(grad=4000e-13, eog=150e-6),
-                    preload=True, add_eeg_ref=False)
+                    preload=True)
 
     # Compute a source estimate per frequency band
     bands = dict(alpha=[10, 10])
@@ -78,15 +79,12 @@ def test_tfr_with_inverse_operator():
     # Compute a source estimate per frequency band
     epochs = Epochs(raw, events[:10], event_id, tmin, tmax, picks=picks,
                     baseline=(None, 0), reject=dict(grad=4000e-13, eog=150e-6),
-                    preload=True, add_eeg_ref=False)
-
-    frequencies = np.arange(7, 30, 2)  # define frequencies of interest
-    power, phase_lock = source_induced_power(epochs, inv,
-                                             frequencies, label,
-                                             baseline=(-0.1, 0),
-                                             baseline_mode='percent',
-                                             n_cycles=2, n_jobs=1,
-                                             prepared=True)
+                    preload=True)
+
+    freqs = np.arange(7, 30, 2)  # define frequencies of interest
+    power, phase_lock = source_induced_power(
+        epochs, inv, freqs, label, baseline=(-0.1, 0), baseline_mode='percent',
+        n_cycles=2, n_jobs=1, prepared=True)
     assert_true(np.all(phase_lock > 0))
     assert_true(np.all(phase_lock <= 1))
     assert_true(np.max(power) > 10)
@@ -95,27 +93,33 @@ def test_tfr_with_inverse_operator():
 @testing.requires_testing_data
 def test_source_psd():
     """Test source PSD computation in label."""
-    raw = read_raw_fif(fname_data, add_eeg_ref=False)
+    raw = read_raw_fif(fname_data)
     inverse_operator = read_inverse_operator(fname_inv)
-    label = read_label(fname_label)
     tmin, tmax = 0, 20  # seconds
     fmin, fmax = 55, 65  # Hz
     n_fft = 2048
-    stc = compute_source_psd(raw, inverse_operator, lambda2=1. / 9.,
-                             method="dSPM", tmin=tmin, tmax=tmax,
-                             fmin=fmin, fmax=fmax, pick_ori="normal",
-                             n_fft=n_fft, label=label, overlap=0.1)
-    assert_true(stc.times[0] >= fmin * 1e-3)
-    assert_true(stc.times[-1] <= fmax * 1e-3)
-    # Time max at line frequency (60 Hz in US)
-    assert_true(59e-3 <= stc.times[np.argmax(np.sum(stc.data, axis=0))] <=
-                61e-3)
+
+    assert_equal(inverse_operator['source_ori'], FIFF.FIFFV_MNE_FREE_ORI)
+
+    for pick_ori in ('normal', None):
+        stc = compute_source_psd(raw, inverse_operator, lambda2=1. / 9.,
+                                 method="dSPM", tmin=tmin, tmax=tmax,
+                                 fmin=fmin, fmax=fmax, pick_ori=pick_ori,
+                                 n_fft=n_fft, overlap=0.1)
+
+        assert_equal(stc.shape[0], inverse_operator['nsource'])
+
+        assert_true(stc.times[0] >= fmin * 1e-3)
+        assert_true(stc.times[-1] <= fmax * 1e-3)
+        # Time max at line frequency (60 Hz in US)
+        assert_true(58e-3 <= stc.times[np.argmax(np.sum(stc.data, axis=0))] <=
+                    61e-3)
 
 
 @testing.requires_testing_data
 def test_source_psd_epochs():
     """Test multi-taper source PSD computation in label from epochs."""
-    raw = read_raw_fif(fname_data, add_eeg_ref=False)
+    raw = read_raw_fif(fname_data)
     inverse_operator = read_inverse_operator(fname_inv)
     label = read_label(fname_label)
 
@@ -131,7 +135,7 @@ def test_source_psd_epochs():
 
     events = find_events(raw, stim_channel='STI 014')
     epochs = Epochs(raw, events, event_id, tmin, tmax, picks=picks,
-                    baseline=(None, 0), reject=reject, add_eeg_ref=False)
+                    baseline=(None, 0), reject=reject)
 
     # only look at one epoch
     epochs.drop_bad()
@@ -168,8 +172,9 @@ def test_source_psd_epochs():
                                prepared=True)[0]
 
     sfreq = epochs.info['sfreq']
-    psd, freqs = _psd_multitaper(stc.data, sfreq=sfreq, bandwidth=bandwidth,
-                                 fmin=fmin, fmax=fmax)
+    psd, freqs = psd_array_multitaper(stc.data, sfreq=sfreq,
+                                      bandwidth=bandwidth, fmin=fmin,
+                                      fmax=fmax)
 
     assert_array_almost_equal(psd, stc_psd.data)
     assert_array_almost_equal(freqs, stc_psd.times)
diff --git a/mne/minimum_norm/time_frequency.py b/mne/minimum_norm/time_frequency.py
index 6924b93..d2e02f1 100644
--- a/mne/minimum_norm/time_frequency.py
+++ b/mne/minimum_norm/time_frequency.py
@@ -16,7 +16,7 @@ from .inverse import (combine_xyz, prepare_inverse_operator, _assemble_kernel,
                       _pick_channels_inverse_operator, _check_method,
                       _check_ori, _subject_from_inverse)
 from ..parallel import parallel_func
-from ..utils import logger, verbose, warn
+from ..utils import logger, verbose, warn, _freqs_dep
 from ..externals import six
 
 
@@ -24,7 +24,7 @@ def _prepare_source_params(inst, inverse_operator, label=None,
                            lambda2=1.0 / 9.0, method="dSPM", nave=1,
                            decim=1, pca=True, pick_ori="normal",
                            prepared=False, verbose=None):
-    """Prepare inverse operator and params for spectral / TFR analysis"""
+    """Prepare inverse operator and params for spectral / TFR analysis."""
     if not prepared:
         inv = prepare_inverse_operator(inverse_operator, nave, lambda2, method)
     else:
@@ -42,7 +42,7 @@ def _prepare_source_params(inst, inverse_operator, label=None,
     #   This does all the data transformations to compute the weights for the
     #   eigenleads
     #
-    K, noise_norm, vertno = _assemble_kernel(inv, label, method, pick_ori)
+    K, noise_norm, vertno, _ = _assemble_kernel(inv, label, method, pick_ori)
 
     if pca:
         U, s, Vh = linalg.svd(K, full_matrices=False)
@@ -64,7 +64,7 @@ def source_band_induced_power(epochs, inverse_operator, bands, label=None,
                               baseline=None, baseline_mode='logratio',
                               pca=True, n_jobs=1, prepared=False,
                               verbose=None):
-    """Compute source space induced power in given frequency bands
+    """Compute source space induced power in given frequency bands.
 
     Parameters
     ----------
@@ -96,11 +96,20 @@ def source_band_induced_power(epochs, inverse_operator, bands, label=None,
         If a is None the beginning of the data is used and if b is None then b
         is set to the end of the interval. If baseline is equal to (None, None)
         all the time interval is used.
-    baseline_mode : None | 'logratio' | 'zscore'
-        Do baseline correction with ratio (power is divided by mean
-        power during baseline) or zscore (power is divided by standard
-        deviation of power during baseline after subtracting the mean,
-        power = [power - mean(power_baseline)] / std(power_baseline)).
+    baseline_mode : 'mean' | 'ratio' | 'logratio' | 'percent' | 'zscore' | 'zlogratio' | None
+        Perform baseline correction by
+
+          - subtracting the mean baseline power ('mean')
+          - dividing by the mean baseline power ('ratio')
+          - dividing by the mean baseline power and taking the log ('logratio')
+          - subtracting the mean baseline power followed by dividing by the
+            mean baseline power ('percent')
+          - subtracting the mean baseline power and dividing by the standard
+            deviation of the baseline power ('zscore')
+          - dividing by the mean baseline power, taking the log, and dividing
+            by the standard deviation of the baseline power ('zlogratio')
+
+        If None no baseline correction is applied.
     pca : bool
         If True, the true dimension of data is estimated before running
         the time-frequency transforms. It reduces the computation times
@@ -110,20 +119,21 @@ def source_band_induced_power(epochs, inverse_operator, bands, label=None,
     prepared : bool
         If True, do not call `prepare_inverse_operator`.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
     stcs : dict with a SourceEstimate (or VolSourceEstimate) for each band
         The estimated source space induced power estimates.
-    """
-    method = _check_method(method)
+    """  # noqa: E501
+    _check_method(method)
 
-    frequencies = np.concatenate([np.arange(band[0], band[1] + df / 2.0, df)
-                                 for _, band in six.iteritems(bands)])
+    freqs = np.concatenate([np.arange(band[0], band[1] + df / 2.0, df)
+                            for _, band in six.iteritems(bands)])
 
     powers, _, vertno = _source_induced_power(
-        epochs, inverse_operator, frequencies, label=label, lambda2=lambda2,
+        epochs, inverse_operator, freqs, label=label, lambda2=lambda2,
         method=method, nave=nave, n_cycles=n_cycles, decim=decim,
         use_fft=use_fft, pca=pca, n_jobs=n_jobs, with_plv=False,
         prepared=prepared)
@@ -132,9 +142,9 @@ def source_band_induced_power(epochs, inverse_operator, bands, label=None,
     stcs = dict()
 
     subject = _subject_from_inverse(inverse_operator)
-    _log_rescale(baseline, baseline_mode)
+    _log_rescale(baseline, baseline_mode)  # for early failure
     for name, band in six.iteritems(bands):
-        idx = [k for k, f in enumerate(frequencies) if band[0] <= f <= band[1]]
+        idx = [k for k, f in enumerate(freqs) if band[0] <= f <= band[1]]
 
         # average power in band + mean over epochs
         power = np.mean(powers[:, idx, :], axis=1)
@@ -155,7 +165,7 @@ def source_band_induced_power(epochs, inverse_operator, bands, label=None,
 
 
 def _prepare_tfr(data, decim, pick_ori, Ws, K, source_ori):
-    """Aux function to prepare TFR source localization"""
+    """Prepare TFR source localization."""
     n_times = data[:, :, ::decim].shape[2]
     n_freqs = len(Ws)
     n_sources = K.shape[0]
@@ -171,7 +181,7 @@ def _prepare_tfr(data, decim, pick_ori, Ws, K, source_ori):
 @verbose
 def _compute_pow_plv(data, K, sel, Ws, source_ori, use_fft, Vh,
                      with_power, with_plv, pick_ori, decim, verbose=None):
-    """Aux function for induced power and PLV"""
+    """Aux function for induced power and PLV."""
     shape, is_free_ori = _prepare_tfr(data, decim, pick_ori, Ws, K, source_ori)
     n_sources, n_times = shape[:2]
     power = np.zeros(shape, dtype=np.float)  # power or raw TFR
@@ -197,7 +207,7 @@ def _compute_pow_plv(data, K, sel, Ws, source_ori, use_fft, Vh,
 
 def _single_epoch_tfr(data, is_free_ori, K, Ws, use_fft, decim, shape,
                       with_plv, with_power):
-    """Compute single trial TFRs, either ITC, power or raw TFR"""
+    """Compute single trial TFRs, either ITC, power or raw TFR."""
     tfr_e = np.zeros(shape, dtype=np.float)  # power or raw TFR
     # phase lock
     plv_e = np.zeros(shape, dtype=np.complex) if with_plv else None
@@ -245,12 +255,12 @@ def _single_epoch_tfr(data, is_free_ori, K, Ws, use_fft, decim, shape,
 
 
 @verbose
-def _source_induced_power(epochs, inverse_operator, frequencies, label=None,
+def _source_induced_power(epochs, inverse_operator, freqs, label=None,
                           lambda2=1.0 / 9.0, method="dSPM", nave=1, n_cycles=5,
                           decim=1, use_fft=False, pca=True, pick_ori="normal",
                           n_jobs=1, with_plv=True, zero_mean=False,
                           prepared=False, verbose=None):
-    """Aux function for source induced power"""
+    """Aux function for source induced power."""
     epochs_data = epochs.get_data()
     K, sel, Vh, vertno, is_free_ori, noise_norm = _prepare_source_params(
         inst=epochs, inverse_operator=inverse_operator, label=label,
@@ -264,7 +274,7 @@ def _source_induced_power(epochs, inverse_operator, frequencies, label=None,
 
     logger.info('Computing source power ...')
 
-    Ws = morlet(Fs, frequencies, n_cycles=n_cycles, zero_mean=zero_mean)
+    Ws = morlet(Fs, freqs, n_cycles=n_cycles, zero_mean=zero_mean)
 
     n_jobs = min(n_jobs, len(epochs_data))
     out = parallel(my_compute_source_tfrs(data=data, K=K, sel=sel, Ws=Ws,
@@ -290,13 +300,13 @@ def _source_induced_power(epochs, inverse_operator, frequencies, label=None,
 
 
 @verbose
-def source_induced_power(epochs, inverse_operator, frequencies, label=None,
+def source_induced_power(epochs, inverse_operator, freqs, label=None,
                          lambda2=1.0 / 9.0, method="dSPM", nave=1, n_cycles=5,
                          decim=1, use_fft=False, pick_ori=None,
                          baseline=None, baseline_mode='logratio', pca=True,
                          n_jobs=1, zero_mean=False, prepared=False,
-                         verbose=None):
-    """Compute induced power and phase lock
+                         frequencies=None, verbose=None):
+    """Compute induced power and phase lock.
 
     Computation can optionaly be restricted in a label.
 
@@ -332,13 +342,22 @@ def source_induced_power(epochs, inverse_operator, frequencies, label=None,
         the interval is between "a (s)" and "b (s)".
         If a is None the beginning of the data is used
         and if b is None then b is set to the end of the interval.
-        If baseline is equal ot (None, None) all the time
+        If baseline is equal to (None, None) all the time
         interval is used.
-    baseline_mode : None | 'logratio' | 'zscore'
-        Do baseline correction with ratio (power is divided by mean
-        power during baseline) or zscore (power is divided by standard
-        deviation of power during baseline after subtracting the mean,
-        power = [power - mean(power_baseline)] / std(power_baseline)).
+    baseline_mode : 'mean' | 'ratio' | 'logratio' | 'percent' | 'zscore' | 'zlogratio' | None
+        Perform baseline correction by
+
+          - subtracting the mean baseline power ('mean')
+          - dividing by the mean baseline power ('ratio')
+          - dividing by the mean baseline power and taking the log ('logratio')
+          - subtracting the mean baseline power followed by dividing by the
+            mean baseline power ('percent')
+          - subtracting the mean baseline power and dividing by the standard
+            deviation of the baseline power ('zscore')
+          - dividing by the mean baseline power, taking the log, and dividing
+            by the standard deviation of the baseline power ('zlogratio')
+
+        If None no baseline correction is applied.
     pca : bool
         If True, the true dimension of data is estimated before running
         the time-frequency transforms. It reduces the computation times
@@ -350,20 +369,18 @@ def source_induced_power(epochs, inverse_operator, frequencies, label=None,
     prepared : bool
         If True, do not call `prepare_inverse_operator`.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
-    """
-    method = _check_method(method)
-    pick_ori = _check_ori(pick_ori)
-
-    power, plv, vertno = _source_induced_power(epochs,
-                                               inverse_operator, frequencies,
-                                               label=label, lambda2=lambda2,
-                                               method=method, nave=nave,
-                                               n_cycles=n_cycles, decim=decim,
-                                               use_fft=use_fft,
-                                               pick_ori=pick_ori,
-                                               pca=pca, n_jobs=n_jobs,
-                                               prepared=False)
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
+    """  # noqa: E501
+    freqs = _freqs_dep(freqs, frequencies)
+    _check_method(method)
+    _check_ori(pick_ori, inverse_operator['source_ori'])
+
+    power, plv, vertno = _source_induced_power(
+        epochs, inverse_operator, freqs, label=label, lambda2=lambda2,
+        method=method, nave=nave, n_cycles=n_cycles, decim=decim,
+        use_fft=use_fft, pick_ori=pick_ori, pca=pca, n_jobs=n_jobs,
+        prepared=False)
 
     # Run baseline correction
     power = rescale(power, epochs.times[::decim], baseline, baseline_mode,
@@ -376,7 +393,7 @@ def compute_source_psd(raw, inverse_operator, lambda2=1. / 9., method="dSPM",
                        tmin=None, tmax=None, fmin=0., fmax=200.,
                        n_fft=2048, overlap=0.5, pick_ori=None, label=None,
                        nave=1, pca=True, prepared=False, verbose=None):
-    """Compute source power spectrum density (PSD)
+    """Compute source power spectrum density (PSD).
 
     Parameters
     ----------
@@ -418,7 +435,8 @@ def compute_source_psd(raw, inverse_operator, lambda2=1. / 9., method="dSPM",
     prepared : bool
         If True, do not call `prepare_inverse_operator`.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -426,7 +444,7 @@ def compute_source_psd(raw, inverse_operator, lambda2=1. / 9., method="dSPM",
         The PSD (in dB) of each of the sources.
     """
     from scipy.signal import hanning
-    pick_ori = _check_ori(pick_ori)
+    _check_ori(pick_ori, inverse_operator['source_ori'])
 
     logger.info('Considering frequencies %g ... %g Hz' % (fmin, fmax))
 
@@ -447,7 +465,10 @@ def compute_source_psd(raw, inverse_operator, lambda2=1. / 9., method="dSPM",
     freqs_mask = (freqs >= 0) & (freqs >= fmin) & (freqs <= fmax)
     freqs = freqs[freqs_mask]
     fstep = np.mean(np.diff(freqs))
-    psd = np.zeros((K.shape[0], np.sum(freqs_mask)))
+    if is_free_ori and pick_ori is None:
+        psd = np.zeros((K.shape[0] // 3, np.sum(freqs_mask)))
+    else:
+        psd = np.zeros((K.shape[0], np.sum(freqs_mask)))
     n_windows = 0
 
     for this_start in np.arange(start, stop, int(n_fft * (1. - overlap))):
@@ -492,8 +513,7 @@ def _compute_source_psd_epochs(epochs, inverse_operator, lambda2=1. / 9.,
                                pca=True, inv_split=None, bandwidth=4.,
                                adaptive=False, low_bias=True, n_jobs=1,
                                prepared=False, verbose=None):
-    """ Generator for compute_source_psd_epochs """
-
+    """Generate compute_source_psd_epochs."""
     logger.info('Considering frequencies %g ... %g Hz' % (fmin, fmax))
 
     K, sel, Vh, vertno, is_free_ori, noise_norm = _prepare_source_params(
@@ -603,8 +623,9 @@ def compute_source_psd_epochs(epochs, inverse_operator, lambda2=1. / 9.,
                               adaptive=False, low_bias=True,
                               return_generator=False, n_jobs=1,
                               prepared=False, verbose=None):
-    """Compute source power spectrum density (PSD) from Epochs using
-       multi-taper method
+    """Compute source power spectrum density (PSD) from Epochs.
+
+    This uses the multi-taper method to compute the PSD.
 
     Parameters
     ----------
@@ -650,14 +671,14 @@ def compute_source_psd_epochs(epochs, inverse_operator, lambda2=1. / 9.,
     prepared : bool
         If True, do not call `prepare_inverse_operator`.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
     stcs : list (or generator object) of SourceEstimate | VolSourceEstimate
         The source space PSDs for each epoch.
     """
-
     # use an auxiliary function so we can either return a generator or a list
     stcs_gen = _compute_source_psd_epochs(epochs, inverse_operator,
                                           lambda2=lambda2, method=method,
diff --git a/mne/misc.py b/mne/misc.py
index ab5f1bd..2eeadc9 100644
--- a/mne/misc.py
+++ b/mne/misc.py
@@ -5,7 +5,7 @@
 
 
 def parse_config(fname):
-    """Parse a config file (like .ave and .cov files)
+    """Parse a config file (like .ave and .cov files).
 
     Parameters
     ----------
@@ -60,14 +60,13 @@ def parse_config(fname):
 
 
 def read_reject_parameters(fname):
-    """Read rejection parameters from .cov or .ave config file
+    """Read rejection parameters from .cov or .ave config file.
 
     Parameters
     ----------
     fname : str
         Filename to read.
     """
-
     try:
         with open(fname, 'r') as f:
             lines = f.readlines()
@@ -88,8 +87,7 @@ def read_reject_parameters(fname):
 
 
 def read_flat_parameters(fname):
-    """Read flat channel rejection parameters from .cov or .ave config file"""
-
+    """Read flat channel rejection parameters from .cov or .ave config file."""
     try:
         with open(fname, 'r') as f:
             lines = f.readlines()
diff --git a/mne/parallel.py b/mne/parallel.py
index 990a996..9e1a4a0 100644
--- a/mne/parallel.py
+++ b/mne/parallel.py
@@ -1,5 +1,4 @@
-"""Parallel util function
-"""
+"""Parallel util function."""
 
 # Author: Alexandre Gramfort <alexandre.gramfort at telecom-paristech.fr>
 #
@@ -20,8 +19,9 @@ else:
 
 
 @verbose
-def parallel_func(func, n_jobs, verbose=None, max_nbytes='auto'):
-    """Return parallel instance with delayed function
+def parallel_func(func, n_jobs, verbose=None, max_nbytes='auto',
+                  pre_dispatch='2 * n_jobs'):
+    """Return parallel instance with delayed function.
 
     Util function to use joblib only if available
 
@@ -32,14 +32,31 @@ def parallel_func(func, n_jobs, verbose=None, max_nbytes='auto'):
     n_jobs: int
         Number of jobs to run in parallel
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
-        INFO or DEBUG will print parallel status, others will not.
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more). INFO or DEBUG
+        will print parallel status, others will not.
     max_nbytes : int, str, or None
         Threshold on the minimum size of arrays passed to the workers that
         triggers automated memmory mapping. Can be an int in Bytes,
         or a human-readable string, e.g., '1M' for 1 megabyte.
         Use None to disable memmaping of large arrays. Use 'auto' to
         use the value set using mne.set_memmap_min_size.
+    pre_dispatch : int, or string, optional
+        Controls the number of jobs that get dispatched during parallel
+        execution. Reducing this number can be useful to avoid an
+        explosion of memory consumption when more jobs get dispatched
+        than CPUs can process. This parameter can be:
+
+            - None, in which case all the jobs are immediately
+              created and spawned. Use this for lightweight and
+              fast-running jobs, to avoid delays due to on-demand
+              spawning of the jobs
+
+            - An int, giving the exact number of total jobs that are
+              spawned
+
+            - A string, giving an expression as a function of n_jobs,
+              as in '2*n_jobs'
 
     Returns
     -------
@@ -90,6 +107,7 @@ def parallel_func(func, n_jobs, verbose=None, max_nbytes='auto'):
 
     # create keyword arguments for Parallel
     kwargs = {'verbose': 5 if logger.level <= logging.INFO else 0}
+    kwargs['pre_dispatch'] = pre_dispatch
 
     if joblib_mmap:
         if cache_dir is None:
@@ -104,7 +122,7 @@ def parallel_func(func, n_jobs, verbose=None, max_nbytes='auto'):
 
 
 def check_n_jobs(n_jobs, allow_cuda=False):
-    """Check n_jobs in particular for negative values
+    """Check n_jobs in particular for negative values.
 
     Parameters
     ----------
diff --git a/mne/preprocessing/__init__.py b/mne/preprocessing/__init__.py
index ab173a6..6960c0a 100644
--- a/mne/preprocessing/__init__.py
+++ b/mne/preprocessing/__init__.py
@@ -1,4 +1,4 @@
-"""Preprocessing with artifact detection, SSP, and ICA"""
+"""Preprocessing with artifact detection, SSP, and ICA."""
 
 # Authors: Alexandre Gramfort <alexandre.gramfort at telecom-paristech.fr>
 #          Matti Hamalainen <msh at nmr.mgh.harvard.edu>
@@ -14,6 +14,7 @@ from .ecg import find_ecg_events, create_ecg_epochs
 from .ica import (ICA, ica_find_eog_events, ica_find_ecg_events,
                   get_score_funcs, read_ica, run_ica, corrmap)
 from .bads import find_outliers
+from .infomax_ import infomax
 from .stim import fix_stim_artifact
 from .maxwell import maxwell_filter
 from .xdawn import Xdawn
diff --git a/mne/preprocessing/_fine_cal.py b/mne/preprocessing/_fine_cal.py
index f9c8b03..88277a7 100644
--- a/mne/preprocessing/_fine_cal.py
+++ b/mne/preprocessing/_fine_cal.py
@@ -9,7 +9,7 @@ from ..utils import check_fname, _check_fname
 
 
 def read_fine_calibration(fname):
-    """Read fine calibration information from a .dat file
+    """Read fine calibration information from a .dat file.
 
     The fine calibration typically includes improved sensor locations,
     calibration coefficients, and gradiometer imbalance information.
@@ -25,7 +25,7 @@ def read_fine_calibration(fname):
         Fine calibration information.
     """
     # Read new sensor locations
-    _check_fname(fname, overwrite=True, must_exist=True)
+    _check_fname(fname, overwrite='read', must_exist=True)
     check_fname(fname, 'cal', ('.dat',))
     ch_names = list()
     locs = list()
@@ -59,7 +59,7 @@ def read_fine_calibration(fname):
 
 
 def write_fine_calibration(fname, calibration):
-    """Write fine calibration information to a .dat file
+    """Write fine calibration information to a .dat file.
 
     Parameters
     ----------
diff --git a/mne/preprocessing/bads.py b/mne/preprocessing/bads.py
index c2f6827..b37b97c 100644
--- a/mne/preprocessing/bads.py
+++ b/mne/preprocessing/bads.py
@@ -6,7 +6,7 @@ import numpy as np
 
 
 def find_outliers(X, threshold=3.0, max_iter=2):
-    """Find outliers based on iterated Z-scoring
+    """Find outliers based on iterated Z-scoring.
 
     This procedure compares the absolute z-score against the threshold.
     After excluding local outliers, the comparison is repeated until no
diff --git a/mne/preprocessing/ctps_.py b/mne/preprocessing/ctps_.py
index 606a9de..43455c5 100644
--- a/mne/preprocessing/ctps_.py
+++ b/mne/preprocessing/ctps_.py
@@ -8,7 +8,7 @@ import numpy as np
 
 
 def _compute_normalized_phase(data):
-    """Compute normalized phase angles
+    """Compute normalized phase angles.
 
     Parameters
     ----------
@@ -25,7 +25,7 @@ def _compute_normalized_phase(data):
 
 
 def ctps(data, is_raw=True):
-    """Compute cross-trial-phase-statistics [1]
+    """Compute cross-trial-phase-statistics [1].
 
     Note. It is assumed that the sources are already
     appropriately filtered
@@ -78,8 +78,8 @@ def ctps(data, is_raw=True):
     return ks_dynamics, pk_dynamics, phase_angles if is_raw else None
 
 
-def kuiper(data, dtype=np.float64):
-    """ Kuiper's test of uniform distribution
+def kuiper(data, dtype=np.float64):  # noqa: D401
+    """Kuiper's test of uniform distribution.
 
     Parameters
     ----------
@@ -118,7 +118,7 @@ def kuiper(data, dtype=np.float64):
 
 
 def _prob_kuiper(d, n_eff, dtype='f8'):
-    """ Test for statistical significance against uniform distribution.
+    """Test for statistical significance against uniform distribution.
 
     Parameters
     ----------
diff --git a/mne/preprocessing/ecg.py b/mne/preprocessing/ecg.py
index d79c69e..30c23f3 100644
--- a/mne/preprocessing/ecg.py
+++ b/mne/preprocessing/ecg.py
@@ -9,9 +9,9 @@ import numpy as np
 from .. import pick_types, pick_channels
 from ..externals.six import string_types
 from ..utils import logger, verbose, sum_squared, warn
-from ..filter import band_pass_filter
-from ..epochs import Epochs, _BaseEpochs
-from ..io.base import _BaseRaw
+from ..filter import filter_data
+from ..epochs import Epochs, BaseEpochs
+from ..io.base import BaseRaw
 from ..evoked import Evoked
 from ..io import RawArray
 from .. import create_info
@@ -52,10 +52,9 @@ def qrs_detector(sfreq, ecg, thresh_value=0.6, levels=2.5, n_thresh=3,
     """
     win_size = int(round((60.0 * sfreq) / 120.0))
 
-    filtecg = band_pass_filter(ecg, sfreq, l_freq, h_freq,
-                               filter_length=filter_length,
-                               l_trans_bandwidth=0.5, h_trans_bandwidth=0.5,
-                               phase='zero-double', fir_window='hann')
+    filtecg = filter_data(ecg, sfreq, l_freq, h_freq, None, filter_length,
+                          0.5, 0.5, phase='zero-double', fir_window='hann',
+                          fir_design='firwin2')
 
     ecg_abs = np.abs(filtecg)
     init = int(sfreq)
@@ -133,7 +132,7 @@ def qrs_detector(sfreq, ecg, thresh_value=0.6, levels=2.5, n_thresh=3,
 def find_ecg_events(raw, event_id=999, ch_name=None, tstart=0.0,
                     l_freq=5, h_freq=35, qrs_threshold='auto',
                     filter_length='10s', return_ecg=False, verbose=None):
-    """Find ECG peaks
+    """Find ECG peaks.
 
     Parameters
     ----------
@@ -163,7 +162,8 @@ def find_ecg_events(raw, event_id=999, ch_name=None, tstart=0.0,
         Return ecg channel if synthesized. Defaults to False. If True and
         and ecg exists this will yield None.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -180,7 +180,7 @@ def find_ecg_events(raw, event_id=999, ch_name=None, tstart=0.0,
                     % raw.ch_names[idx_ecg])
         ecg, times = raw[idx_ecg, :]
     else:
-        ecg, times = _make_ecg(raw, None, None, verbose)
+        ecg, times = _make_ecg(raw, None, None, verbose=verbose)
 
     # detecting QRS and generating event file
     ecg_events = qrs_detector(raw.info['sfreq'], ecg.ravel(), tstart=tstart,
@@ -202,7 +202,7 @@ def find_ecg_events(raw, event_id=999, ch_name=None, tstart=0.0,
 
 
 def _get_ecg_channel_index(ch_name, inst):
-    """Geting ECG channel index. If no channel found returns None."""
+    """Get ECG channel index, if no channel found returns None."""
     if ch_name is None:
         ecg_idx = pick_types(inst.info, meg=False, eeg=False, stim=False,
                              eog=False, ecg=True, emg=False, ref_meg=False,
@@ -229,8 +229,8 @@ def _get_ecg_channel_index(ch_name, inst):
 def create_ecg_epochs(raw, ch_name=None, event_id=999, picks=None, tmin=-0.5,
                       tmax=0.5, l_freq=8, h_freq=16, reject=None, flat=None,
                       baseline=None, preload=True, keep_ecg=False,
-                      verbose=None):
-    """Conveniently generate epochs around ECG artifact events
+                      reject_by_annotation=True, verbose=None):
+    """Conveniently generate epochs around ECG artifact events.
 
     Parameters
     ----------
@@ -284,8 +284,16 @@ def create_ecg_epochs(raw, ch_name=None, event_id=999, picks=None, tmin=-0.5,
         When ECG is synthetically created (after picking), should it be added
         to the epochs? Must be False when synthetic channel is not used.
         Defaults to False.
+    reject_by_annotation : bool
+        Whether to reject based on annotations. If True (default), epochs
+        overlapping with segments whose description begins with ``'bad'`` are
+        rejected. If False, no rejection based on annotations is performed.
+
+        .. versionadded:: 0.14.0
+
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -298,6 +306,9 @@ def create_ecg_epochs(raw, ch_name=None, event_id=999, picks=None, tmin=-0.5,
         raw, ch_name=ch_name, event_id=event_id, l_freq=l_freq, h_freq=h_freq,
         return_ecg=True, verbose=verbose)
 
+    # Load raw data so that add_channels works
+    raw.load_data()
+
     if not has_ecg:
         ecg_raw = RawArray(
             ecg[None],
@@ -319,7 +330,8 @@ def create_ecg_epochs(raw, ch_name=None, event_id=999, picks=None, tmin=-0.5,
     ecg_epochs = Epochs(raw, events=events, event_id=event_id,
                         tmin=tmin, tmax=tmax, proj=False, flat=flat,
                         picks=picks, reject=reject, baseline=baseline,
-                        verbose=verbose, preload=preload, add_eeg_ref=False)
+                        reject_by_annotation=reject_by_annotation,
+                        verbose=verbose, preload=preload)
 
     if not has_ecg:
         raw.drop_channels(['ECG-SYN'])
@@ -328,9 +340,8 @@ def create_ecg_epochs(raw, ch_name=None, event_id=999, picks=None, tmin=-0.5,
 
 
 @verbose
-def _make_ecg(inst, start, stop, verbose=None):
-    """Create ECG signal from cross channel average
-    """
+def _make_ecg(inst, start, stop, reject_by_annotation=False, verbose=None):
+    """Create ECG signal from cross channel average."""
     if not any(c in inst for c in ['mag', 'grad']):
         raise ValueError('Unable to generate artificial ECG channel')
     for ch in ['mag', 'grad']:
@@ -340,9 +351,11 @@ def _make_ecg(inst, start, stop, verbose=None):
                 .format({'mag': 'Magnetometers',
                          'grad': 'Gradiometers'}[ch]))
     picks = pick_types(inst.info, meg=ch, eeg=False, ref_meg=False)
-    if isinstance(inst, _BaseRaw):
-        ecg, times = inst[picks, start:stop]
-    elif isinstance(inst, _BaseEpochs):
+    if isinstance(inst, BaseRaw):
+        reject_by_annotation = 'omit' if reject_by_annotation else None
+        ecg, times = inst.get_data(picks, start, stop, reject_by_annotation,
+                                   True)
+    elif isinstance(inst, BaseEpochs):
         ecg = np.hstack(inst.copy().crop(start, stop).get_data())
         times = inst.times
     elif isinstance(inst, Evoked):
diff --git a/mne/preprocessing/eog.py b/mne/preprocessing/eog.py
index 4e0b8c1..9e4e941 100644
--- a/mne/preprocessing/eog.py
+++ b/mne/preprocessing/eog.py
@@ -8,8 +8,8 @@ import numpy as np
 
 from .peak_finder import peak_finder
 from .. import pick_types, pick_channels
-from ..utils import logger, verbose
-from ..filter import band_pass_filter
+from ..utils import logger, verbose, _pl
+from ..filter import filter_data
 from ..epochs import Epochs
 from ..externals.six import string_types
 
@@ -17,8 +17,8 @@ from ..externals.six import string_types
 @verbose
 def find_eog_events(raw, event_id=998, l_freq=1, h_freq=10,
                     filter_length='10s', ch_name=None, tstart=0,
-                    verbose=None):
-    """Locate EOG artifacts
+                    reject_by_annotation=False, verbose=None):
+    """Locate EOG artifacts.
 
     Parameters
     ----------
@@ -36,20 +36,27 @@ def find_eog_events(raw, event_id=998, l_freq=1, h_freq=10,
         If not None, use specified channel(s) for EOG
     tstart : float
         Start detection after tstart seconds.
+    reject_by_annotation : bool
+        Whether to omit data that is annotated as bad.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
     eog_events : array
         Events.
     """
-
     # Getting EOG Channel
     eog_inds = _get_eog_channel_index(ch_name, raw)
     logger.info('EOG channel index for this subject is: %s' % eog_inds)
 
-    eog, _ = raw[eog_inds, :]
+    # Reject bad segments.
+    reject_by_annotation = 'omit' if reject_by_annotation else None
+    eog, times = raw.get_data(picks=eog_inds,
+                              reject_by_annotation=reject_by_annotation,
+                              return_times=True)
+    times = times * raw.info['sfreq'] + raw.first_samp
 
     eog_events = _find_eog_events(eog, event_id=event_id, l_freq=l_freq,
                                   h_freq=h_freq,
@@ -57,32 +64,33 @@ def find_eog_events(raw, event_id=998, l_freq=1, h_freq=10,
                                   first_samp=raw.first_samp,
                                   filter_length=filter_length,
                                   tstart=tstart)
-
+    # Map times to corresponding samples.
+    eog_events[:, 0] = np.round(times[eog_events[:, 0] -
+                                      raw.first_samp]).astype(int)
     return eog_events
 
 
 def _find_eog_events(eog, event_id, l_freq, h_freq, sampling_rate, first_samp,
                      filter_length='10s', tstart=0.):
-    """Helper function"""
-
+    """Find EOG events."""
     logger.info('Filtering the data to remove DC offset to help '
                 'distinguish blinks from saccades')
 
     # filtering to remove dc offset so that we know which is blink and saccades
     fmax = np.minimum(45, sampling_rate / 2.0 - 0.75)  # protect Nyquist
-    filteog = np.array([band_pass_filter(
-        x, sampling_rate, 2, fmax, filter_length=filter_length,
-        l_trans_bandwidth=0.5, h_trans_bandwidth=0.5, phase='zero-double',
-        fir_window='hann') for x in eog])
+    filteog = np.array([filter_data(
+        x, sampling_rate, 2, fmax, None, filter_length, 0.5, 0.5,
+        phase='zero-double', fir_window='hann', fir_design='firwin2')
+        for x in eog])
     temp = np.sqrt(np.sum(filteog ** 2, axis=1))
 
     indexmax = np.argmax(temp)
 
     # easier to detect peaks with filtering.
-    filteog = band_pass_filter(
-        eog[indexmax], sampling_rate, l_freq, h_freq,
-        filter_length=filter_length, l_trans_bandwidth=0.5,
-        h_trans_bandwidth=0.5, phase='zero-double', fir_window='hann')
+    filteog = filter_data(
+        eog[indexmax], sampling_rate, l_freq, h_freq, None,
+        filter_length, 0.5, 0.5, phase='zero-double', fir_window='hann',
+        fir_design='firwin2')
 
     # detecting eog blinks and generating event file
 
@@ -106,6 +114,7 @@ def _find_eog_events(eog, event_id, l_freq, h_freq, sampling_rate, first_samp,
 
 
 def _get_eog_channel_index(ch_name, inst):
+    """Get EOG channel index."""
     if isinstance(ch_name, string_types):
         # Check if multiple EOG Channels
         if ',' in ch_name:
@@ -119,8 +128,7 @@ def _get_eog_channel_index(ch_name, inst):
             raise ValueError('%s not in channel list' % ch_name)
         else:
             logger.info('Using channel %s as EOG channel%s' % (
-                        " and ".join(ch_name),
-                        '' if len(eog_inds) < 2 else 's'))
+                        " and ".join(ch_name), _pl(eog_inds)))
     elif ch_name is None:
 
         eog_inds = pick_types(inst.info, meg=False, eeg=False, stim=False,
@@ -141,11 +149,11 @@ def _get_eog_channel_index(ch_name, inst):
 
 
 @verbose
-def create_eog_epochs(raw, ch_name=None, event_id=998, picks=None,
-                      tmin=-0.5, tmax=0.5, l_freq=1, h_freq=10,
-                      reject=None, flat=None, baseline=None,
-                      preload=True, verbose=None):
-    """Conveniently generate epochs around EOG artifact events
+def create_eog_epochs(raw, ch_name=None, event_id=998, picks=None, tmin=-0.5,
+                      tmax=0.5, l_freq=1, h_freq=10, reject=None, flat=None,
+                      baseline=None, preload=True, reject_by_annotation=True,
+                      verbose=None):
+    """Conveniently generate epochs around EOG artifact events.
 
     Parameters
     ----------
@@ -193,8 +201,17 @@ def create_eog_epochs(raw, ch_name=None, event_id=998, picks=None,
         interval is used. If None, no correction is applied.
     preload : bool
         Preload epochs or not.
+    reject_by_annotation : bool
+        Whether to reject based on annotations. If True (default), segments
+        whose description begins with ``'bad'`` are not used for finding
+        artifacts and epochs overlapping with them are rejected. If False, no
+        rejection based on annotations is performed.
+
+        .. versionadded:: 0.14.0
+
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -202,11 +219,12 @@ def create_eog_epochs(raw, ch_name=None, event_id=998, picks=None,
         Data epoched around EOG events.
     """
     events = find_eog_events(raw, ch_name=ch_name, event_id=event_id,
-                             l_freq=l_freq, h_freq=h_freq)
+                             l_freq=l_freq, h_freq=h_freq,
+                             reject_by_annotation=reject_by_annotation)
 
     # create epochs around EOG events
-    eog_epochs = Epochs(raw, events=events, event_id=event_id,
-                        tmin=tmin, tmax=tmax, proj=False, reject=reject,
-                        flat=flat, picks=picks, baseline=baseline,
-                        preload=preload, add_eeg_ref=False)
+    eog_epochs = Epochs(raw, events=events, event_id=event_id, tmin=tmin,
+                        tmax=tmax, proj=False, reject=reject, flat=flat,
+                        picks=picks, baseline=baseline, preload=preload,
+                        reject_by_annotation=reject_by_annotation)
     return eog_epochs
diff --git a/mne/preprocessing/ica.py b/mne/preprocessing/ica.py
index 6d080bc..bdf6625 100644
--- a/mne/preprocessing/ica.py
+++ b/mne/preprocessing/ica.py
@@ -7,6 +7,7 @@
 from inspect import isfunction
 from collections import namedtuple
 from copy import deepcopy
+from numbers import Integral
 
 import os
 import json
@@ -31,8 +32,8 @@ from ..io.open import fiff_open
 from ..io.tag import read_tag
 from ..io.meas_info import write_meas_info, read_meas_info
 from ..io.constants import Bunch, FIFF
-from ..io.base import _BaseRaw
-from ..epochs import _BaseEpochs
+from ..io.base import BaseRaw
+from ..epochs import BaseEpochs
 from ..viz import (plot_ica_components, plot_ica_scores,
                    plot_ica_sources, plot_ica_overlay)
 from ..viz.ica import plot_ica_properties
@@ -45,22 +46,23 @@ from ..channels.channels import _contains_ch_type, ContainsMixin
 from ..io.write import start_file, end_file, write_id
 from ..utils import (check_version, logger, check_fname, verbose,
                      _reject_data_segments, check_random_state,
-                     _get_fast_dot, compute_corr, _get_inst_data,
-                     copy_function_doc_to_method_doc)
+                     compute_corr, _get_inst_data, _ensure_int,
+                     copy_function_doc_to_method_doc, _pl, warn)
+
 from ..fixes import _get_args
-from ..filter import band_pass_filter
+from ..filter import filter_data
 from .bads import find_outliers
 from .ctps_ import ctps
 from ..externals.six import string_types, text_type
 from ..io.pick import channel_type
 
 
-__all__ = ['ICA', 'ica_find_ecg_events', 'ica_find_eog_events',
-           'get_score_funcs', 'read_ica', 'run_ica']
+__all__ = ('ICA', 'ica_find_ecg_events', 'ica_find_eog_events',
+           'get_score_funcs', 'read_ica', 'run_ica')
 
 
 def _make_xy_sfunc(func, ndim_output=False):
-    """Aux function"""
+    """Aux function."""
     if ndim_output:
         def sfunc(x, y):
             return np.array([func(a, y.ravel()) for a in x])[:, 0]
@@ -74,7 +76,7 @@ def _make_xy_sfunc(func, ndim_output=False):
 
 # makes score funcs attr accessible for users
 def get_score_funcs():
-    """Helper to get the score functions"""
+    """Get the score functions."""
     from scipy import stats
     from scipy.spatial import distance
     score_funcs = Bunch()
@@ -92,8 +94,9 @@ def get_score_funcs():
 
 
 def _check_for_unsupported_ica_channels(picks, info):
-    """Check for channels in picks that are not considered
-    valid channels. Accepted channels are the data channels
+    """Check for channels in picks that are not considered valid channels.
+
+    Accepted channels are the data channels
     ('seeg','ecog','eeg', 'hbo', 'hbr', 'mag', and 'grad') and 'eog'.
     This prevents the program from crashing without
     feedback when a bad channel is provided to ICA whitening.
@@ -113,7 +116,7 @@ def _check_for_unsupported_ica_channels(picks, info):
 
 
 class ICA(ContainsMixin):
-    """M/EEG signal decomposition using Independent Component Analysis (ICA)
+    """M/EEG signal decomposition using Independent Component Analysis (ICA).
 
     This object can be used to estimate ICA components and then
     remove some from Raw or Epochs for data exploration or artifact
@@ -134,6 +137,20 @@ class ICA(ContainsMixin):
               Extended-Infomax seems to be more stable in this respect
               enhancing reproducibility and stability of results.
 
+    .. warning:: ICA is sensitive to low-frequency drifts and therefore
+                 requires the data to be high-pass filtered prior to fitting.
+                 Typically, a cutoff frequency of 1 Hz is recommended. Note
+                 that FIR filters prior to MNE 0.15 used the ``'firwin2'``
+                 design method, which generally produces rather shallow filters
+                 that might not work for ICA processing. Therefore, it is
+                 recommended to use IIR filters for MNE up to 0.14. In MNE
+                 0.15, FIR filters can be designed with the ``'firwin'``
+                 method, which generally produces much steeper filters. This
+                 method will be the default FIR design method in MNE 0.16. In
+                 MNE 0.15, you need to explicitly set ``fir_design='firwin'``
+                 to use this method. This is the recommended filter method for
+                 ICA preprocessing.
+
     Parameters
     ----------
     n_components : int | float | None
@@ -170,7 +187,8 @@ class ICA(ContainsMixin):
     max_iter : int, optional
         Maximum number of iterations during fit.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Attributes
     ----------
@@ -215,11 +233,12 @@ class ICA(ContainsMixin):
         independent components. This attribute is set by some of the artifact
         detection functions.
     """
+
     @verbose
     def __init__(self, n_components=None, max_pca_components=None,
                  n_pca_components=None, noise_cov=None, random_state=None,
                  method='fastica', fit_params=None, max_iter=200,
-                 verbose=None):
+                 verbose=None):  # noqa: D102
         methods = ('fastica', 'infomax', 'extended-infomax')
         if method not in methods:
             raise ValueError('`method` must be "%s". You passed: "%s"' %
@@ -230,8 +249,9 @@ class ICA(ContainsMixin):
 
         self.noise_cov = noise_cov
 
-        if max_pca_components is not None and \
-                n_components > max_pca_components:
+        if (n_components is not None and
+                max_pca_components is not None and
+                n_components > max_pca_components):
             raise ValueError('n_components must be smaller than '
                              'max_pca_components')
 
@@ -271,9 +291,10 @@ class ICA(ContainsMixin):
         self.exclude = []
         self.info = None
         self.method = method
+        self.labels_ = dict()
 
     def __repr__(self):
-        """ICA fit information"""
+        """ICA fit information."""
         if self.current_fit == 'unfitted':
             s = 'no'
         elif self.current_fit == 'raw':
@@ -296,8 +317,9 @@ class ICA(ContainsMixin):
 
     @verbose
     def fit(self, inst, picks=None, start=None, stop=None, decim=None,
-            reject=None, flat=None, tstep=2.0, verbose=None):
-        """Run the ICA decomposition on raw data
+            reject=None, flat=None, tstep=2.0, reject_by_annotation=True,
+            verbose=None):
+        """Run the ICA decomposition on raw data.
 
         Caveat! If supplying a noise covariance keep track of the projections
         available in the cov, the raw or the epochs object. For example,
@@ -343,21 +365,30 @@ class ICA(ContainsMixin):
         tstep : float
             Length of data chunks for artifact rejection in seconds.
             It only applies if `inst` is of type Raw.
+        reject_by_annotation : bool
+            Whether to omit bad segments from the data before fitting. If True,
+            annotated segments with a description that starts with 'bad' are
+            omitted. Has no effect if ``inst`` is an Epochs or Evoked object.
+            Defaults to True.
+
+            .. versionadded:: 0.14.0
+
         verbose : bool, str, int, or None
-            If not None, override default verbose level (see mne.verbose).
-            Defaults to self.verbose.
+            If not None, override default verbose level (see
+            :func:`mne.verbose` and :ref:`Logging documentation <tut_logging>`
+            for more). Defaults to self.verbose.
 
         Returns
         -------
         self : instance of ICA
             Returns the modified instance.
         """
-        if isinstance(inst, _BaseRaw) or isinstance(inst, _BaseEpochs):
+        if isinstance(inst, (BaseRaw, BaseEpochs)):
             _check_for_unsupported_ica_channels(picks, inst.info)
-            if isinstance(inst, _BaseRaw):
+            if isinstance(inst, BaseRaw):
                 self._fit_raw(inst, picks, start, stop, decim, reject, flat,
-                              tstep, verbose)
-            elif isinstance(inst, _BaseEpochs):
+                              tstep, reject_by_annotation, verbose)
+            elif isinstance(inst, BaseEpochs):
                 self._fit_epochs(inst, picks, decim, verbose)
         else:
             raise ValueError('Data input must be of Raw or Epochs type')
@@ -370,7 +401,7 @@ class ICA(ContainsMixin):
         return self
 
     def _reset(self):
-        """Aux method"""
+        """Aux method."""
         del self._pre_whitener
         del self.unmixing_matrix_
         del self.mixing_matrix_
@@ -383,9 +414,8 @@ class ICA(ContainsMixin):
             del self.drop_inds_
 
     def _fit_raw(self, raw, picks, start, stop, decim, reject, flat, tstep,
-                 verbose):
-        """Aux method
-        """
+                 reject_by_annotation, verbose):
+        """Aux method."""
         if self.current_fit != 'unfitted':
             self._reset()
 
@@ -406,8 +436,10 @@ class ICA(ContainsMixin):
         self.ch_names = self.info['ch_names']
         start, stop = _check_start_stop(raw, start, stop)
 
+        reject_by_annotation = 'omit' if reject_by_annotation else None
         # this will be a copy
-        data = raw[picks, start:stop][0]
+        data = raw.get_data(picks, start, stop, reject_by_annotation)
+
         # this will be a view
         if decim is not None:
             data = data[:, ::decim]
@@ -427,8 +459,7 @@ class ICA(ContainsMixin):
         return self
 
     def _fit_epochs(self, epochs, picks, decim, verbose):
-        """Aux method
-        """
+        """Aux method."""
         if self.current_fit != 'unfitted':
             self._reset()
 
@@ -466,8 +497,7 @@ class ICA(ContainsMixin):
         return self
 
     def _pre_whiten(self, data, info, picks):
-        """Aux function"""
-        fast_dot = _get_fast_dot()
+        """Aux function."""
         has_pre_whitener = hasattr(self, '_pre_whitener')
         if not has_pre_whitener and self.noise_cov is None:
             # use standardization as whitener
@@ -497,19 +527,18 @@ class ICA(ContainsMixin):
         elif not has_pre_whitener and self.noise_cov is not None:
             pre_whitener, _ = compute_whitener(self.noise_cov, info, picks)
             assert data.shape[0] == pre_whitener.shape[1]
-            data = fast_dot(pre_whitener, data)
+            data = np.dot(pre_whitener, data)
         elif has_pre_whitener and self.noise_cov is None:
             data /= self._pre_whitener
             pre_whitener = self._pre_whitener
         else:
-            data = fast_dot(self._pre_whitener, data)
+            data = np.dot(self._pre_whitener, data)
             pre_whitener = self._pre_whitener
 
         return data, pre_whitener
 
     def _fit(self, data, max_pca_components, fit_type):
-        """Aux function """
-
+        """Aux function."""
         random_state = check_random_state(self.random_state)
 
         if not check_version('sklearn', '0.18'):
@@ -532,7 +561,11 @@ class ICA(ContainsMixin):
         if isinstance(self.n_components, float):
             # compute eplained variance manually, cf. sklearn bug
             # fixed in #2664
-            explained_variance_ratio_ = pca.explained_variance_ / full_var
+            if check_version('sklearn', '0.19'):
+                explained_variance_ratio_ = pca.explained_variance_ratio_
+            else:
+                explained_variance_ratio_ = pca.explained_variance_ / full_var
+            del full_var
             n_components_ = np.sum(explained_variance_ratio_.cumsum() <=
                                    self.n_components)
             if n_components_ < 1:
@@ -565,6 +598,7 @@ class ICA(ContainsMixin):
         del pca
         # update number of components
         self.n_components_ = sel.stop
+        self._update_ica_names()
         if self.n_pca_components is not None:
             if self.n_pca_components > len(self.pca_components_):
                 self.n_pca_components = len(self.pca_components_)
@@ -586,19 +620,23 @@ class ICA(ContainsMixin):
         self.mixing_matrix_ = linalg.pinv(self.unmixing_matrix_)
         self.current_fit = fit_type
 
+    def _update_ica_names(self):
+        """Update ICA names when n_components_ is set."""
+        self._ica_names = ['ICA%03d' % ii for ii in range(self.n_components_)]
+
     def _transform(self, data):
-        """Compute sources from data (operates inplace)"""
-        fast_dot = _get_fast_dot()
+        """Compute sources from data (operates inplace)."""
         if self.pca_mean_ is not None:
             data -= self.pca_mean_[:, None]
 
         # Apply first PCA
-        pca_data = fast_dot(self.pca_components_[:self.n_components_], data)
+        pca_data = np.dot(self.pca_components_[:self.n_components_], data)
         # Apply unmixing to low dimension PCA
-        sources = fast_dot(self.unmixing_matrix_, pca_data)
+        sources = np.dot(self.unmixing_matrix_, pca_data)
         return sources
 
-    def _transform_raw(self, raw, start, stop):
+    def _transform_raw(self, raw, start, stop, reject_by_annotation=False):
+        """Transform raw data."""
         if not hasattr(self, 'mixing_matrix_'):
             raise RuntimeError('No fit available. Please fit ICA.')
         start, stop = _check_start_stop(raw, start, stop)
@@ -612,12 +650,15 @@ class ICA(ContainsMixin):
                                'ica.ch_names' % (len(self.ch_names),
                                                  len(picks)))
 
-        data, _ = self._pre_whiten(raw[picks, start:stop][0], raw.info, picks)
+        if reject_by_annotation:
+            data = raw.get_data(picks, start, stop, 'omit')
+        else:
+            data = raw[picks, start:stop][0]
+        data, _ = self._pre_whiten(data, raw.info, picks)
         return self._transform(data)
 
     def _transform_epochs(self, epochs, concatenate):
-        """Aux method
-        """
+        """Aux method."""
         if not hasattr(self, 'mixing_matrix_'):
             raise RuntimeError('No fit available. Please fit ICA')
 
@@ -642,8 +683,7 @@ class ICA(ContainsMixin):
         return sources
 
     def _transform_evoked(self, evoked):
-        """Aux method
-        """
+        """Aux method."""
         if not hasattr(self, 'mixing_matrix_'):
             raise RuntimeError('No fit available. Please first fit ICA')
 
@@ -662,8 +702,19 @@ class ICA(ContainsMixin):
 
         return sources
 
+    def get_components(self):
+        """Get ICA topomap for components as numpy arrays.
+
+        Returns
+        -------
+        components : array, shape (n_channels, n_components)
+            The ICA components (maps).
+        """
+        return np.dot(self.mixing_matrix_[:, :self.n_components_].T,
+                      self.pca_components_[:self.n_components_]).T
+
     def get_sources(self, inst, add_channels=None, start=None, stop=None):
-        """Estimate sources given the unmixing matrix
+        """Estimate sources given the unmixing matrix.
 
         This method will return the sources in the container format passed.
         Typical usecases:
@@ -691,9 +742,9 @@ class ICA(ContainsMixin):
         sources : instance of Raw, Epochs or Evoked
             The ICA sources time series.
         """
-        if isinstance(inst, _BaseRaw):
+        if isinstance(inst, BaseRaw):
             sources = self._sources_as_raw(inst, add_channels, start, stop)
-        elif isinstance(inst, _BaseEpochs):
+        elif isinstance(inst, BaseEpochs):
             sources = self._sources_as_epochs(inst, add_channels, False)
         elif isinstance(inst, Evoked):
             sources = self._sources_as_evoked(inst, add_channels)
@@ -704,8 +755,7 @@ class ICA(ContainsMixin):
         return sources
 
     def _sources_as_raw(self, raw, add_channels, start, stop):
-        """Aux method
-        """
+        """Aux method."""
         # merge copied instance and picked data with sources
         sources = self._transform_raw(raw, start=start, stop=stop)
         if raw.preload:  # get data and temporarily delete
@@ -727,7 +777,7 @@ class ICA(ContainsMixin):
             _, times_ = raw[0, start:stop]
         out._data = data_
         out._times = times_
-        out._filenames = list()
+        out._filenames = [None]
         out.preload = True
 
         # update first and last samples
@@ -743,7 +793,7 @@ class ICA(ContainsMixin):
         return out
 
     def _sources_as_epochs(self, epochs, add_channels, concatenate):
-        """Aux method"""
+        """Aux method."""
         out = epochs.copy()
         sources = self._transform_epochs(epochs, concatenate)
         if add_channels is not None:
@@ -761,8 +811,7 @@ class ICA(ContainsMixin):
         return out
 
     def _sources_as_evoked(self, evoked, add_channels):
-        """Aux method
-        """
+        """Aux method."""
         if add_channels is not None:
             picks = [evoked.ch_names.index(k) for k in add_channels]
         else:
@@ -780,21 +829,18 @@ class ICA(ContainsMixin):
         return out
 
     def _export_info(self, info, container, add_channels):
-        """Aux method
-        """
+        """Aux method."""
         # set channel names and info
         ch_names = []
         ch_info = info['chs'] = []
-        for ii in range(self.n_components_):
-            this_source = 'ICA %03d' % (ii + 1)
-            ch_names.append(this_source)
-            ch_info.append(dict(ch_name=this_source, cal=1,
-                                logno=ii + 1, coil_type=FIFF.FIFFV_COIL_NONE,
-                                kind=FIFF.FIFFV_MISC_CH,
-                                coord_Frame=FIFF.FIFFV_COORD_UNKNOWN,
-                                loc=np.array([0., 0., 0., 1.] * 3, dtype='f4'),
-                                unit=FIFF.FIFF_UNIT_NONE,
-                                range=1.0, scanno=ii + 1, unit_mul=0))
+        for ii, name in enumerate(self._ica_names):
+            ch_names.append(name)
+            ch_info.append(dict(
+                ch_name=name, cal=1, logno=ii + 1,
+                coil_type=FIFF.FIFFV_COIL_NONE, kind=FIFF.FIFFV_MISC_CH,
+                coord_Frame=FIFF.FIFFV_COORD_UNKNOWN, unit=FIFF.FIFF_UNIT_NONE,
+                loc=np.array([0., 0., 0., 1.] * 3, dtype='f4'),
+                range=1.0, scanno=ii + 1, unit_mul=0))
 
         if add_channels is not None:
             # re-append additionally picked ch_names
@@ -810,8 +856,8 @@ class ICA(ContainsMixin):
     @verbose
     def score_sources(self, inst, target=None, score_func='pearsonr',
                       start=None, stop=None, l_freq=None, h_freq=None,
-                      verbose=None):
-        """Assign score to components based on statistic or metric
+                      reject_by_annotation=True, verbose=None):
+        """Assign score to components based on statistic or metric.
 
         Parameters
         ----------
@@ -842,18 +888,25 @@ class ICA(ContainsMixin):
             Low pass frequency.
         h_freq : float
             High pass frequency.
+        reject_by_annotation : bool
+            If True, data annotated as bad will be omitted. Defaults to True.
+
+            .. versionadded:: 0.14.0
+
         verbose : bool, str, int, or None
-            If not None, override default verbose level (see mne.verbose).
-            Defaults to self.verbose.
+            If not None, override default verbose level (see
+            :func:`mne.verbose` and :ref:`Logging documentation <tut_logging>`
+            for more). Defaults to self.verbose.
 
         Returns
         -------
         scores : ndarray
             scores for each source as returned from score_func
         """
-        if isinstance(inst, _BaseRaw):
-            sources = self._transform_raw(inst, start, stop)
-        elif isinstance(inst, _BaseEpochs):
+        if isinstance(inst, BaseRaw):
+            sources = self._transform_raw(inst, start, stop,
+                                          reject_by_annotation)
+        elif isinstance(inst, BaseEpochs):
             sources = self._transform_epochs(inst, concatenate=True)
         elif isinstance(inst, Evoked):
             sources = self._transform_evoked(inst)
@@ -861,7 +914,8 @@ class ICA(ContainsMixin):
             raise ValueError('Input must be of Raw, Epochs or Evoked type')
 
         if target is not None:  # we can have univariate metrics without target
-            target = self._check_target(target, inst, start, stop)
+            target = self._check_target(target, inst, start, stop,
+                                        reject_by_annotation)
 
             if sources.shape[-1] != target.shape[-1]:
                 raise ValueError('Sources and target do not have the same'
@@ -869,7 +923,7 @@ class ICA(ContainsMixin):
             # auto target selection
             if verbose is None:
                 verbose = self.verbose
-            if isinstance(inst, (_BaseRaw, _BaseRaw)):
+            if isinstance(inst, BaseRaw):
                 sources, target = _band_pass_filter(self, sources, target,
                                                     l_freq, h_freq, verbose)
 
@@ -877,18 +931,20 @@ class ICA(ContainsMixin):
 
         return scores
 
-    def _check_target(self, target, inst, start, stop):
-        """Aux Method"""
-        if isinstance(inst, _BaseRaw):
+    def _check_target(self, target, inst, start, stop,
+                      reject_by_annotation=False):
+        """Aux Method."""
+        if isinstance(inst, BaseRaw):
+            reject_by_annotation = 'omit' if reject_by_annotation else None
             start, stop = _check_start_stop(inst, start, stop)
             if hasattr(target, 'ndim'):
                 if target.ndim < 2:
                     target = target.reshape(1, target.shape[-1])
             if isinstance(target, string_types):
                 pick = _get_target_ch(inst, target)
-                target, _ = inst[pick, start:stop]
+                target = inst.get_data(pick, start, stop, reject_by_annotation)
 
-        elif isinstance(inst, _BaseEpochs):
+        elif isinstance(inst, BaseEpochs):
             if isinstance(target, string_types):
                 pick = _get_target_ch(inst, target)
                 target = inst.get_data()[:, pick]
@@ -905,13 +961,13 @@ class ICA(ContainsMixin):
         return target
 
     @verbose
-    def find_bads_ecg(self, inst, ch_name=None, threshold=None,
-                      start=None, stop=None, l_freq=8, h_freq=16,
-                      method='ctps', verbose=None):
-        """Detect ECG related components using correlation
+    def find_bads_ecg(self, inst, ch_name=None, threshold=None, start=None,
+                      stop=None, l_freq=8, h_freq=16, method='ctps',
+                      reject_by_annotation=True, verbose=None):
+        """Detect ECG related components using correlation.
 
-        Note. If no ECG channel is available, routine attempts to create
-        an artificial ECG based on cross-channel averaging.
+        .. note:: If no ECG channel is available, routine attempts to create
+                  an artificial ECG based on cross-channel averaging.
 
         Parameters
         ----------
@@ -945,9 +1001,15 @@ class ICA(ContainsMixin):
             threshold components will be masked and the z-score will
             be recomputed until no supra-threshold component remains.
             Defaults to 'ctps'.
+        reject_by_annotation : bool
+            If True, data annotated as bad will be omitted. Defaults to True.
+
+            .. versionadded:: 0.14.0
+
         verbose : bool, str, int, or None
-            If not None, override default verbose level (see mne.verbose).
-            Defaults to self.verbose.
+            If not None, override default verbose level (see
+            :func:`mne.verbose` and :ref:`Logging documentation <tut_logging>`
+            for more). Defaults to self.verbose.
 
         Returns
         -------
@@ -956,7 +1018,7 @@ class ICA(ContainsMixin):
         scores : np.ndarray of float, shape (``n_components_``)
             The correlation scores.
 
-        See also
+        See Also
         --------
         find_bads_eog
 
@@ -976,24 +1038,24 @@ class ICA(ContainsMixin):
         if idx_ecg is None:
             if verbose is not None:
                 verbose = self.verbose
-            ecg, times = _make_ecg(inst, start, stop, verbose)
-            ch_name = 'ECG-MAG'
+            ecg, times = _make_ecg(inst, start, stop,
+                                   reject_by_annotation=reject_by_annotation,
+                                   verbose=verbose)
         else:
             ecg = inst.ch_names[idx_ecg]
 
-        # some magic we need inevitably ...
-        if inst.ch_names != self.ch_names:
-            extra_picks = pick_types(inst.info, meg=False, ecg=True)
-            ch_names_to_pick = (self.ch_names +
-                                [inst.ch_names[k] for k in extra_picks])
-            inst = inst.copy().pick_channels(ch_names_to_pick)
-
         if method == 'ctps':
             if threshold is None:
                 threshold = 0.25
-            if isinstance(inst, _BaseRaw):
-                sources = self.get_sources(create_ecg_epochs(inst)).get_data()
-            elif isinstance(inst, _BaseEpochs):
+            if isinstance(inst, BaseRaw):
+                sources = self.get_sources(create_ecg_epochs(
+                    inst, ch_name, keep_ecg=False,
+                    reject_by_annotation=reject_by_annotation)).get_data()
+
+                if sources.shape[0] == 0:
+                    warn('No ECG activity detected. Consider changing '
+                         'the input parameters.')
+            elif isinstance(inst, BaseEpochs):
                 sources = self.get_sources(inst).get_data()
             else:
                 raise ValueError('With `ctps` only Raw and Epochs input is '
@@ -1004,27 +1066,27 @@ class ICA(ContainsMixin):
         elif method == 'correlation':
             if threshold is None:
                 threshold = 3.0
-            scores = self.score_sources(inst, target=ecg,
-                                        score_func='pearsonr',
-                                        start=start, stop=stop,
-                                        l_freq=l_freq, h_freq=h_freq,
-                                        verbose=verbose)
+            scores = self.score_sources(
+                inst, target=ecg, score_func='pearsonr', start=start,
+                stop=stop, l_freq=l_freq, h_freq=h_freq,
+                reject_by_annotation=reject_by_annotation, verbose=verbose)
             ecg_idx = find_outliers(scores, threshold=threshold)
         else:
             raise ValueError('Method "%s" not supported.' % method)
         # sort indices by scores
         ecg_idx = ecg_idx[np.abs(scores[ecg_idx]).argsort()[::-1]]
-        if not hasattr(self, 'labels_') or self.labels_ is None:
-            self.labels_ = dict()
+
         self.labels_['ecg'] = list(ecg_idx)
+        if ch_name is None:
+            ch_name = 'ECG-MAG'
         self.labels_['ecg/%s' % ch_name] = list(ecg_idx)
         return self.labels_['ecg'], scores
 
     @verbose
-    def find_bads_eog(self, inst, ch_name=None, threshold=3.0,
-                      start=None, stop=None, l_freq=1, h_freq=10,
-                      verbose=None):
-        """Detect EOG related components using correlation
+    def find_bads_eog(self, inst, ch_name=None, threshold=3.0, start=None,
+                      stop=None, l_freq=1, h_freq=10,
+                      reject_by_annotation=True, verbose=None):
+        """Detect EOG related components using correlation.
 
         Detection is based on Pearson correlation between the
         filtered data and the filtered EOG channel.
@@ -1052,9 +1114,15 @@ class ICA(ContainsMixin):
             Low pass frequency.
         h_freq : float
             High pass frequency.
+        reject_by_annotation : bool
+            If True, data annotated as bad will be omitted. Defaults to True.
+
+            .. versionadded:: 0.14.0
+
         verbose : bool, str, int, or None
-            If not None, override default verbose level (see mne.verbose).
-            Defaults to self.verbose.
+            If not None, override default verbose level (see
+            :func:`mne.verbose` and :ref:`Logging documentation <tut_logging>`
+            for more). Defaults to self.verbose.
 
         Returns
         -------
@@ -1078,21 +1146,15 @@ class ICA(ContainsMixin):
         eog_chs = [inst.ch_names[k] for k in eog_inds]
 
         # some magic we need inevitably ...
-        # get targets befor equalizing
-        targets = [self._check_target(k, inst, start, stop) for k in eog_chs]
-
-        if inst.ch_names != self.ch_names:
-            inst = inst.copy().pick_channels(self.ch_names)
-
-        if not hasattr(self, 'labels_') or self.labels_ is None:
-            self.labels_ = dict()
+        # get targets before equalizing
+        targets = [self._check_target(k, inst, start, stop,
+                                      reject_by_annotation) for k in eog_chs]
 
         for ii, (eog_ch, target) in enumerate(zip(eog_chs, targets)):
-            scores += [self.score_sources(inst, target=target,
-                                          score_func='pearsonr',
-                                          start=start, stop=stop,
-                                          l_freq=l_freq, h_freq=h_freq,
-                                          verbose=verbose)]
+            scores += [self.score_sources(
+                inst, target=target, score_func='pearsonr', start=start,
+                stop=stop, l_freq=l_freq, h_freq=h_freq, verbose=verbose,
+                reject_by_annotation=reject_by_annotation)]
             # pick last scores
             this_idx = find_outliers(scores[-1], threshold=threshold)
             eog_idx += [this_idx]
@@ -1124,11 +1186,12 @@ class ICA(ContainsMixin):
         zero out components, and inverse transform the data.
         This procedure will reconstruct M/EEG signals from which
         the dynamics described by the excluded components is subtracted.
+        The data is processed in place.
 
         Parameters
         ----------
         inst : instance of Raw, Epochs or Evoked
-            The data to be processed.
+            The data to be processed. The instance is modified inplace.
         include : array_like of int.
             The indices referring to columns in the ummixing matrix. The
             components to be kept.
@@ -1145,13 +1208,18 @@ class ICA(ContainsMixin):
         stop : int | float | None
             Last sample to not include. If float, data will be interpreted as
             time in seconds. If None, data will be used to the last sample.
+
+        Returns
+        -------
+        out : instance of Raw, Epochs or Evoked
+            The processed data.
         """
-        if isinstance(inst, _BaseRaw):
+        if isinstance(inst, BaseRaw):
             out = self._apply_raw(raw=inst, include=include,
                                   exclude=exclude,
                                   n_pca_components=n_pca_components,
                                   start=start, stop=stop)
-        elif isinstance(inst, _BaseEpochs):
+        elif isinstance(inst, BaseEpochs):
             out = self._apply_epochs(epochs=inst, include=include,
                                      exclude=exclude,
                                      n_pca_components=n_pca_components)
@@ -1165,7 +1233,7 @@ class ICA(ContainsMixin):
         return out
 
     def _apply_raw(self, raw, include, exclude, n_pca_components, start, stop):
-        """Aux method"""
+        """Aux method."""
         if not raw.preload:
             raise ValueError('Raw data must be preloaded to apply ICA')
 
@@ -1191,7 +1259,7 @@ class ICA(ContainsMixin):
         return raw
 
     def _apply_epochs(self, epochs, include, exclude, n_pca_components):
-
+        """Aux method."""
         if not epochs.preload:
             raise ValueError('Epochs must be preloaded to apply ICA')
 
@@ -1222,7 +1290,7 @@ class ICA(ContainsMixin):
         return epochs
 
     def _apply_evoked(self, evoked, include, exclude, n_pca_components):
-
+        """Aux method."""
         picks = pick_types(evoked.info, meg=False, ref_meg=False,
                            include=self.ch_names,
                            exclude='bads')
@@ -1249,8 +1317,7 @@ class ICA(ContainsMixin):
         return evoked
 
     def _pick_sources(self, data, include, exclude):
-        """Aux function"""
-        fast_dot = _get_fast_dot()
+        """Aux function."""
         if exclude is None:
             exclude = self.exclude
         else:
@@ -1292,7 +1359,7 @@ class ICA(ContainsMixin):
 
         proj_mat = np.dot(mixing[:, sel_keep], unmixing[sel_keep, :])
 
-        data = fast_dot(proj_mat, data)
+        data = np.dot(proj_mat, data)
 
         if self.pca_mean_ is not None:
             data += self.pca_mean_[:, None]
@@ -1301,7 +1368,7 @@ class ICA(ContainsMixin):
         if self.noise_cov is None:  # revert standardization
             data *= self._pre_whitener
         else:
-            data = fast_dot(linalg.pinv(self._pre_whitener), data)
+            data = np.dot(linalg.pinv(self._pre_whitener, cond=1e-14), data)
 
         return data
 
@@ -1325,15 +1392,16 @@ class ICA(ContainsMixin):
 
         try:
             _write_ica(fid, self)
+            end_file(fid)
         except Exception:
+            end_file(fid)
             os.remove(fname)
             raise
-        end_file(fid)
 
         return self
 
     def copy(self):
-        """Copy the ICA object
+        """Copy the ICA object.
 
         Returns
         -------
@@ -1368,14 +1436,15 @@ class ICA(ContainsMixin):
 
     @copy_function_doc_to_method_doc(plot_ica_sources)
     def plot_sources(self, inst, picks=None, exclude=None, start=None,
-                     stop=None, title=None, show=True, block=False):
+                     stop=None, title=None, show=True, block=False,
+                     show_first_samp=False):
         return plot_ica_sources(self, inst=inst, picks=picks, exclude=exclude,
                                 start=start, stop=stop, title=title, show=show,
-                                block=block)
+                                block=block, show_first_samp=show_first_samp)
 
     @copy_function_doc_to_method_doc(plot_ica_scores)
     def plot_scores(self, scores, exclude=None, labels=None, axhline=None,
-                    title='ICA component scores', figsize=(12, 6),
+                    title='ICA component scores', figsize=None,
                     show=True):
         return plot_ica_scores(
             ica=self, scores=scores, exclude=exclude, labels=labels,
@@ -1501,7 +1570,7 @@ class ICA(ContainsMixin):
 
     @verbose
     def _check_n_pca_components(self, _n_pca_comp, verbose=None):
-        """Aux function"""
+        """Aux function."""
         if isinstance(_n_pca_comp, float):
             _n_pca_comp = ((self.pca_explained_variance_ /
                            self.pca_explained_variance_.sum()).cumsum() <=
@@ -1517,16 +1586,24 @@ class ICA(ContainsMixin):
 
 
 def _check_start_stop(raw, start, stop):
-    """Aux function"""
-    return [c if (isinstance(c, int) or c is None) else
-            raw.time_as_index(c)[0] for c in (start, stop)]
+    """Aux function."""
+    out = list()
+    for st in (start, stop):
+        if st is None:
+            out.append(st)
+        else:
+            try:
+                out.append(_ensure_int(st))
+            except TypeError:  # not int-like
+                out.append(raw.time_as_index(st)[0])
+    return out
 
 
 @verbose
 def ica_find_ecg_events(raw, ecg_source, event_id=999,
                         tstart=0.0, l_freq=5, h_freq=35, qrs_threshold='auto',
                         verbose=None):
-    """Find ECG peaks from one selected ICA source
+    """Find ECG peaks from one selected ICA source.
 
     Parameters
     ----------
@@ -1548,7 +1625,8 @@ def ica_find_ecg_events(raw, ecg_source, event_id=999,
         automatically choose the threshold that generates a reasonable
         number of heartbeats (40-160 beats / min).
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -1577,7 +1655,7 @@ def ica_find_ecg_events(raw, ecg_source, event_id=999,
 @verbose
 def ica_find_eog_events(raw, eog_source=None, event_id=998, l_freq=1,
                         h_freq=10, verbose=None):
-    """Locate EOG artifacts from one selected ICA source
+    """Locate EOG artifacts from one selected ICA source.
 
     Parameters
     ----------
@@ -1592,7 +1670,8 @@ def ica_find_eog_events(raw, eog_source=None, event_id=998, l_freq=1,
     h_freq : float
         High cut-off frequency in Hz.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -1607,8 +1686,7 @@ def ica_find_eog_events(raw, eog_source=None, event_id=998, l_freq=1,
 
 
 def _get_target_ch(container, target):
-    """Aux function"""
-
+    """Aux function."""
     # auto target selection
     picks = pick_channels(container.ch_names, include=[target])
     ref_picks = pick_types(container.info, meg=False, eeg=False, ref_meg=True)
@@ -1622,7 +1700,7 @@ def _get_target_ch(container, target):
 
 
 def _find_sources(sources, target, score_func):
-    """Aux function"""
+    """Aux function."""
     if isinstance(score_func, string_types):
         score_func = get_score_funcs().get(score_func, score_func)
 
@@ -1636,7 +1714,7 @@ def _find_sources(sources, target, score_func):
 
 
 def _ica_explained_variance(ica, inst, normalize=False):
-    """Checks variance accounted for by each component in supplied data.
+    """Check variance accounted for by each component in supplied data.
 
     Parameters
     ----------
@@ -1655,14 +1733,14 @@ def _ica_explained_variance(ica, inst, normalize=False):
     # check if ica is ICA and whether inst is Raw or Epochs
     if not isinstance(ica, ICA):
         raise TypeError('first argument must be an instance of ICA.')
-    if not isinstance(inst, (_BaseRaw, _BaseEpochs, Evoked)):
+    if not isinstance(inst, (BaseRaw, BaseEpochs, Evoked)):
         raise TypeError('second argument must an instance of either Raw, '
                         'Epochs or Evoked.')
 
     source_data = _get_inst_data(ica.get_sources(inst))
 
     # if epochs - reshape to channels x timesamples
-    if isinstance(inst, _BaseEpochs):
+    if isinstance(inst, BaseEpochs):
         n_epochs, n_chan, n_samp = source_data.shape
         source_data = source_data.transpose(1, 0, 2).reshape(
             (n_chan, n_epochs * n_samp))
@@ -1690,20 +1768,19 @@ def _sort_components(ica, order, copy=True):
         order = list(order)
     if ica.exclude:
         ica.exclude = [order.index(ic) for ic in ica.exclude]
-    if hasattr(ica, 'labels_'):
-        for k in ica.labels_.keys():
-            ica.labels_[k] = [order.index(ic) for ic in ica.labels_[k]]
+    for k in ica.labels_.keys():
+        ica.labels_[k] = [order.index(ic) for ic in ica.labels_[k]]
 
     return ica
 
 
 def _serialize(dict_, outer_sep=';', inner_sep=':'):
-    """Aux function"""
+    """Aux function."""
     s = []
     for key, value in dict_.items():
         if callable(value):
             value = value.__name__
-        elif isinstance(value, int):
+        elif isinstance(value, Integral):
             value = int(value)
         elif isinstance(value, dict):
             # py35 json does not support numpy int64
@@ -1723,7 +1800,7 @@ def _serialize(dict_, outer_sep=';', inner_sep=':'):
 
 
 def _deserialize(str_, outer_sep=';', inner_sep=':'):
-    """Aux Function"""
+    """Aux Function."""
     out = {}
     for mapping in str_.split(outer_sep):
         k, v = mapping.split(inner_sep, 1)
@@ -1734,7 +1811,7 @@ def _deserialize(str_, outer_sep=';', inner_sep=':'):
 
 
 def _write_ica(fid, ica):
-    """Write an ICA object
+    """Write an ICA object.
 
     Parameters
     ----------
@@ -1835,8 +1912,6 @@ def read_ica(fname):
                     'Functionality requiring the info won\'t be'
                     ' available.')
         info = None
-    else:
-        info['filename'] = fname
 
     ica_data = dir_tree_find(tree, FIFF.FIFFB_MNE_ICA)
     if len(ica_data) == 0:
@@ -1899,6 +1974,7 @@ def read_ica(fname):
     ica.pca_mean_ = f(pca_mean)
     ica.pca_components_ = f(pca_components)
     ica.n_components_ = unmixing_matrix.shape[0]
+    ica._update_ica_names()
     ica.pca_explained_variance_ = f(pca_explained_variance)
     ica.unmixing_matrix_ = f(unmixing_matrix)
     ica.mixing_matrix_ = linalg.pinv(ica.unmixing_matrix_)
@@ -1907,7 +1983,9 @@ def read_ica(fname):
     if 'n_samples_' in ica_misc:
         ica.n_samples_ = ica_misc['n_samples_']
     if 'labels_' in ica_misc:
-        ica.labels_ = ica_misc['labels_']
+        labels_ = ica_misc['labels_']
+        if labels_ is not None:
+            ica.labels_ = labels_
     if 'method' in ica_misc:
         ica.method = ica_misc['method']
 
@@ -1923,7 +2001,7 @@ def _detect_artifacts(ica, raw, start_find, stop_find, ecg_ch, ecg_score_func,
                       ecg_criterion, eog_ch, eog_score_func, eog_criterion,
                       skew_criterion, kurt_criterion, var_criterion,
                       add_nodes):
-    """Aux Function"""
+    """Aux Function."""
     from scipy import stats
 
     nodes = []
@@ -1958,7 +2036,7 @@ def _detect_artifacts(ica, raw, start_find, stop_find, ecg_ch, ecg_score_func,
         else:
             found = list(np.atleast_1d(abs(scores).argsort()[node.criterion]))
 
-        case = (len(found), 's' if len(found) > 1 else '', node.name)
+        case = (len(found), _pl(found), node.name)
         logger.info('    found %s artifact%s by %s' % case)
         ica.exclude += found
 
@@ -1978,7 +2056,7 @@ def run_ica(raw, n_components, max_pca_components=100,
             ecg_criterion=0.1, eog_ch=None, eog_score_func='pearsonr',
             eog_criterion=0.1, skew_criterion=-1, kurt_criterion=-1,
             var_criterion=0, add_nodes=None, verbose=None):
-    """Run ICA decomposition on raw data and identify artifact sources
+    """Run ICA decomposition on raw data and identify artifact sources.
 
     This function implements an automated artifact removal work flow.
 
@@ -2100,7 +2178,8 @@ def run_ica(raw, n_components, max_pca_components=100,
             add_nodes=('ECG phase lock', ECG 01', my_phase_lock_function, 0.5)
 
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -2130,37 +2209,26 @@ def run_ica(raw, n_components, max_pca_components=100,
 
 @verbose
 def _band_pass_filter(ica, sources, target, l_freq, h_freq, verbose=None):
+    """Optionally band-pass filter the data."""
     if l_freq is not None and h_freq is not None:
         logger.info('... filtering ICA sources')
-        # use fft, here, steeper is better here.
-        sources = band_pass_filter(sources, ica.info['sfreq'],
-                                   l_freq, h_freq, method='fft',
-                                   verbose=verbose)
+        # use FIR here, steeper is better
+        kw = dict(phase='zero-double', filter_length='10s', fir_window='hann',
+                  l_trans_bandwidth=0.5, h_trans_bandwidth=0.5,
+                  fir_design='firwin2')
+        sources = filter_data(sources, ica.info['sfreq'], l_freq, h_freq, **kw)
         logger.info('... filtering target')
-        target = band_pass_filter(target, ica.info['sfreq'],
-                                  l_freq, h_freq, method='fft',
-                                  verbose=verbose)
+        target = filter_data(target, ica.info['sfreq'], l_freq, h_freq, **kw)
     elif l_freq is not None or h_freq is not None:
         raise ValueError('Must specify both pass bands')
-
     return sources, target
 
 
 # #############################################################################
 # CORRMAP
 
-def _get_ica_map(ica, components=None):
-    """Get ICA topomap for components"""
-    fast_dot = _get_fast_dot()
-    if components is None:
-        components = list(range(ica.n_components_))
-    maps = fast_dot(ica.mixing_matrix_[:, components].T,
-                    ica.pca_components_[:ica.n_components_])
-    return maps
-
-
 def _find_max_corrs(all_maps, target, threshold):
-    """Compute correlations between template and target components"""
+    """Compute correlations between template and target components."""
     all_corrs = [compute_corr(target, subj.T) for subj in all_maps]
     abs_corrs = [np.abs(a) for a in all_corrs]
     corr_polarities = [np.sign(a) for a in all_corrs]
@@ -2199,8 +2267,8 @@ def _find_max_corrs(all_maps, target, threshold):
 
 
 def _plot_corrmap(data, subjs, indices, ch_type, ica, label, show, outlines,
-                  layout, cmap, contours, template=True):
-    """Customized ica.plot_components for corrmap"""
+                  layout, cmap, contours, template=False):
+    """Customize ica.plot_components for corrmap."""
     if not template:
         title = 'Detected components'
         if label is not None:
@@ -2237,7 +2305,7 @@ def _plot_corrmap(data, subjs, indices, ch_type, ica, label, show, outlines,
         from ..channels.layout import _merge_grad_data
     for ii, data_, ax, subject, idx in zip(picks, data, axes, subjs, indices):
         if template:
-            ttl = 'Subj. {0}, IC {1}'.format(subject, idx)
+            ttl = 'Subj. {0}, {1}'.format(subject, ica._ica_names[idx])
             ax.set_title(ttl, fontsize=12)
         data_ = _merge_grad_data(data_) if merge_grads else data_
         vmin_, vmax_ = _setup_vmin_vmax(data_, None, None)
@@ -2265,8 +2333,8 @@ def corrmap(icas, template, threshold="auto", label=None, ch_type="eeg",
     specific ICs across subjects.
 
     The specific procedure consists of two iterations. In a first step, the
-    maps best correlating with the template are identified. In the step, the
-    analysis is repeated with the mean of the maps identified in the first
+    maps best correlating with the template are identified. In the next step,
+    the analysis is repeated with the mean of the maps identified in the first
     stage.
 
     Run with `plot` and `show` set to `True` and `label=False` to find
@@ -2314,7 +2382,8 @@ def corrmap(icas, template, threshold="auto", label=None, ch_type="eeg",
     show : bool
         Show figures if True.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
     outlines : 'head' | dict | None
         The outlines to be drawn. If 'head', a head scheme will be drawn. If
         dict, each key refers to a tuple of x and y positions. The values in
@@ -2332,8 +2401,12 @@ def corrmap(icas, template, threshold="auto", label=None, ch_type="eeg",
         Add markers for sensor locations to the plot. Accepts matplotlib plot
         format string (e.g., 'r+' for red plusses). If True, a circle will be
         used (via .add_artist). Defaults to True.
-    contours : int | False | None
+    contours : int | array of float
         The number of contour lines to draw. If 0, no contours will be drawn.
+        When an integer, matplotlib ticker locator is used to find suitable
+        values for the contour thresholds (may sometimes be inaccurate, use
+        array for accuracy). If an array, the values represent the levels for
+        the contours. Defaults to 6.
     cmap : None | matplotlib colormap
         Colormap for the plot. If ``None``, defaults to 'Reds_r' for norm data,
         otherwise to 'RdBu_r'.
@@ -2351,7 +2424,7 @@ def corrmap(icas, template, threshold="auto", label=None, ch_type="eeg",
     if threshold == 'auto':
         threshold = np.arange(60, 95, dtype=np.float64) / 100.
 
-    all_maps = [_get_ica_map(ica) for ica in icas]
+    all_maps = [ica.get_components().T for ica in icas]
 
     # check if template is an index to one IC in one ICA object, or an array
     if len(template) == 2:
@@ -2412,8 +2485,6 @@ def corrmap(icas, template, threshold="auto", label=None, ch_type="eeg",
         logger.info('Displaying selected ICs per subject.')
 
     for ii, (ica, max_corr) in enumerate(zip(icas, mx)):
-        if (label is not None) and (not hasattr(ica, 'labels_')):
-            ica.labels_ = dict()
         if len(max_corr) > 0:
             if isinstance(max_corr[0], np.ndarray):
                 max_corr = max_corr[0]
@@ -2421,7 +2492,7 @@ def corrmap(icas, template, threshold="auto", label=None, ch_type="eeg",
                 ica.labels_[label] = list(set(list(max_corr) +
                                           ica.labels_.get(label, list())))
             if plot is True:
-                allmaps.extend(_get_ica_map(ica, components=max_corr))
+                allmaps.extend(ica.get_components()[:, max_corr].T)
                 subjs.extend([ii] * len(max_corr))
                 indices.extend(max_corr)
         else:
diff --git a/mne/preprocessing/infomax_.py b/mne/preprocessing/infomax_.py
index 8b5b328..53dd8ec 100644
--- a/mne/preprocessing/infomax_.py
+++ b/mne/preprocessing/infomax_.py
@@ -28,8 +28,10 @@ def infomax(data, weights=None, l_rate=None, block=None, w_change=1e-12,
         Defaults to None, which means the identity matrix is used.
     l_rate : float
         This quantity indicates the relative size of the change in weights.
+        Defaults to ``0.01 / log(n_features ** 2)``.
+
         .. note:: Smaller learning rates will slow down the ICA procedure.
-        Defaults to 0.01 / log(n_features ** 2).
+
     block : int
         The block size of randomly chosen data segments.
         Defaults to floor(sqrt(n_times / 3.)).
@@ -40,8 +42,7 @@ def infomax(data, weights=None, l_rate=None, block=None, w_change=1e-12,
         Defaults to 60.0.
     anneal_step : float
         The factor by which the learning rate will be reduced once
-        ``anneal_deg`` is exceeded:
-            l_rate *= anneal_step
+        ``anneal_deg`` is exceeded: ``l_rate *= anneal_step.``
         Defaults to 0.9.
     extended : bool
         Whether to use the extended Infomax algorithm or not.
@@ -72,20 +73,18 @@ def infomax(data, weights=None, l_rate=None, block=None, w_change=1e-12,
     blowup_fac : float
         The factor by which the learning rate will be reduced if the difference
         between two successive estimations of the unmixing matrix exceededs
-        ``blowup``:
-            l_rate *= blowup_fac
-        Defaults to 0.5.
+        ``blowup``: ``l_rate *= blowup_fac``. Defaults to 0.5.
     n_small_angle : int | None
         The maximum number of allowed steps in which the angle between two
         successive estimations of the unmixing matrix is less than
         ``anneal_deg``. If None, this parameter is not taken into account to
-        stop the iterations.
-        Defaults to 20.
+        stop the iterations. Defaults to 20.
     use_bias : bool
         This quantity indicates if the bias should be computed.
         Defaults to True.
     verbose : bool, str, int, or None
-        If not None, override default verbosity level (see mne.verbose).
+        If not None, override default verbosity level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -94,12 +93,12 @@ def infomax(data, weights=None, l_rate=None, block=None, w_change=1e-12,
 
     References
     ----------
-    [1] A. J. Bell, T. J. Sejnowski. An information-maximization approach to
-        blind separation and blind deconvolution. Neural Computation, 7(6),
-        1129-1159, 1995.
-    [2] T. W. Lee, M. Girolami, T. J. Sejnowski. Independent component analysis
-        using an extended infomax algorithm for mixed subgaussian and
-        supergaussian sources. Neural Computation, 11(2), 417-441, 1999.
+    .. [1] A. J. Bell, T. J. Sejnowski. An information-maximization approach to
+           blind separation and blind deconvolution. Neural Computation, 7(6),
+           1129-1159, 1995.
+    .. [2] T. W. Lee, M. Girolami, T. J. Sejnowski. Independent component
+           analysis using an extended infomax algorithm for mixed subgaussian
+           and supergaussian sources. Neural Computation, 11(2), 417-441, 1999.
     """
     from scipy.stats import kurtosis
     rng = check_random_state(random_state)
diff --git a/mne/preprocessing/maxfilter.py b/mne/preprocessing/maxfilter.py
index 6b0fb63..3e3117c 100644
--- a/mne/preprocessing/maxfilter.py
+++ b/mne/preprocessing/maxfilter.py
@@ -15,6 +15,7 @@ from ..externals.six.moves import map
 
 
 def _mxwarn(msg):
+    """Warn about a bug."""
     warn('Possible MaxFilter bug: %s, more info: '
          'http://imaging.mrc-cbu.cam.ac.uk/meg/maxbugs' % msg)
 
@@ -27,100 +28,75 @@ def apply_maxfilter(in_fname, out_fname, origin=None, frame='device',
                     mv_hpistep=None, mv_hpisubt=None, mv_hpicons=True,
                     linefreq=None, cal=None, ctc=None, mx_args='',
                     overwrite=True, verbose=None):
+    """Apply NeuroMag MaxFilter to raw data.
 
-    """ Apply NeuroMag MaxFilter to raw data.
-
-        Needs Maxfilter license, maxfilter has to be in PATH
+    Needs Maxfilter license, maxfilter has to be in PATH.
 
     Parameters
     ----------
     in_fname : string
         Input file name
-
     out_fname : string
         Output file name
-
     origin : array-like or string
         Head origin in mm. If None it will be estimated from headshape points.
-
     frame : string ('device' or 'head')
         Coordinate frame for head center
-
     bad : string, list (or None)
         List of static bad channels. Can be a list with channel names, or a
         string with channels (names or logical channel numbers)
-
     autobad : string ('on', 'off', 'n')
         Sets automated bad channel detection on or off
-
     skip : string or a list of float-tuples (or None)
         Skips raw data sequences, time intervals pairs in sec,
         e.g.: 0 30 120 150
-
     force : bool
         Ignore program warnings
-
     st : bool
         Apply the time-domain MaxST extension
-
     st_buflen : float
         MaxSt buffer length in sec (disabled if st is False)
-
     st_corr : float
         MaxSt subspace correlation limit (disabled if st is False)
-
     mv_trans : string (filename or 'default') (or None)
         Transforms the data into the coil definitions of in_fname, or into the
         default frame (None: don't use option)
-
     mv_comp : bool (or 'inter')
         Estimates and compensates head movements in continuous raw data
-
     mv_headpos : bool
         Estimates and stores head position parameters, but does not compensate
         movements (disabled if mv_comp is False)
-
     mv_hp : string (or None)
         Stores head position data in an ascii file
         (disabled if mv_comp is False)
-
     mv_hpistep : float (or None)
         Sets head position update interval in ms (disabled if mv_comp is False)
-
     mv_hpisubt : string ('amp', 'base', 'off') (or None)
         Subtracts hpi signals: sine amplitudes, amp + baseline, or switch off
         (disabled if mv_comp is False)
-
     mv_hpicons : bool
         Check initial consistency isotrak vs hpifit
         (disabled if mv_comp is False)
-
     linefreq : int (50, 60) (or None)
         Sets the basic line interference frequency (50 or 60 Hz)
         (None: do not use line filter)
-
     cal : string
         Path to calibration file
-
     ctc : string
         Path to Cross-talk compensation file
-
     mx_args : string
         Additional command line arguments to pass to MaxFilter
-
     overwrite : bool
         Overwrite output file if it already exists
-
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
-
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
     origin: string
         Head origin in selected coordinate frame
     """
-
     # check for possible maxfilter bugs
     if mv_trans is not None and mv_comp:
         _mxwarn("Don't use '-trans' with head-movement compensation "
@@ -136,7 +112,7 @@ def apply_maxfilter(in_fname, out_fname, origin=None, frame='device',
     # determine the head origin if necessary
     if origin is None:
         logger.info('Estimating head origin from headshape points..')
-        raw = read_raw_fif(in_fname, add_eeg_ref=False)
+        raw = read_raw_fif(in_fname)
         r, o_head, o_dev = fit_sphere_to_headshape(raw.info, units='mm')
         raw.close()
         logger.info('[done]')
diff --git a/mne/preprocessing/maxwell.py b/mne/preprocessing/maxwell.py
index 6071485..3bf42aa 100644
--- a/mne/preprocessing/maxwell.py
+++ b/mne/preprocessing/maxwell.py
@@ -17,16 +17,19 @@ from .. import __version__
 from ..bem import _check_origin
 from ..chpi import quat_to_rot, rot_to_quat
 from ..transforms import (_str_to_frame, _get_trans, Transform, apply_trans,
-                          _find_vector_rotation)
+                          _find_vector_rotation, _cart_to_sph, _get_n_moments,
+                          _sph_to_cart_partials, _deg_ord_idx,
+                          _sh_complex_to_real, _sh_real_to_complex, _sh_negate)
 from ..forward import _concatenate_coils, _prep_meg_channels, _create_meg_coils
 from ..surface import _normalize_vectors
 from ..io.constants import FIFF
+from ..io.meas_info import _simplify_info
 from ..io.proc_history import _read_ctc
 from ..io.write import _generate_meas_id, _date_now
-from ..io import _loc_to_coil_trans, _BaseRaw
-from ..io.pick import pick_types, pick_info, pick_channels
-from ..utils import verbose, logger, _clean_names, warn, _time_mask
-from ..fixes import _get_args, _safe_svd
+from ..io import _loc_to_coil_trans, BaseRaw
+from ..io.pick import pick_types, pick_info
+from ..utils import verbose, logger, _clean_names, warn, _time_mask, _pl
+from ..fixes import _get_args, _safe_svd, _get_sph_harm
 from ..externals.six import string_types
 from ..channels.channels import _get_T1T2_mag_inds
 
@@ -43,16 +46,14 @@ def maxwell_filter(raw, origin='auto', int_order=8, ext_order=3,
                    regularize='in', ignore_ref=False, bad_condition='error',
                    head_pos=None, st_fixed=True, st_only=False, mag_scale=100.,
                    verbose=None):
-    """Apply Maxwell filter to data using multipole moments
+    u"""Apply Maxwell filter to data using multipole moments.
 
     .. warning:: Automatic bad channel detection is not currently implemented.
                  It is critical to mark bad channels before running Maxwell
-                 filtering, so data should be inspected and marked accordingly
-                 prior to running this algorithm.
+                 filtering to prevent artifact spreading.
 
-    .. warning:: Not all features of Elekta MaxFilter™ are currently
-                 implemented (see Notes). Maxwell filtering in mne-python
-                 is not designed for clinical use.
+    .. warning:: Maxwell filtering in MNE is not designed or certified
+                 for clinical use.
 
     Parameters
     ----------
@@ -149,7 +150,8 @@ def maxwell_filter(raw, origin='auto', int_order=8, ext_order=3,
         .. versionadded:: 0.13
 
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose)
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -158,8 +160,9 @@ def maxwell_filter(raw, origin='auto', int_order=8, ext_order=3,
 
     See Also
     --------
-    mne.epochs.average_movements
+    mne.chpi.filter_chpi
     mne.chpi.read_head_pos
+    mne.epochs.average_movements
 
     Notes
     -----
@@ -169,34 +172,49 @@ def maxwell_filter(raw, origin='auto', int_order=8, ext_order=3,
     permission from Jussi Nurminen. These algorithms are based on work
     from [1]_ and [2]_.
 
-    Compared to Elekta's MaxFilter™ software, our Maxwell filtering
-    algorithm currently provides the following features:
-
-        * Bad channel reconstruction
-        * Cross-talk cancellation
-        * Fine calibration correction
-        * tSSS
-        * Coordinate frame translation
-        * Regularization of internal components using information theory
-        * Raw movement compensation
-          (using head positions estimated by MaxFilter)
-        * cHPI subtraction (see :func:`mne.chpi.filter_chpi`)
-
-    The following features are not yet implemented:
-
-        * **Not certified for clinical use**
-        * Automatic bad channel detection
-        * Head position estimation
-
-    Our algorithm has the following enhancements:
-
-        * Double floating point precision
-        * Handling of 3D (in addition to 1D) fine calibration files
-        * Automated processing of split (-1.fif) and concatenated files
-        * Epoch-based movement compensation as described in [1]_ through
-          :func:`mne.epochs.average_movements`
-        * **Experimental** processing of data from (un-compensated)
-          non-Elekta systems
+    .. note:: This code may use multiple CPU cores, see the
+              :ref:`FAQ <faq_cpu>` for more information.
+
+    Compared to Elekta's MaxFilter™ software, the MNE Maxwell filtering
+    routines currently provide the following features:
+
+    +-----------------------------------------------------------------------------+-----+-----------+
+    | Feature                                                                     | MNE | MaxFilter |
+    +=============================================================================+=====+===========+
+    | Maxwell filtering software shielding                                        | X   | X         |
+    +-----------------------------------------------------------------------------+-----+-----------+
+    | Bad channel reconstruction                                                  | X   | X         |
+    +-----------------------------------------------------------------------------+-----+-----------+
+    | Cross-talk cancellation                                                     | X   | X         |
+    +-----------------------------------------------------------------------------+-----+-----------+
+    | Fine calibration correction (1D)                                            | X   | X         |
+    +-----------------------------------------------------------------------------+-----+-----------+
+    | Fine calibration correction (3D)                                            | X   |           |
+    +-----------------------------------------------------------------------------+-----+-----------+
+    | Spatio-temporal SSS (tSSS)                                                  | X   | X         |
+    +-----------------------------------------------------------------------------+-----+-----------+
+    | Coordinate frame translation                                                | X   | X         |
+    +-----------------------------------------------------------------------------+-----+-----------+
+    | Regularization using information theory                                     | X   | X         |
+    +-----------------------------------------------------------------------------+-----+-----------+
+    | Movement compensation (raw)                                                 | X   | X         |
+    +-----------------------------------------------------------------------------+-----+-----------+
+    | Movement compensation (:func:`epochs <mne.epochs.average_movements>`)       | X   |           |
+    +-----------------------------------------------------------------------------+-----+-----------+
+    | :func:`cHPI subtraction <mne.chpi.filter_chpi>`                             | X   | X         |
+    +-----------------------------------------------------------------------------+-----+-----------+
+    | Double floating point precision                                             | X   |           |
+    +-----------------------------------------------------------------------------+-----+-----------+
+    | Seamless processing of split (``-1.fif``) and concatenated files            | X   |           |
+    +-----------------------------------------------------------------------------+-----+-----------+
+    | Certified for clinical use                                                  |     | X         |
+    +-----------------------------------------------------------------------------+-----+-----------+
+    | Automatic bad channel detection                                             |     | X         |
+    +-----------------------------------------------------------------------------+-----+-----------+
+    | Head position estimation                                                    |     | X         |
+    +-----------------------------------------------------------------------------+-----+-----------+
+
+    Epoch-based movement compensation is described in [1]_.
 
     Use of Maxwell filtering routines with non-Elekta systems is currently
     **experimental**. Worse results for non-Elekta systems are expected due
@@ -222,6 +240,11 @@ def maxwell_filter(raw, origin='auto', int_order=8, ext_order=3,
               These patents likely preclude the use of Maxwell filtering code
               in commercial applications. Consult a lawyer if necessary.
 
+    Currently, in order to perform Maxwell filtering, the raw data must not
+    have any projectors applied. During Maxwell filtering, the spatial
+    structure of the data is modified, so projectors are discarded (unless
+    in ``st_only=True`` mode).
+
     References
     ----------
     .. [1] Taulu S. and Kajola M. "Presentation of electromagnetic
@@ -235,7 +258,7 @@ def maxwell_filter(raw, origin='auto', int_order=8, ext_order=3,
            Physics in Medicine and Biology, vol. 51, pp. 1759-1768, 2006.
 
            http://lib.tkk.fi/Diss/2008/isbn9789512295654/article3.pdf
-    """
+    """  # noqa: E501
     # There are an absurd number of different possible notations for spherical
     # coordinates, which confounds the notation for spherical harmonics.  Here,
     # we purposefully stay away from shorthand notation in both and use
@@ -244,7 +267,7 @@ def maxwell_filter(raw, origin='auto', int_order=8, ext_order=3,
     # Our code follows the same standard that ``scipy`` uses for ``sph_harm``.
 
     # triage inputs ASAP to avoid late-thrown errors
-    if not isinstance(raw, _BaseRaw):
+    if not isinstance(raw, BaseRaw):
         raise TypeError('raw must be Raw, not %s' % type(raw))
     _check_usable(raw)
     _check_regularize(regularize)
@@ -291,7 +314,9 @@ def maxwell_filter(raw, origin='auto', int_order=8, ext_order=3,
     raw_sss, pos_picks = _copy_preload_add_channels(
         raw, add_channels=add_channels)
     del raw
-    _remove_meg_projs(raw_sss)  # remove MEG projectors, they won't apply now
+    if not st_only:
+        # remove MEG projectors, they won't apply now
+        _remove_meg_projs(raw_sss)
     info = raw_sss.info
     meg_picks, mag_picks, grad_picks, good_picks, mag_or_fine = \
         _get_mf_picks(info, int_order, ext_order, ignore_ref)
@@ -321,6 +346,10 @@ def maxwell_filter(raw, origin='auto', int_order=8, ext_order=3,
         sss_ctc = _read_ctc(cross_talk)
         ctc_chs = sss_ctc['proj_items_chs']
         meg_ch_names = [info['ch_names'][p] for p in meg_picks]
+        # checking for extra space ambiguity in channel names
+        # between old and new fif files
+        if meg_ch_names[0] not in ctc_chs:
+            ctc_chs = _clean_names(ctc_chs, remove_whitespace=True)
         missing = sorted(list(set(meg_ch_names) - set(ctc_chs)))
         if len(missing) != 0:
             raise RuntimeError('Missing MEG channels in cross-talk matrix:\n%s'
@@ -328,9 +357,8 @@ def maxwell_filter(raw, origin='auto', int_order=8, ext_order=3,
         missing = sorted(list(set(ctc_chs) - set(meg_ch_names)))
         if len(missing) > 0:
             warn('Not all cross-talk channels in raw:\n%s' % missing)
-        ctc_picks = pick_channels(ctc_chs,
-                                  [info['ch_names'][c]
-                                   for c in meg_picks[good_picks]])
+        ctc_picks = [ctc_chs.index(info['ch_names'][c])
+                     for c in meg_picks[good_picks]]
         assert len(ctc_picks) == len(good_picks)  # otherwise we errored
         ctc = sss_ctc['decoupler'][ctc_picks][:, ctc_picks]
         # I have no idea why, but MF transposes this for storage..
@@ -383,7 +411,8 @@ def maxwell_filter(raw, origin='auto', int_order=8, ext_order=3,
             logger.info('    Spatiotemporal window did not fit evenly into '
                         'raw object. The final %0.2f seconds were lumped '
                         'onto the previous window.'
-                        % ((read_lims[-1] - read_lims[-2]) / info['sfreq'],))
+                        % ((read_lims[-1] - read_lims[-2] - st_duration) /
+                           info['sfreq'],))
     assert len(read_lims) >= 2
     assert read_lims[0] == 0 and read_lims[-1] == len(raw_sss.times)
 
@@ -414,9 +443,9 @@ def maxwell_filter(raw, origin='auto', int_order=8, ext_order=3,
     reg_moments_0 = reg_moments.copy()
     # Loop through buffer windows of data
     n_sig = int(np.floor(np.log10(max(len(read_lims), 0)))) + 1
-    pl = 's' if len(read_lims) != 2 else ''
     logger.info('    Processing %s data chunk%s of (at least) %0.1f sec'
-                % (len(read_lims) - 1, pl, st_duration / info['sfreq']))
+                % (len(read_lims) - 1, _pl(read_lims),
+                   st_duration / info['sfreq']))
     for ii, (start, stop) in enumerate(zip(read_lims[:-1], read_lims[1:])):
         rel_times = raw_sss.times[start:stop]
         t_str = '%8.3f - %8.3f sec' % tuple(rel_times[[0, -1]])
@@ -505,14 +534,14 @@ def maxwell_filter(raw, origin='auto', int_order=8, ext_order=3,
             _do_tSSS(out_meg_data, orig_in_data, resid, st_correlation,
                      n_positions, t_str)
         elif st_when == 'never' and head_pos[0] is not None:
-            pl = 's' if n_positions > 1 else ''
             logger.info('        Used % 2d head position%s for %s'
-                        % (n_positions, pl, t_str))
+                        % (n_positions, _pl(n_positions), t_str))
         raw_sss._data[meg_picks, start:stop] = out_meg_data
         raw_sss._data[pos_picks, start:stop] = out_pos_data
 
     # Update info
-    info['dev_head_t'] = recon_trans  # set the reconstruction transform
+    if not st_only:
+        info['dev_head_t'] = recon_trans  # set the reconstruction transform
     _update_sss_info(raw_sss, origin, int_order, ext_order, len(good_picks),
                      coord_frame, sss_ctc, sss_cal, max_st, reg_moments_0,
                      st_only)
@@ -521,7 +550,7 @@ def maxwell_filter(raw, origin='auto', int_order=8, ext_order=3,
 
 
 def _get_coil_scale(meg_picks, mag_picks, grad_picks, mag_scale, info):
-    """Helper to get the magnetometer scale factor"""
+    """Get the magnetometer scale factor."""
     if isinstance(mag_scale, string_types):
         if mag_scale != 'auto':
             raise ValueError('mag_scale must be a float or "auto", got "%s"'
@@ -533,8 +562,8 @@ def _get_coil_scale(meg_picks, mag_picks, grad_picks, mag_scale, info):
         else:
             # Find our physical distance between gradiometer pickup loops
             # ("base line")
-            coils = _create_meg_coils(pick_info(info, meg_picks)['chs'],
-                                      'accurate')
+            coils = _create_meg_coils([info['chs'][pick]
+                                       for pick in meg_picks], 'accurate')
             grad_base = set(coils[pick]['base'] for pick in grad_picks)
             if len(grad_base) != 1 or list(grad_base)[0] <= 0:
                 raise RuntimeError('Could not automatically determine '
@@ -552,7 +581,7 @@ def _get_coil_scale(meg_picks, mag_picks, grad_picks, mag_scale, info):
 
 
 def _remove_meg_projs(inst):
-    """Helper to remove inplace existing MEG projectors (assumes inactive)"""
+    """Remove inplace existing MEG projectors (assumes inactive)."""
     meg_picks = pick_types(inst.info, meg=True, exclude=[])
     meg_channels = [inst.ch_names[pi] for pi in meg_picks]
     non_meg_proj = list()
@@ -563,7 +592,7 @@ def _remove_meg_projs(inst):
 
 
 def _check_destination(destination, info, head_frame):
-    """Helper to triage our reconstruction trans"""
+    """Triage our reconstruction trans."""
     if destination is None:
         return info['dev_head_t']
     if not head_frame:
@@ -589,10 +618,10 @@ def _check_destination(destination, info, head_frame):
 
 
 def _prep_mf_coils(info, ignore_ref=True):
-    """Helper to get all coil integration information loaded and sorted"""
+    """Get all coil integration information loaded and sorted."""
     coils, comp_coils = _prep_meg_channels(
         info, accurate=True, elekta_defs=True, head_frame=False,
-        ignore_ref=ignore_ref, verbose=False)[:2]
+        ignore_ref=ignore_ref, do_picking=False, verbose=False)[:2]
     mag_mask = _get_mag_mask(coils)
     if len(comp_coils) > 0:
         meg_picks = pick_types(info, meg=True, ref_meg=False, exclude=[])
@@ -622,7 +651,7 @@ def _prep_mf_coils(info, ignore_ref=True):
 
 
 def _trans_starts_stops_quats(pos, start, stop, this_pos_data):
-    """Helper to get all trans and limits we need"""
+    """Get all trans and limits we need."""
     pos_idx = np.arange(*np.searchsorted(pos[1], [start, stop]))
     used = np.zeros(stop - start, bool)
     trans = list()
@@ -674,20 +703,21 @@ def _trans_starts_stops_quats(pos, start, stop, this_pos_data):
 
 def _do_tSSS(clean_data, orig_in_data, resid, st_correlation,
              n_positions, t_str):
-    """Compute and apply SSP-like projection vectors based on min corr"""
+    """Compute and apply SSP-like projection vectors based on min corr."""
     np.asarray_chkfinite(resid)
     t_proj = _overlap_projector(orig_in_data, resid, st_correlation)
     # Apply projector according to Eq. 12 in [2]_
-    msg = ('        Projecting %2d intersecting tSSS components '
-           'for %s' % (t_proj.shape[1], t_str))
+    msg = ('        Projecting %2d intersecting tSSS component%s '
+           'for %s' % (t_proj.shape[1], _pl(t_proj.shape[1], ' '), t_str))
     if n_positions > 1:
-        msg += ' (across %2d positions)' % n_positions
+        msg += ' (across %2d position%s)' % (n_positions,
+                                             _pl(n_positions, ' '))
     logger.info(msg)
     clean_data -= np.dot(np.dot(clean_data, t_proj), t_proj.T)
 
 
 def _copy_preload_add_channels(raw, add_channels):
-    """Helper to load data for processing and (maybe) add cHPI pos channels"""
+    """Load data for processing and (maybe) add cHPI pos channels."""
     raw = raw.copy()
     if add_channels:
         kinds = [FIFF.FIFFV_QUAT_1, FIFF.FIFFV_QUAT_2, FIFF.FIFFV_QUAT_3,
@@ -730,7 +760,7 @@ def _copy_preload_add_channels(raw, add_channels):
 
 
 def _check_pos(pos, head_frame, raw, st_fixed, sfreq):
-    """Check for a valid pos array and transform it to a more usable form"""
+    """Check for a valid pos array and transform it to a more usable form."""
     if pos is None:
         return [None, np.array([-1])]
     if not head_frame:
@@ -767,7 +797,7 @@ def _check_pos(pos, head_frame, raw, st_fixed, sfreq):
 def _get_decomp(trans, all_coils, cal, regularize, exp, ignore_ref,
                 coil_scale, grad_picks, mag_picks, good_picks, mag_or_fine,
                 bad_condition, t, mag_scale):
-    """Helper to get a decomposition matrix and pseudoinverse matrices"""
+    """Get a decomposition matrix and pseudoinverse matrices."""
     #
     # Fine calibration processing (point-like magnetometers and calib. coeffs)
     #
@@ -799,7 +829,7 @@ def _get_decomp(trans, all_coils, cal, regularize, exp, ignore_ref,
 
 def _get_s_decomp(exp, all_coils, trans, coil_scale, cal, ignore_ref,
                   grad_picks, mag_picks, good_picks, mag_scale):
-    """Helper to get S_decomp"""
+    """Get S_decomp."""
     S_decomp = _trans_sss_basis(exp, all_coils, trans, coil_scale)
     if cal is not None:
         # Compute point-like mags to incorporate gradiometer imbalance
@@ -815,7 +845,7 @@ def _get_s_decomp(exp, all_coils, trans, coil_scale, cal, ignore_ref,
 
 @verbose
 def _regularize(regularize, exp, S_decomp, mag_or_fine, t, verbose=None):
-    """Regularize a decomposition matrix"""
+    """Regularize a decomposition matrix."""
     # ALWAYS regularize the out components according to norm, since
     # gradiometer-only setups (e.g., KIT) can have zero first-order
     # components
@@ -823,7 +853,6 @@ def _regularize(regularize, exp, S_decomp, mag_or_fine, t, verbose=None):
     n_in, n_out = _get_n_moments([int_order, ext_order])
     t_str = '%8.3f' % t
     if regularize is not None:  # regularize='in'
-        logger.info('    Computing regularization')
         in_removes, out_removes = _regularize_in(
             int_order, ext_order, S_decomp, mag_or_fine)
     else:
@@ -846,7 +875,7 @@ def _regularize(regularize, exp, S_decomp, mag_or_fine, t, verbose=None):
 
 
 def _get_mf_picks(info, int_order, ext_order, ignore_ref=False):
-    """Helper to pick types for Maxwell filtering"""
+    """Pick types for Maxwell filtering."""
     # Check for T1/T2 mag types
     mag_inds_T1T2 = _get_T1T2_mag_inds(info)
     if len(mag_inds_T1T2) > 0:
@@ -856,7 +885,7 @@ def _get_mf_picks(info, int_order, ext_order, ignore_ref=False):
     # Get indices of channels to use in multipolar moment calculation
     ref = not ignore_ref
     meg_picks = pick_types(info, meg=True, ref_meg=ref, exclude=[])
-    meg_info = pick_info(info, meg_picks)
+    meg_info = pick_info(_simplify_info(info), meg_picks)
     del info
     good_picks = pick_types(meg_info, meg=True, ref_meg=ref, exclude='bads')
     n_bases = _get_n_moments([int_order, ext_order]).sum()
@@ -891,16 +920,17 @@ def _get_mf_picks(info, int_order, ext_order, ignore_ref=False):
 
 
 def _check_regularize(regularize):
-    """Helper to ensure regularize is valid"""
+    """Ensure regularize is valid."""
     if not (regularize is None or (isinstance(regularize, string_types) and
                                    regularize in ('in',))):
         raise ValueError('regularize must be None or "in"')
 
 
 def _check_usable(inst):
-    """Helper to ensure our data are clean"""
+    """Ensure our data are clean."""
     if inst.proj:
-        raise RuntimeError('Projectors cannot be applied to data.')
+        raise RuntimeError('Projectors cannot be applied to data during '
+                           'Maxwell filtering.')
     current_comp = inst.compensation_grade
     if current_comp not in (0, None):
         raise RuntimeError('Maxwell filter cannot be done on compensated '
@@ -909,7 +939,7 @@ def _check_usable(inst):
 
 
 def _col_norm_pinv(x):
-    """Compute the pinv with column-normalization to stabilize calculation
+    """Compute the pinv with column-normalization to stabilize calculation.
 
     Note: will modify/overwrite x.
     """
@@ -922,18 +952,18 @@ def _col_norm_pinv(x):
 
 
 def _sq(x):
-    """Helper to square"""
+    """Square quickly."""
     return x * x
 
 
 def _check_finite(data):
-    """Helper to ensure data is finite"""
+    """Ensure data is finite."""
     if not np.isfinite(data).all():
         raise RuntimeError('data contains non-finite numbers')
 
 
 def _sph_harm_norm(order, degree):
-    """Normalization factor for spherical harmonics"""
+    """Compute normalization factor for spherical harmonics."""
     # we could use scipy.special.poch(degree + order + 1, -2 * order)
     # here, but it's slower for our fairly small degree
     norm = np.sqrt((2 * degree + 1.) / (4 * np.pi))
@@ -943,59 +973,8 @@ def _sph_harm_norm(order, degree):
     return norm
 
 
-def _sph_harm(order, degree, az, pol, norm=True):
-    """Evaluate point in specified multipolar moment. [1]_ Equation 4.
-
-    When using, pay close attention to inputs. Spherical harmonic notation for
-    order/degree, and theta/phi are both reversed in original SSS work compared
-    to many other sources. See mathworld.wolfram.com/SphericalHarmonic.html for
-    more discussion.
-
-    Note that scipy has ``scipy.special.sph_harm``, but that function is
-    too slow on old versions (< 0.15) for heavy use.
-
-    Parameters
-    ----------
-    order : int
-        Order of spherical harmonic. (Usually) corresponds to 'm'.
-    degree : int
-        Degree of spherical harmonic. (Usually) corresponds to 'l'.
-    az : float
-        Azimuthal (longitudinal) spherical coordinate [0, 2*pi]. 0 is aligned
-        with x-axis.
-    pol : float
-        Polar (or colatitudinal) spherical coordinate [0, pi]. 0 is aligned
-        with z-axis.
-    norm : bool
-        If True, include normalization factor.
-
-    Returns
-    -------
-    base : complex float
-        The spherical harmonic value.
-    """
-    from scipy.special import lpmv
-
-    # Error checks
-    if np.abs(order) > degree:
-        raise ValueError('Absolute value of order must be <= degree')
-    # Ensure that polar and azimuth angles are arrays
-    az = np.asarray(az)
-    pol = np.asarray(pol)
-    if (np.abs(az) > 2 * np.pi).any():
-        raise ValueError('Azimuth coords must lie in [-2*pi, 2*pi]')
-    if(pol < 0).any() or (pol > np.pi).any():
-        raise ValueError('Polar coords must lie in [0, pi]')
-    # This is the "seismology" convention on Wikipedia, w/o Condon-Shortley
-    if norm:
-        norm = _sph_harm_norm(order, degree)
-    else:
-        norm = 1.
-    return norm * lpmv(order, degree, np.cos(pol)) * np.exp(1j * order * az)
-
-
 def _concatenate_sph_coils(coils):
-    """Helper to concatenate MEG coil parameters for spherical harmoncs."""
+    """Concatenate MEG coil parameters for spherical harmoncs."""
     rs = np.concatenate([coil['r0_exey'] for coil in coils])
     wcoils = np.concatenate([coil['w'] for coil in coils])
     ezs = np.concatenate([np.tile(coil['ez'][np.newaxis, :],
@@ -1010,13 +989,13 @@ _mu_0 = 4e-7 * np.pi  # magnetic permeability
 
 
 def _get_mag_mask(coils):
-    """Helper to get the coil_scale for Maxwell filtering"""
+    """Get the coil_scale for Maxwell filtering."""
     return np.array([coil['coil_class'] == FIFF.FWD_COILC_MAG
                      for coil in coils])
 
 
 def _sss_basis_basic(exp, coils, mag_scale=100., method='standard'):
-    """Compute SSS basis using non-optimized (but more readable) algorithms"""
+    """Compute SSS basis using non-optimized (but more readable) algorithms."""
     int_order, ext_order = exp['int_order'], exp['ext_order']
     origin = exp['origin']
     # Compute vector between origin and coil, convert to spherical coords
@@ -1054,9 +1033,8 @@ def _sss_basis_basic(exp, coils, mag_scale=100., method='standard'):
             S_in_out = list()
             grads_in_out = list()
             # Same spherical harmonic is used for both internal and external
-            sph = _sph_harm(order, degree, az, pol, norm=False)
+            sph = _get_sph_harm()(order, degree, az, pol)
             sph_norm = _sph_harm_norm(order, degree)
-            sph *= sph_norm
             # Compute complex gradient for all integration points
             # in spherical coordinates (Eq. 6). The gradient for rad, az, pol
             # is obtained by taking the partial derivative of Eq. 4 w.r.t. each
@@ -1097,14 +1075,14 @@ def _sss_basis_basic(exp, coils, mag_scale=100., method='standard'):
                         # Gradients dotted w/integration point weighted normals
                         gr = np.einsum('ij,ij->i', gr, cosmags)
                         vals = np.bincount(bins, gr, len(coils))
-                        spc[:, _deg_order_idx(degree, oo)] = -vals
+                        spc[:, _deg_ord_idx(degree, oo)] = -vals
                 else:
                     grads = np.einsum('ij,ij->i', grads, ezs)
                     v = (np.bincount(bins, grads.real, len(coils)) +
                          1j * np.bincount(bins, grads.imag, len(coils)))
-                    spc[:, _deg_order_idx(degree, order)] = -v
+                    spc[:, _deg_ord_idx(degree, order)] = -v
                     if order > 0:
-                        spc[:, _deg_order_idx(degree, -order)] = \
+                        spc[:, _deg_ord_idx(degree, -order)] = \
                             -_sh_negate(v, order)
 
     # Scale magnetometers
@@ -1186,7 +1164,7 @@ def _sss_basis(exp, all_coils):
         mult = 2e-7 * np.sqrt((2 * degree + 1) * np.pi)
 
         if degree > 0:
-            idx = _deg_order_idx(degree, 0)
+            idx = _deg_ord_idx(degree, 0)
             # alpha
             if degree <= int_order:
                 b_r = mult * (degree + 1) * L[degree][0] / r_nn2
@@ -1209,7 +1187,7 @@ def _sss_basis(exp, all_coils):
             factor = mult * np.sqrt(2)  # equivalence fix (Elekta uses 2.)
 
             # Real
-            idx = _deg_order_idx(degree, order)
+            idx = _deg_ord_idx(degree, order)
             r_fact = factor * L[degree][order] * cos_order
             az_fact = factor * order * sin_order * L[degree][order]
             pol_fact = -factor * (L[degree][order + 1] -
@@ -1235,7 +1213,7 @@ def _sss_basis(exp, all_coils):
                     cosmags, bins, n_coils)
 
             # Imaginary
-            idx = _deg_order_idx(degree, -order)
+            idx = _deg_ord_idx(degree, -order)
             r_fact = factor * L[degree][order] * sin_order
             az_fact = factor * order * cos_order * L[degree][order]
             pol_fact = factor * (L[degree][order + 1] -
@@ -1264,14 +1242,14 @@ def _sss_basis(exp, all_coils):
 
 def _integrate_points(cos_az, sin_az, cos_pol, sin_pol, b_r, b_az, b_pol,
                       cosmags, bins, n_coils):
-    """Helper to integrate points in spherical coords"""
+    """Integrate points in spherical coords."""
     grads = _sp_to_cart(cos_az, sin_az, cos_pol, sin_pol, b_r, b_az, b_pol).T
     grads = np.einsum('ij,ij->i', grads, cosmags)
     return np.bincount(bins, grads, n_coils)
 
 
 def _tabular_legendre(r, nind):
-    """Helper to compute associated Legendre polynomials"""
+    """Compute associated Legendre polynomials."""
     r_n = np.sqrt(np.sum(r * r, axis=1))
     x = r[:, 2] / r_n  # cos(theta)
     L = list()
@@ -1297,7 +1275,7 @@ def _tabular_legendre(r, nind):
 
 
 def _sp_to_cart(cos_az, sin_az, cos_pol, sin_pol, b_r, b_az, b_pol):
-    """Helper to convert spherical coords to cartesian"""
+    """Convert spherical coords to cartesian."""
     return np.array([(sin_pol * cos_az * b_r +
                       cos_pol * cos_az * b_pol - sin_az * b_az),
                      (sin_pol * sin_az * b_r +
@@ -1306,27 +1284,22 @@ def _sp_to_cart(cos_az, sin_az, cos_pol, sin_pol, b_r, b_az, b_pol):
 
 
 def _get_degrees_orders(order):
-    """Helper to get the set of degrees used in our basis functions"""
+    """Get the set of degrees used in our basis functions."""
     degrees = np.zeros(_get_n_moments(order), int)
     orders = np.zeros_like(degrees)
     for degree in range(1, order + 1):
         # Only loop over positive orders, negative orders are handled
         # for efficiency within
         for order in range(degree + 1):
-            ii = _deg_order_idx(degree, order)
+            ii = _deg_ord_idx(degree, order)
             degrees[ii] = degree
             orders[ii] = order
-            ii = _deg_order_idx(degree, -order)
+            ii = _deg_ord_idx(degree, -order)
             degrees[ii] = degree
             orders[ii] = -order
     return degrees, orders
 
 
-def _deg_order_idx(deg, order):
-    """Helper to get the index into S_in or S_out given a degree and order"""
-    return _sq(deg) + deg + order - 1
-
-
 def _alegendre_deriv(order, degree, val):
     """Compute the derivative of the associated Legendre polynomial at a value.
 
@@ -1351,62 +1324,8 @@ def _alegendre_deriv(order, degree, val):
             lpmv(order - 1, degree, val)) / (1. - val * val)
 
 
-def _sh_negate(sh, order):
-    """Helper to get the negative spherical harmonic from a positive one"""
-    assert order >= 0
-    return sh.conj() * (-1. if order % 2 else 1.)  # == (-1) ** order
-
-
-def _sh_complex_to_real(sh, order):
-    """Helper function to convert complex to real basis functions.
-
-    Parameters
-    ----------
-    sh : array-like
-        Spherical harmonics. Must be from order >=0 even if negative orders
-        are used.
-    order : int
-        Order (usually 'm') of multipolar moment.
-
-    Returns
-    -------
-    real_sh : array-like
-        The real version of the spherical harmonics.
-
-    Notes
-    -----
-    This does not include the Condon-Shortely phase.
-    """
-
-    if order == 0:
-        return np.real(sh)
-    else:
-        return np.sqrt(2.) * (np.real if order > 0 else np.imag)(sh)
-
-
-def _sh_real_to_complex(shs, order):
-    """Convert real spherical harmonic pair to complex
-
-    Parameters
-    ----------
-    shs : ndarray, shape (2, ...)
-        The real spherical harmonics at ``[order, -order]``.
-    order : int
-        Order (usually 'm') of multipolar moment.
-
-    Returns
-    -------
-    sh : array-like, shape (...)
-        The complex version of the spherical harmonics.
-    """
-    if order == 0:
-        return shs[0]
-    else:
-        return (shs[0] + 1j * np.sign(order) * shs[1]) / np.sqrt(2.)
-
-
 def _bases_complex_to_real(complex_tot, int_order, ext_order):
-    """Convert complex spherical harmonics to real"""
+    """Convert complex spherical harmonics to real."""
     n_in, n_out = _get_n_moments([int_order, ext_order])
     complex_in = complex_tot[:, :n_in]
     complex_out = complex_tot[:, n_in:]
@@ -1418,8 +1337,8 @@ def _bases_complex_to_real(complex_tot, int_order, ext_order):
                                      [int_order, ext_order]):
         for deg in range(1, exp_order + 1):
             for order in range(deg + 1):
-                idx_pos = _deg_order_idx(deg, order)
-                idx_neg = _deg_order_idx(deg, -order)
+                idx_pos = _deg_ord_idx(deg, order)
+                idx_neg = _deg_ord_idx(deg, -order)
                 real[:, idx_pos] = _sh_complex_to_real(comp[:, idx_pos], order)
                 if order != 0:
                     # This extra mult factor baffles me a bit, but it works
@@ -1431,7 +1350,7 @@ def _bases_complex_to_real(complex_tot, int_order, ext_order):
 
 
 def _bases_real_to_complex(real_tot, int_order, ext_order):
-    """Convert real spherical harmonics to complex"""
+    """Convert real spherical harmonics to complex."""
     n_in, n_out = _get_n_moments([int_order, ext_order])
     real_in = real_tot[:, :n_in]
     real_out = real_tot[:, n_in:]
@@ -1444,8 +1363,8 @@ def _bases_real_to_complex(real_tot, int_order, ext_order):
         for deg in range(1, exp_order + 1):
             # only loop over positive orders, figure out neg from pos
             for order in range(deg + 1):
-                idx_pos = _deg_order_idx(deg, order)
-                idx_neg = _deg_order_idx(deg, -order)
+                idx_pos = _deg_ord_idx(deg, order)
+                idx_neg = _deg_ord_idx(deg, -order)
                 this_comp = _sh_real_to_complex([real[:, idx_pos],
                                                  real[:, idx_neg]], order)
                 comp[:, idx_pos] = this_comp
@@ -1453,83 +1372,9 @@ def _bases_real_to_complex(real_tot, int_order, ext_order):
     return comp_tot
 
 
-def _get_n_moments(order):
-    """Compute the number of multipolar moments.
-
-    Equivalent to [1]_ Eq. 32.
-
-    Parameters
-    ----------
-    order : array-like
-        Expansion orders, often ``[int_order, ext_order]``.
-
-    Returns
-    -------
-    M : ndarray
-        Number of moments due to each order.
-    """
-    order = np.asarray(order, int)
-    return (order + 2) * order
-
-
-def _sph_to_cart_partials(az, pol, g_rad, g_az, g_pol):
-    """Convert spherical partial derivatives to cartesian coords.
-
-    Note: Because we are dealing with partial derivatives, this calculation is
-    not a static transformation. The transformation matrix itself is dependent
-    on azimuth and polar coord.
-
-    See the 'Spherical coordinate sytem' section here:
-    wikipedia.org/wiki/Vector_fields_in_cylindrical_and_spherical_coordinates
-
-    Parameters
-    ----------
-    az : ndarray, shape (n_points,)
-        Array containing spherical coordinates points (azimuth).
-    pol : ndarray, shape (n_points,)
-        Array containing spherical coordinates points (polar).
-    sph_grads : ndarray, shape (n_points, 3)
-        Array containing partial derivatives at each spherical coordinate
-        (radius, azimuth, polar).
-
-    Returns
-    -------
-    cart_grads : ndarray, shape (n_points, 3)
-        Array containing partial derivatives in Cartesian coordinates (x, y, z)
-    """
-    sph_grads = np.c_[g_rad, g_az, g_pol]
-    cart_grads = np.zeros_like(sph_grads)
-    c_as, s_as = np.cos(az), np.sin(az)
-    c_ps, s_ps = np.cos(pol), np.sin(pol)
-    trans = np.array([[c_as * s_ps, -s_as, c_as * c_ps],
-                      [s_as * s_ps, c_as, c_ps * s_as],
-                      [c_ps, np.zeros_like(c_as), -s_ps]])
-    cart_grads = np.einsum('ijk,kj->ki', trans, sph_grads)
-    return cart_grads
-
-
-def _cart_to_sph(cart_pts):
-    """Convert Cartesian coordinates to spherical coordinates.
-
-    Parameters
-    ----------
-    cart_pts : ndarray, shape (n_points, 3)
-        Array containing points in Cartesian coordinates (x, y, z)
-
-    Returns
-    -------
-    sph_pts : ndarray, shape (n_points, 3)
-        Array containing points in spherical coordinates (rad, azimuth, polar)
-    """
-    rad = np.sqrt(np.sum(cart_pts * cart_pts, axis=1))
-    az = np.arctan2(cart_pts[:, 1], cart_pts[:, 0])
-    pol = np.arccos(cart_pts[:, 2] / rad)
-    return np.array([rad, az, pol]).T
-
-
 def _check_info(info, sss=True, tsss=True, calibration=True, ctc=True):
-    """Ensure that Maxwell filtering has not been applied yet"""
-    for ent in info.get('proc_history', []):
+    """Ensure that Maxwell filtering has not been applied yet."""
+    for ent in info['proc_history']:
         for msg, key, doing in (('SSS', 'sss_info', sss),
                                 ('tSSS', 'max_st', tsss),
                                 ('fine calibration', 'sss_cal', calibration),
@@ -1543,7 +1388,7 @@ def _check_info(info, sss=True, tsss=True, calibration=True, ctc=True):
 
 def _update_sss_info(raw, origin, int_order, ext_order, nchan, coord_frame,
                      sss_ctc, sss_cal, max_st, reg_moments, st_only):
-    """Helper function to update info inplace after Maxwell filtering
+    """Update info inplace after Maxwell filtering.
 
     Parameters
     ----------
@@ -1587,14 +1432,13 @@ def _update_sss_info(raw, origin, int_order, ext_order, nchan, coord_frame,
         # Reset 'bads' for any MEG channels since they've been reconstructed
         _reset_meg_bads(raw.info)
     block_id = _generate_meas_id()
-    proc_block = dict(max_info=max_info_dict, block_id=block_id,
-                      creator='mne-python v%s' % __version__,
-                      date=_date_now(), experimentor='')
-    raw.info['proc_history'] = [proc_block] + raw.info.get('proc_history', [])
+    raw.info['proc_history'].insert(0, dict(
+        max_info=max_info_dict, block_id=block_id, date=_date_now(),
+        creator='mne-python v%s' % __version__, experimenter=''))
 
 
 def _reset_meg_bads(info):
-    """Helper to reset MEG bads"""
+    """Reset MEG bads."""
     meg_picks = pick_types(info, meg=True, exclude=[])
     info['bads'] = [bad for bad in info['bads']
                     if info['ch_names'].index(bad) not in meg_picks]
@@ -1606,7 +1450,7 @@ if 'check_finite' in _get_args(linalg.svd):
 
 
 def _orth_overwrite(A):
-    """Helper to create a slightly more efficient 'orth'"""
+    """Create a slightly more efficient 'orth'."""
     # adapted from scipy/linalg/decomp_svd.py
     u, s = _safe_svd(A, full_matrices=False, **check_disable)[:2]
     M, N = A.shape
@@ -1617,7 +1461,7 @@ def _orth_overwrite(A):
 
 
 def _overlap_projector(data_int, data_res, corr):
-    """Calculate projector for removal of subspace intersection in tSSS"""
+    """Calculate projector for removal of subspace intersection in tSSS."""
     # corr necessary to deal with noise when finding identical signal
     # directions in the subspace. See the end of the Results section in [2]_
 
@@ -1642,9 +1486,8 @@ def _overlap_projector(data_int, data_res, corr):
     del Q_int
 
     # Compute angles between subspace and which bases to keep
-    S_intersect, Vh_intersect = linalg.svd(C_mat, overwrite_a=True,
-                                           full_matrices=False,
-                                           **check_disable)[1:]
+    S_intersect, Vh_intersect = _safe_svd(C_mat, full_matrices=False,
+                                          **check_disable)[1:]
     del C_mat
     intersect_mask = (S_intersect >= corr)
     del S_intersect
@@ -1656,66 +1499,40 @@ def _overlap_projector(data_int, data_res, corr):
     return V_principal
 
 
-def _read_fine_cal(fine_cal):
-    """Read sensor locations and calib. coeffs from fine calibration file."""
-
-    # Read new sensor locations
-    cal_chs = list()
-    cal_ch_numbers = list()
-    with open(fine_cal, 'r') as fid:
-        lines = [line for line in fid if line[0] not in '#\n']
-        for line in lines:
-            # `vals` contains channel number, (x, y, z), x-norm 3-vec, y-norm
-            # 3-vec, z-norm 3-vec, and (1 or 3) imbalance terms
-            vals = np.fromstring(line, sep=' ').astype(np.float64)
-
-            # Check for correct number of items
-            if len(vals) not in [14, 16]:
-                raise RuntimeError('Error reading fine calibration file')
-
-            ch_name = 'MEG' + '%04d' % vals[0]  # Zero-pad names to 4 char
-            cal_ch_numbers.append(vals[0])
-
-            # Get orientation information for coil transformation
-            loc = vals[1:13].copy()  # Get orientation information for 'loc'
-            calib_coeff = vals[13:].copy()  # Get imbalance/calibration coeff
-            cal_chs.append(dict(ch_name=ch_name,
-                                loc=loc, calib_coeff=calib_coeff,
-                                coord_frame=FIFF.FIFFV_COORD_DEVICE))
-    return cal_chs, cal_ch_numbers
-
-
 def _update_sensor_geometry(info, fine_cal, ignore_ref):
-    """Helper to replace sensor geometry information and reorder cal_chs"""
+    """Replace sensor geometry information and reorder cal_chs."""
     from ._fine_cal import read_fine_calibration
     logger.info('    Using fine calibration %s' % op.basename(fine_cal))
     fine_cal = read_fine_calibration(fine_cal)  # filename -> dict
     ch_names = _clean_names(info['ch_names'], remove_whitespace=True)
-    info_order = pick_channels(ch_names, fine_cal['ch_names'])
+    info_to_cal = dict()
+    missing = list()
+    for ci, name in enumerate(fine_cal['ch_names']):
+        if name not in ch_names:
+            missing.append(name)
+        else:
+            oi = ch_names.index(name)
+            info_to_cal[oi] = ci
     meg_picks = pick_types(info, meg=True, exclude=[])
-    if len(set(info_order) - set(meg_picks)) != 0:
-        # this should never happen
-        raise RuntimeError('Found channels in cal file that are not marked '
-                           'as MEG channels in the data file')
-    if len(info_order) != len(meg_picks):
+    if len(info_to_cal) != len(meg_picks):
         raise RuntimeError(
             'Not all MEG channels found in fine calibration file, missing:\n%s'
             % sorted(list(set(ch_names[pick] for pick in meg_picks) -
                           set(fine_cal['ch_names']))))
-    rev_order = np.argsort(info_order)
-    rev_grad = rev_order[np.in1d(meg_picks,
-                                 pick_types(info, meg='grad', exclude=()))]
-    rev_mag = rev_order[np.in1d(meg_picks,
-                                pick_types(info, meg='mag', exclude=()))]
+    if len(missing):
+        warn('Found cal channel%s not in data: %s' % (_pl(missing), missing))
+    grad_picks = pick_types(info, meg='grad', exclude=())
+    mag_picks = pick_types(info, meg='mag', exclude=())
 
     # Determine gradiometer imbalances and magnetometer calibrations
-    grad_imbalances = np.array([fine_cal['imb_cals'][ri] for ri in rev_grad]).T
+    grad_imbalances = np.array([fine_cal['imb_cals'][info_to_cal[gi]]
+                                for gi in grad_picks]).T
     if grad_imbalances.shape[0] not in [1, 3]:
         raise ValueError('Must have 1 (x) or 3 (x, y, z) point-like ' +
                          'magnetometers. Currently have %i' %
                          grad_imbalances.shape[0])
-    mag_cals = np.array([fine_cal['imb_cals'][ri] for ri in rev_mag])
-    del rev_order, rev_grad, rev_mag
+    mag_cals = np.array([fine_cal['imb_cals'][info_to_cal[mi]]
+                         for mi in mag_picks])
     # Now let's actually construct our point-like adjustment coils for grads
     grad_coilsets = _get_grad_point_coilsets(
         info, n_types=len(grad_imbalances), ignore_ref=ignore_ref)
@@ -1727,13 +1544,12 @@ def _update_sensor_geometry(info, fine_cal, ignore_ref):
     used = np.zeros(len(info['chs']), bool)
     cal_corrs = list()
     cal_chans = list()
-    grad_picks = pick_types(info, meg='grad', exclude=())
     adjust_logged = False
-    for ci, info_idx in enumerate(info_order):
-        assert ch_names[info_idx] == fine_cal['ch_names'][ci]
-        assert not used[info_idx]
-        used[info_idx] = True
-        info_ch = info['chs'][info_idx]
+    for oi, ci in info_to_cal.items():
+        assert ch_names[oi] == fine_cal['ch_names'][ci]
+        assert not used[oi]
+        used[oi] = True
+        info_ch = info['chs'][oi]
         ch_num = int(fine_cal['ch_names'][ci].lstrip('MEG').lstrip('0'))
         cal_chans.append([ch_num, info_ch['coil_type']])
 
@@ -1759,7 +1575,7 @@ def _update_sensor_geometry(info, fine_cal, ignore_ref):
         v2 = _loc_to_coil_trans(info_ch['loc'])[:3, :3]
         _normalize_vectors(v2)
         ang_shift[ci] = np.sum(v1 * v2, axis=0)
-        if info_idx in grad_picks:
+        if oi in grad_picks:
             extra = [1., fine_cal['imb_cals'][ci][0]]
         else:
             extra = [fine_cal['imb_cals'][ci][0], 0.]
@@ -1770,6 +1586,7 @@ def _update_sensor_geometry(info, fine_cal, ignore_ref):
         assert (info_ch['coord_frame'] == FIFF.FIFFV_COORD_DEVICE)
     assert used[meg_picks].all()
     assert not used[np.setdiff1d(np.arange(len(used)), meg_picks)].any()
+    ang_shift = ang_shift[list(info_to_cal.values())]  # subselect used ones
     # This gets written to the Info struct
     sss_cal = dict(cal_corrs=np.array(cal_corrs),
                    cal_chans=np.array(cal_chans))
@@ -1786,10 +1603,10 @@ def _update_sensor_geometry(info, fine_cal, ignore_ref):
 
 
 def _get_grad_point_coilsets(info, n_types, ignore_ref):
-    """Helper to get point-type coilsets for gradiometers"""
+    """Get point-type coilsets for gradiometers."""
     grad_coilsets = list()
     grad_info = pick_info(
-        info, pick_types(info, meg='grad', exclude=[]), copy=True)
+        _simplify_info(info), pick_types(info, meg='grad', exclude=[]))
     # Coil_type values for x, y, z point magnetometers
     # Note: 1D correction files only have x-direction corrections
     pt_types = [FIFF.FIFFV_COIL_POINT_MAGNETOMETER_X,
@@ -1803,7 +1620,7 @@ def _get_grad_point_coilsets(info, n_types, ignore_ref):
 
 
 def _sss_basis_point(exp, trans, cal, ignore_ref=False, mag_scale=100.):
-    """Compute multipolar moments for point-like magnetometers (in fine cal)"""
+    """Compute multipolar moments for point-like mags (in fine cal)."""
     # Loop over all coordinate directions desired and create point mags
     S_tot = 0.
     # These are magnetometers, so use a uniform coil_scale of 100.
@@ -1819,14 +1636,14 @@ def _sss_basis_point(exp, trans, cal, ignore_ref=False, mag_scale=100.):
 
 
 def _regularize_out(int_order, ext_order, mag_or_fine):
-    """Helper to regularize out components based on norm"""
+    """Regularize out components based on norm."""
     n_in = _get_n_moments(int_order)
     out_removes = list(np.arange(0 if mag_or_fine.any() else 3) + n_in)
     return list(out_removes)
 
 
 def _regularize_in(int_order, ext_order, S_decomp, mag_or_fine):
-    """Regularize basis set using idealized SNR measure"""
+    """Regularize basis set using idealized SNR measure."""
     n_in, n_out = _get_n_moments([int_order, ext_order])
 
     # The "signal" terms depend only on the inner expansion order
@@ -1858,11 +1675,11 @@ def _regularize_in(int_order, ext_order, S_decomp, mag_or_fine):
     #     for degree in range(1, int_order + 1):
     #         for order in range(0, degree + 1):
     #             assert plot_ord[count] == -1
-    #             plot_ord[count] = _deg_order_idx(degree, order)
+    #             plot_ord[count] = _deg_ord_idx(degree, order)
     #             count += 1
     #             if order > 0:
     #                 assert plot_ord[count] == -1
-    #                 plot_ord[count] = _deg_order_idx(degree, -order)
+    #                 plot_ord[count] = _deg_ord_idx(degree, -order)
     #                 count += 1
     #     assert count == n_in
     #     assert (plot_ord >= 0).all()
@@ -1921,7 +1738,7 @@ def _regularize_in(int_order, ext_order, S_decomp, mag_or_fine):
 
 
 def _compute_sphere_activation_in(degrees):
-    """Helper to compute the "in" power from random currents in a sphere
+    u"""Compute the "in" power from random currents in a sphere.
 
     Parameters
     ----------
@@ -1931,17 +1748,15 @@ def _compute_sphere_activation_in(degrees):
     Returns
     -------
     a_power : ndarray
-        The a_lm associated for the associated degrees.
+        The a_lm associated for the associated degrees (see [1]_).
     rho_i : float
         The current density.
 
-    Notes
-    -----
-    See also:
-
-        A 122-channel whole-cortex SQUID system for measuring the brain’s
-        magnetic fields. Knuutila et al. IEEE Transactions on Magnetics,
-        Vol 29 No 6, Nov 1993.
+    References
+    ----------
+    .. [1] A 122-channel whole-cortex SQUID system for measuring the brain’s
+       magnetic fields. Knuutila et al. IEEE Transactions on Magnetics,
+       Vol 29 No 6, Nov 1993.
     """
     r_in = 0.080  # radius of the randomly-activated sphere
 
@@ -1965,7 +1780,7 @@ def _compute_sphere_activation_in(degrees):
 
 
 def _trans_sss_basis(exp, all_coils, trans=None, coil_scale=100.):
-    """SSS basis (optionally) using a dev<->head trans"""
+    """Compute SSS basis (optionally) using a dev<->head trans."""
     if trans is not None:
         if not isinstance(trans, Transform):
             trans = Transform('meg', 'head', trans)
diff --git a/mne/preprocessing/peak_finder.py b/mne/preprocessing/peak_finder.py
index a2e78fb..e5c648e 100644
--- a/mne/preprocessing/peak_finder.py
+++ b/mne/preprocessing/peak_finder.py
@@ -6,7 +6,7 @@ from .. utils import logger, verbose
 
 @verbose
 def peak_finder(x0, thresh=None, extrema=1, verbose=None):
-    """Noise tolerant fast peak finding algorithm
+    """Noise-tolerant fast peak-finding algorithm.
 
     Parameters
     ----------
@@ -20,7 +20,8 @@ def peak_finder(x0, thresh=None, extrema=1, verbose=None):
         1 if maxima are desired, -1 if minima are desired
         (default = maxima, 1).
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -42,7 +43,6 @@ def peak_finder(x0, thresh=None, extrema=1, verbose=None):
     x(1250:1255) = max(x);
     peak_finder(x)
     """
-
     x0 = np.asanyarray(x0)
 
     if x0.ndim >= 2:
diff --git a/mne/preprocessing/ssp.py b/mne/preprocessing/ssp.py
index ccaf05b..e7541c8 100644
--- a/mne/preprocessing/ssp.py
+++ b/mne/preprocessing/ssp.py
@@ -17,7 +17,7 @@ from .eog import find_eog_events
 
 
 def _safe_del_key(dict_, key):
-    """ Aux function
+    """Aux function.
 
     Use this function when preparing rejection parameters
     instead of directly deleting keys.
@@ -32,8 +32,9 @@ def _compute_exg_proj(mode, raw, raw_event, tmin, tmax,
                       average, filter_length, n_jobs, ch_name,
                       reject, flat, bads, avg_ref, no_proj, event_id,
                       exg_l_freq, exg_h_freq, tstart, qrs_threshold,
-                      filter_method, iir_params=None, verbose=None):
-    """Compute SSP/PCA projections for ECG or EOG artifacts
+                      filter_method, iir_params=None, return_drop_log=False,
+                      verbose=None):
+    """Compute SSP/PCA projections for ECG or EOG artifacts.
 
     .. note:: raw data must be preloaded.
 
@@ -95,8 +96,11 @@ def _compute_exg_proj(mode, raw, raw_event, tmin, tmax,
         Dictionary of parameters to use for IIR filtering.
         See mne.filter.construct_iir_filter for details. If iir_params
         is None and method="iir", 4th order Butterworth will be used.
+    return_drop_log : bool
+        If True, return the drop log.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -104,6 +108,8 @@ def _compute_exg_proj(mode, raw, raw_event, tmin, tmax,
         Computed SSP projectors.
     events : ndarray
         Detected events.
+    drop_log : list
+        The drop log, if requested.
     """
     if not raw.preload:
         raise ValueError('raw needs to be preloaded, '
@@ -182,16 +188,15 @@ def _compute_exg_proj(mode, raw, raw_event, tmin, tmax,
     raw.filter(l_freq, h_freq, picks=picks, filter_length=filter_length,
                n_jobs=n_jobs, method=filter_method, iir_params=iir_params,
                l_trans_bandwidth=0.5, h_trans_bandwidth=0.5,
-               phase='zero-double')
+               phase='zero-double', fir_design='firwin2')
 
     epochs = Epochs(raw, events, None, tmin, tmax, baseline=None, preload=True,
-                    picks=picks, reject=reject, flat=flat, proj=True,
-                    add_eeg_ref=False)
+                    picks=picks, reject=reject, flat=flat, proj=True)
 
-    epochs.drop_bad()
+    drop_log = epochs.drop_log
     if epochs.events.shape[0] < 1:
         warn('No good epochs found, returning None for projs')
-        return None, events
+        return (None, events) + ((drop_log,) if return_drop_log else ())
 
     if average:
         evoked = epochs.average()
@@ -205,10 +210,8 @@ def _compute_exg_proj(mode, raw, raw_event, tmin, tmax,
         p['desc'] = mode + "-" + p['desc']
 
     projs.extend(ev_projs)
-
     logger.info('Done.')
-
-    return projs, events
+    return (projs, events) + ((drop_log,) if return_drop_log else ())
 
 
 @verbose
@@ -220,8 +223,9 @@ def compute_proj_ecg(raw, raw_event=None, tmin=-0.2, tmax=0.4,
                      flat=None, bads=[], avg_ref=False,
                      no_proj=False, event_id=999, ecg_l_freq=5, ecg_h_freq=35,
                      tstart=0., qrs_threshold='auto', filter_method='fft',
-                     iir_params=None, copy=True, verbose=None):
-    """Compute SSP/PCA projections for ECG artifacts
+                     iir_params=None, copy=True, return_drop_log=False,
+                     verbose=None):
+    """Compute SSP/PCA projections for ECG artifacts.
 
     .. note:: raw data must be preloaded.
 
@@ -283,8 +287,13 @@ def compute_proj_ecg(raw, raw_event=None, tmin=-0.2, tmax=0.4,
         is None and method="iir", 4th order Butterworth will be used.
     copy : bool
         If False, filtering raw data is done in place. Defaults to True.
+    return_drop_log : bool
+        If True, return the drop log.
+
+        .. versionadded:: 0.15
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -292,19 +301,15 @@ def compute_proj_ecg(raw, raw_event=None, tmin=-0.2, tmax=0.4,
         Computed SSP projectors.
     ecg_events : ndarray
         Detected ECG events.
+    drop_log : list
+        The drop log, if requested.
     """
-    if copy is True:
-        raw = raw.copy()
-
-    projs, ecg_events = _compute_exg_proj('ECG', raw, raw_event, tmin, tmax,
-                                          n_grad, n_mag, n_eeg, l_freq, h_freq,
-                                          average, filter_length, n_jobs,
-                                          ch_name, reject, flat, bads, avg_ref,
-                                          no_proj, event_id, ecg_l_freq,
-                                          ecg_h_freq, tstart, qrs_threshold,
-                                          filter_method, iir_params)
-
-    return projs, ecg_events
+    raw = raw.copy() if copy else raw
+    return _compute_exg_proj(
+        'ECG', raw, raw_event, tmin, tmax, n_grad, n_mag, n_eeg,
+        l_freq, h_freq, average, filter_length, n_jobs, ch_name, reject, flat,
+        bads, avg_ref, no_proj, event_id, ecg_l_freq, ecg_h_freq, tstart,
+        qrs_threshold, filter_method, iir_params, return_drop_log)
 
 
 @verbose
@@ -315,8 +320,9 @@ def compute_proj_eog(raw, raw_event=None, tmin=-0.2, tmax=0.2,
                                  eog=np.inf), flat=None, bads=[],
                      avg_ref=False, no_proj=False, event_id=998, eog_l_freq=1,
                      eog_h_freq=10, tstart=0., filter_method='fft',
-                     iir_params=None, ch_name=None, copy=True, verbose=None):
-    """Compute SSP/PCA projections for EOG artifacts
+                     iir_params=None, ch_name=None, copy=True,
+                     return_drop_log=False, verbose=None):
+    """Compute SSP/PCA projections for EOG artifacts.
 
     .. note:: raw data must be preloaded.
 
@@ -374,8 +380,13 @@ def compute_proj_eog(raw, raw_event=None, tmin=-0.2, tmax=0.2,
         If not None, specify EOG channel name.
     copy : bool
         If False, filtering raw data is done in place. Defaults to True.
+    return_drop_log : bool
+        If True, return the drop log.
+
+        .. versionadded:: 0.15
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -383,17 +394,13 @@ def compute_proj_eog(raw, raw_event=None, tmin=-0.2, tmax=0.2,
         Computed SSP projectors.
     eog_events: ndarray
         Detected EOG events.
+    drop_log : list
+        The drop log, if requested.
     """
-    if copy is True:
-        raw = raw.copy()
-    projs, eog_events = _compute_exg_proj('EOG', raw, raw_event, tmin, tmax,
-                                          n_grad, n_mag, n_eeg, l_freq, h_freq,
-                                          average, filter_length, n_jobs,
-                                          ch_name, reject, flat, bads, avg_ref,
-                                          no_proj, event_id, eog_l_freq,
-                                          eog_h_freq, tstart,
-                                          qrs_threshold='auto',
-                                          filter_method=filter_method,
-                                          iir_params=iir_params)
-
-    return projs, eog_events
+    raw = raw.copy() if copy else raw
+    return _compute_exg_proj(
+        'EOG', raw, raw_event, tmin, tmax, n_grad, n_mag, n_eeg,
+        l_freq, h_freq, average, filter_length, n_jobs, ch_name, reject, flat,
+        bads, avg_ref, no_proj, event_id, eog_l_freq, eog_h_freq, tstart,
+        qrs_threshold='auto', filter_method=filter_method,
+        iir_params=iir_params, return_drop_log=return_drop_log)
diff --git a/mne/preprocessing/stim.py b/mne/preprocessing/stim.py
index 9728b7d..5e36808 100644
--- a/mne/preprocessing/stim.py
+++ b/mne/preprocessing/stim.py
@@ -4,16 +4,16 @@
 
 import numpy as np
 from ..evoked import Evoked
-from ..epochs import _BaseEpochs
-from ..io import _BaseRaw
+from ..epochs import BaseEpochs
+from ..io import BaseRaw
 from ..event import find_events
 
-from ..io.pick import pick_channels
-from ..utils import _check_copy_dep
+from ..io.pick import _pick_data_channels
+from ..utils import _check_preload
 
 
 def _get_window(start, end):
-    """Return window which has length as much as parameter start - end"""
+    """Return window which has length as much as parameter start - end."""
     from scipy.signal import hann
     window = 1 - np.r_[hann(4)[:2],
                        np.ones(np.abs(end - start) - 4),
@@ -21,20 +21,12 @@ def _get_window(start, end):
     return window
 
 
-def _check_preload(inst):
-    """Check if inst.preload is False. If it is False, raising error"""
-    if inst.preload is False:
-        raise RuntimeError('Modifying data of Instance is only supported '
-                           'when preloading is used. Use preload=True '
-                           '(or string) in the constructor.')
-
-
 def _fix_artifact(data, window, picks, first_samp, last_samp, mode):
-    """Modify original data by using parameter data"""
+    """Modify original data by using parameter data."""
     from scipy.interpolate import interp1d
     if mode == 'linear':
         x = np.array([first_samp, last_samp])
-        f = interp1d(x, data[:, (first_samp, last_samp)])
+        f = interp1d(x, data[:, (first_samp, last_samp)][picks])
         xnew = np.arange(first_samp, last_samp)
         interp_data = f(xnew)
         data[picks, first_samp:last_samp] = interp_data
@@ -44,8 +36,11 @@ def _fix_artifact(data, window, picks, first_samp, last_samp, mode):
 
 
 def fix_stim_artifact(inst, events=None, event_id=None, tmin=0.,
-                      tmax=0.01, mode='linear', stim_channel=None, copy=None):
-    """Eliminate stimulation's artifacts from instance
+                      tmax=0.01, mode='linear', stim_channel=None):
+    """Eliminate stimulation's artifacts from instance.
+
+    .. note:: This function operates in-place, consider passing
+              ``inst.copy()`` if this is not desired.
 
     Parameters
     ----------
@@ -66,8 +61,6 @@ def fix_stim_artifact(inst, events=None, event_id=None, tmin=0.,
         'window' applies a (1 - hanning) window.
     stim_channel : str | None
         Stim channel to use.
-    copy : bool
-        If True, data will be copied. Else data may be modified in place.
 
     Returns
     -------
@@ -76,7 +69,6 @@ def fix_stim_artifact(inst, events=None, event_id=None, tmin=0.,
     """
     if mode not in ('linear', 'window'):
         raise ValueError("mode has to be 'linear' or 'window' (got %s)" % mode)
-    inst = _check_copy_dep(inst, copy)
     s_start = int(np.ceil(inst.info['sfreq'] * tmin))
     s_end = int(np.ceil(inst.info['sfreq'] * tmax))
     if (mode == "window") and (s_end - s_start) < 4:
@@ -85,11 +77,10 @@ def fix_stim_artifact(inst, events=None, event_id=None, tmin=0.,
     window = None
     if mode == 'window':
         window = _get_window(s_start, s_end)
-    ch_names = inst.info['ch_names']
-    picks = pick_channels(ch_names, ch_names)
+    picks = _pick_data_channels(inst.info)
 
-    if isinstance(inst, _BaseRaw):
-        _check_preload(inst)
+    _check_preload(inst, 'fix_stim_artifact')
+    if isinstance(inst, BaseRaw):
         if events is None:
             events = find_events(inst, stim_channel=stim_channel)
         if len(events) == 0:
@@ -105,8 +96,7 @@ def fix_stim_artifact(inst, events=None, event_id=None, tmin=0.,
             last_samp = int(event_idx) - inst.first_samp + s_end
             _fix_artifact(data, window, picks, first_samp, last_samp, mode)
 
-    elif isinstance(inst, _BaseEpochs):
-        _check_preload(inst)
+    elif isinstance(inst, BaseEpochs):
         if inst.reject is not None:
             raise RuntimeError('Reject is already applied. Use reject=None '
                                'in the constructor.')
diff --git a/mne/preprocessing/tests/test_ecg.py b/mne/preprocessing/tests/test_ecg.py
index 165b9db..3e017da 100644
--- a/mne/preprocessing/tests/test_ecg.py
+++ b/mne/preprocessing/tests/test_ecg.py
@@ -16,7 +16,8 @@ proj_fname = op.join(data_path, 'test-proj.fif')
 
 def test_find_ecg():
     """Test find ECG peaks."""
-    raw = read_raw_fif(raw_fname, add_eeg_ref=False)
+    # Test if ECG analysis will work on data that is not preloaded
+    raw = read_raw_fif(raw_fname, preload=False)
 
     # once with mag-trick
     # once with characteristic channel
@@ -33,7 +34,10 @@ def test_find_ecg():
         eog=False, ecg=True, emg=False, ref_meg=False,
         exclude='bads')
 
-    raw.load_data()
+    # There should be no ECG channels, or else preloading will not be
+    # tested
+    assert_true('ecg' not in raw)
+
     ecg_epochs = create_ecg_epochs(raw, picks=picks, keep_ecg=True)
     assert_equal(len(ecg_epochs.events), n_events)
     assert_true('ECG-SYN' not in raw.ch_names)
@@ -55,4 +59,5 @@ def test_find_ecg():
     assert_true(len(w) == 1 and 'unit for channel' in str(w[0].message))
     create_ecg_epochs(raw)
 
+
 run_tests_if_main()
diff --git a/mne/preprocessing/tests/test_eeglab_infomax.py b/mne/preprocessing/tests/test_eeglab_infomax.py
index 90ffcd8..0819fcd 100644
--- a/mne/preprocessing/tests/test_eeglab_infomax.py
+++ b/mne/preprocessing/tests/test_eeglab_infomax.py
@@ -1,8 +1,8 @@
 import os.path as op
-import warnings
 
 import numpy as np
 from numpy.testing import assert_almost_equal
+import pytest
 
 from scipy.linalg import svd, pinv
 import scipy.io as sio
@@ -10,7 +10,7 @@ import scipy.io as sio
 from mne.io import read_raw_fif
 from mne import pick_types
 from mne.preprocessing.infomax_ import infomax
-from mne.utils import random_permutation, slow_test
+from mne.utils import random_permutation, run_tests_if_main
 from mne.datasets import testing
 
 base_dir = op.join(op.dirname(__file__), 'data')
@@ -22,7 +22,7 @@ def generate_data_for_comparing_against_eeglab_infomax(ch_type, random_state):
     data_dir = op.join(testing.data_path(download=False), 'MEG', 'sample')
     raw_fname = op.join(data_dir, 'sample_audvis_trunc_raw.fif')
 
-    raw = read_raw_fif(raw_fname, preload=True, add_eeg_ref=False)
+    raw = read_raw_fif(raw_fname, preload=True)
 
     if ch_type == 'eeg':
         picks = pick_types(raw.info, meg=False, eeg=True, exclude='bads')
@@ -35,13 +35,10 @@ def generate_data_for_comparing_against_eeglab_infomax(ch_type, random_state):
     idx_perm = random_permutation(picks.shape[0], random_state)
     picks = picks[idx_perm[:number_of_channels_to_use]]
 
-    with warnings.catch_warnings(record=True):  # deprecated params
-        raw.filter(1, 45, picks=picks)
-    # Eventually we will need to add these, but for now having none of
-    # them is a nice deprecation sanity check.
-    #           filter_length='10s',
-    #           l_trans_bandwidth=0.5, h_trans_bandwidth=0.5,
-    #           phase='zero-double', fir_window='hann')  # use the old way
+    raw.filter(1, 45, picks=picks, filter_length='10s',
+               l_trans_bandwidth=0.5, h_trans_bandwidth=0.5,
+               phase='zero-double', fir_window='hann',
+               fir_design='firwin2')  # use the old way
     X = raw[picks, :][0][:, ::20]
 
     # Subtract the mean
@@ -62,7 +59,7 @@ def generate_data_for_comparing_against_eeglab_infomax(ch_type, random_state):
     return Y
 
 
- at slow_test
+ at pytest.mark.slowtest
 @testing.requires_testing_data
 def test_mne_python_vs_eeglab():
     """ Test eeglab vs mne_python infomax code."""
@@ -179,3 +176,5 @@ def test_mne_python_vs_eeglab():
                                                unmixing_eeglab))
 
             assert_almost_equal(maximum_difference, 1e-12, decimal=10)
+
+run_tests_if_main()
diff --git a/mne/preprocessing/tests/test_eog.py b/mne/preprocessing/tests/test_eog.py
index eb7afa3..bc4dd30 100644
--- a/mne/preprocessing/tests/test_eog.py
+++ b/mne/preprocessing/tests/test_eog.py
@@ -1,6 +1,7 @@
 import os.path as op
 from nose.tools import assert_true
 
+from mne import Annotations
 from mne.io import read_raw_fif
 from mne.preprocessing.eog import find_eog_events
 
@@ -12,7 +13,11 @@ proj_fname = op.join(data_path, 'test-proj.fif')
 
 def test_find_eog():
     """Test find EOG peaks."""
-    raw = read_raw_fif(raw_fname, add_eeg_ref=False)
+    raw = read_raw_fif(raw_fname)
+    raw.annotations = Annotations([14, 21], [1, 1], 'BAD_blink')
     events = find_eog_events(raw)
-    n_events = len(events)
-    assert_true(n_events == 4)
+    assert_true(len(events) == 4)
+    assert_true(not all(events[:, 0] < 29000))
+
+    events = find_eog_events(raw, reject_by_annotation=True)
+    assert_true(all(events[:, 0] < 29000))
diff --git a/mne/preprocessing/tests/test_ica.py b/mne/preprocessing/tests/test_ica.py
index a15e3e2..f3b3155 100644
--- a/mne/preprocessing/tests/test_ica.py
+++ b/mne/preprocessing/tests/test_ica.py
@@ -9,30 +9,32 @@ import os
 import os.path as op
 import warnings
 
-from nose.tools import assert_true, assert_raises, assert_equal, assert_false
+from nose.tools import (assert_true, assert_raises, assert_equal, assert_false,
+                        assert_not_equal, assert_is_none)
+import pytest
 import numpy as np
 from numpy.testing import (assert_array_almost_equal, assert_array_equal,
                            assert_allclose)
 from scipy import stats
 from itertools import product
 
-from mne import Epochs, read_events, pick_types, create_info, EpochsArray
+from mne import (Epochs, read_events, pick_types, create_info, EpochsArray,
+                 EvokedArray, Annotations)
 from mne.cov import read_cov
 from mne.preprocessing import (ICA, ica_find_ecg_events, ica_find_eog_events,
                                read_ica, run_ica)
-from mne.preprocessing.ica import (get_score_funcs, corrmap, _get_ica_map,
-                                   _ica_explained_variance, _sort_components)
+from mne.preprocessing.ica import (get_score_funcs, corrmap, _sort_components,
+                                   _ica_explained_variance)
 from mne.io import read_raw_fif, Info, RawArray
 from mne.io.meas_info import _kind_dict
 from mne.io.pick import _DATA_CH_TYPES_SPLIT
 from mne.tests.common import assert_naming
-from mne.utils import (catch_logging, _TempDir, requires_sklearn, slow_test,
+from mne.utils import (catch_logging, _TempDir, requires_sklearn,
                        run_tests_if_main)
 
 # Set our plotters to test mode
 import matplotlib
 matplotlib.use('Agg')  # for testing don't use X server
-import matplotlib.pyplot as plt  # noqa
 
 warnings.simplefilter('always')  # enable b/c these tests throw warnings
 
@@ -48,7 +50,7 @@ score_funcs_unsuited = ['pointbiserialr', 'ansari']
 try:
     from sklearn.utils.validation import NonBLASDotWarning
     warnings.simplefilter('error', NonBLASDotWarning)
-except:
+except Exception:
     pass
 
 
@@ -56,19 +58,19 @@ except:
 def test_ica_full_data_recovery():
     """Test recovery of full data when no source is rejected."""
     # Most basic recovery
-    raw = read_raw_fif(raw_fname, add_eeg_ref=False)
-    raw.crop(0.5, stop, copy=False).load_data()
+    raw = read_raw_fif(raw_fname).crop(0.5, stop).load_data()
     events = read_events(event_name)
     picks = pick_types(raw.info, meg=True, stim=False, ecg=False,
                        eog=False, exclude='bads')[:10]
     with warnings.catch_warnings(record=True):  # bad proj
         epochs = Epochs(raw, events[:4], event_id, tmin, tmax, picks=picks,
-                        baseline=(None, 0), preload=True, add_eeg_ref=False)
+                        baseline=(None, 0), preload=True)
     evoked = epochs.average()
     n_channels = 5
     data = raw._data[:n_channels].copy()
     data_epochs = epochs.get_data()
     data_evoked = evoked.data
+    raw.annotations = Annotations([0.5], [0.5], ['BAD'])
     for method in ['fastica']:
         stuff = [(2, n_channels, True), (2, n_channels // 2, False)]
         for n_components, n_pca_components, ok in stuff:
@@ -115,8 +117,7 @@ def test_ica_full_data_recovery():
 def test_ica_rank_reduction():
     """Test recovery ICA rank reduction."""
     # Most basic recovery
-    raw = read_raw_fif(raw_fname, add_eeg_ref=False)
-    raw.crop(0.5, stop, copy=False).load_data()
+    raw = read_raw_fif(raw_fname).crop(0.5, stop).load_data()
     picks = pick_types(raw.info, meg=True, stim=False, ecg=False,
                        eog=False, exclude='bads')[:10]
     n_components = 5
@@ -143,8 +144,7 @@ def test_ica_rank_reduction():
 @requires_sklearn
 def test_ica_reset():
     """Test ICA resetting."""
-    raw = read_raw_fif(raw_fname, add_eeg_ref=False)
-    raw.crop(0.5, stop, copy=False).load_data()
+    raw = read_raw_fif(raw_fname).crop(0.5, stop).load_data()
     picks = pick_types(raw.info, meg=True, stim=False, ecg=False,
                        eog=False, exclude='bads')[:10]
 
@@ -158,30 +158,30 @@ def test_ica_reset():
         'pca_explained_variance_',
         'pca_mean_'
     )
-    with warnings.catch_warnings(record=True):
+    with warnings.catch_warnings(record=True):  # convergence
         ica = ICA(
             n_components=3, max_pca_components=3, n_pca_components=3,
             method='fastica', max_iter=1).fit(raw, picks=picks)
 
     assert_true(all(hasattr(ica, attr) for attr in run_time_attrs))
+    assert_not_equal(ica.labels_, None)
     ica._reset()
     assert_true(not any(hasattr(ica, attr) for attr in run_time_attrs))
+    assert_not_equal(ica.labels_, None)
 
 
 @requires_sklearn
 def test_ica_core():
     """Test ICA on raw and epochs."""
-    raw = read_raw_fif(raw_fname, add_eeg_ref=False)
-    raw.crop(1.5, stop, copy=False).load_data()
-    picks = pick_types(raw.info, meg=True, stim=False, ecg=False,
-                       eog=False, exclude='bads')
+    raw = read_raw_fif(raw_fname).crop(1.5, stop).load_data()
+
     # XXX. The None cases helped revealing bugs but are time consuming.
     test_cov = read_cov(test_cov_name)
     events = read_events(event_name)
     picks = pick_types(raw.info, meg=True, stim=False, ecg=False,
                        eog=False, exclude='bads')
     epochs = Epochs(raw, events[:4], event_id, tmin, tmax, picks=picks,
-                    baseline=(None, 0), preload=True, add_eeg_ref=False)
+                    baseline=(None, 0), preload=True)
     noise_cov = [None, test_cov]
     # removed None cases to speed up...
     n_components = [2, 1.0]  # for future dbg add cases
@@ -210,7 +210,7 @@ def test_ica_core():
         assert_raises(RuntimeError, ica.get_sources, epochs)
 
         # test decomposition
-        with warnings.catch_warnings(record=True):
+        with warnings.catch_warnings(record=True):  # convergence
             ica.fit(raw, picks=pcks, start=start, stop=stop)
             repr(ica)  # to test repr
         assert_true('mag' in ica)  # should now work without error
@@ -221,7 +221,12 @@ def test_ica_core():
             ica.fit(raw, picks=pcks, start=start, stop=stop)
         assert_array_almost_equal(unmixing1, ica.unmixing_matrix_)
 
-        sources = ica.get_sources(raw)[:, :][0]
+        raw_sources = ica.get_sources(raw)
+        # test for #3804
+        assert_equal(raw_sources._filenames, [None])
+        print(raw_sources)
+
+        sources = raw_sources[:, :][0]
         assert_true(sources.shape[0] == ica.n_components_)
 
         # test preload filter
@@ -270,14 +275,15 @@ def test_ica_core():
     assert_raises(ValueError, ica.apply, offender)
 
 
- at slow_test
+ at pytest.mark.slowtest
 @requires_sklearn
 def test_ica_additional():
     """Test additional ICA functionality."""
+    import matplotlib.pyplot as plt
     tempdir = _TempDir()
     stop2 = 500
-    raw = read_raw_fif(raw_fname, add_eeg_ref=False)
-    raw.crop(1.5, stop, copy=False).load_data()
+    raw = read_raw_fif(raw_fname).crop(1.5, stop).load_data()
+    raw.annotations = Annotations([0.5], [0.5], ['BAD'])
     # XXX This breaks the tests :(
     # raw.info['bads'] = [raw.ch_names[1]]
     test_cov = read_cov(test_cov_name)
@@ -285,7 +291,7 @@ def test_ica_additional():
     picks = pick_types(raw.info, meg=True, stim=False, ecg=False,
                        eog=False, exclude='bads')
     epochs = Epochs(raw, events[:4], event_id, tmin, tmax, picks=picks,
-                    baseline=(None, 0), preload=True, add_eeg_ref=False)
+                    baseline=(None, 0), preload=True)
     # test if n_components=None works
     with warnings.catch_warnings(record=True):
         ica = ICA(n_components=None,
@@ -296,7 +302,7 @@ def test_ica_additional():
     picks2 = pick_types(raw.info, meg=True, stim=False, ecg=False,
                         eog=True, exclude='bads')
     epochs_eog = Epochs(raw, events[:4], event_id, tmin, tmax, picks=picks2,
-                        baseline=(None, 0), preload=True, add_eeg_ref=False)
+                        baseline=(None, 0), preload=True)
 
     test_cov2 = test_cov.copy()
     ica = ICA(noise_cov=test_cov2, n_components=3, max_pca_components=4,
@@ -310,9 +316,16 @@ def test_ica_additional():
     ica = ICA(n_components=3, max_pca_components=4,
               n_pca_components=4)
     assert_raises(RuntimeError, ica.save, '')
+
     with warnings.catch_warnings(record=True):
         ica.fit(raw, picks=[1, 2, 3, 4, 5], start=start, stop=stop2)
 
+    # check passing a ch_name to find_bads_ecg
+    with warnings.catch_warnings(record=True):  # filter length
+        _, scores_1 = ica.find_bads_ecg(raw)
+        _, scores_2 = ica.find_bads_ecg(raw, raw.ch_names[1])
+    assert_false(scores_1[0] == scores_2[0])
+
     # test corrmap
     ica2 = ica.copy()
     ica3 = ica.copy()
@@ -321,10 +334,15 @@ def test_ica_additional():
     corrmap([ica, ica2], (0, 0), threshold=2, plot=False, show=False)
     assert_true(ica.labels_["blinks"] == ica2.labels_["blinks"])
     assert_true(0 in ica.labels_["blinks"])
-    template = _get_ica_map(ica)[0]
+    # test retrieval of component maps as arrays
+    components = ica.get_components()
+    template = components[:, 0]
+    EvokedArray(components, ica.info, tmin=0.).plot_topomap([0])
+
     corrmap([ica, ica3], template, threshold='auto', label='blinks', plot=True,
             ch_type="mag")
     assert_true(ica2.labels_["blinks"] == ica3.labels_["blinks"])
+
     plt.close('all')
 
     # test warnings on bad filenames
@@ -398,15 +416,12 @@ def test_ica_additional():
 
         # test filtering
         d1 = ica_raw._data[0].copy()
-        ica_raw.filter(4, 20, l_trans_bandwidth='auto',
-                       h_trans_bandwidth='auto', filter_length='auto',
-                       phase='zero', fir_window='hamming')
+        ica_raw.filter(4, 20, fir_design='firwin2')
         assert_equal(ica_raw.info['lowpass'], 20.)
         assert_equal(ica_raw.info['highpass'], 4.)
         assert_true((d1 != ica_raw._data[0]).any())
         d1 = ica_raw._data[0].copy()
-        ica_raw.notch_filter([10], filter_length='auto', trans_bandwidth=10,
-                             phase='zero', fir_window='hamming')
+        ica_raw.notch_filter([10], trans_bandwidth=10, fir_design='firwin')
         assert_true((d1 != ica_raw._data[0]).any())
 
         ica.n_pca_components = 2
@@ -451,7 +466,7 @@ def test_ica_additional():
         assert_array_almost_equal(_raw1[:, :][0], _raw2[:, :][0])
 
     os.remove(test_ica_fname)
-    # check scrore funcs
+    # check score funcs
     for name, func in get_score_funcs().items():
         if name in score_funcs_unsuited:
             continue
@@ -472,6 +487,11 @@ def test_ica_additional():
         ica.detect_artifacts(raw, start_find=0, stop_find=50, ecg_ch=ch_name,
                              eog_ch=ch_name, skew_criterion=idx,
                              var_criterion=idx, kurt_criterion=idx)
+
+    evoked = epochs.average()
+    evoked_data = evoked.data.copy()
+    raw_data = raw[:][0].copy()
+    epochs_data = epochs.get_data().copy()
     with warnings.catch_warnings(record=True):
         idx, scores = ica.find_bads_ecg(raw, method='ctps')
         assert_equal(len(scores), ica.n_components_)
@@ -481,19 +501,32 @@ def test_ica_additional():
         idx, scores = ica.find_bads_eog(raw)
         assert_equal(len(scores), ica.n_components_)
 
-        ica.labels_ = None
         idx, scores = ica.find_bads_ecg(epochs, method='ctps')
+
         assert_equal(len(scores), ica.n_components_)
         assert_raises(ValueError, ica.find_bads_ecg, epochs.average(),
                       method='ctps')
         assert_raises(ValueError, ica.find_bads_ecg, raw,
                       method='crazy-coupling')
 
+        idx, scores = ica.find_bads_eog(raw)
+        assert_equal(len(scores), ica.n_components_)
+
         raw.info['chs'][raw.ch_names.index('EOG 061') - 1]['kind'] = 202
         idx, scores = ica.find_bads_eog(raw)
         assert_true(isinstance(scores, list))
         assert_equal(len(scores[0]), ica.n_components_)
 
+        idx, scores = ica.find_bads_eog(evoked, ch_name='MEG 1441')
+        assert_equal(len(scores), ica.n_components_)
+
+        idx, scores = ica.find_bads_ecg(evoked, method='correlation')
+        assert_equal(len(scores), ica.n_components_)
+
+    assert_array_equal(raw_data, raw[:][0])
+    assert_array_equal(epochs_data, epochs.get_data())
+    assert_array_equal(evoked_data, evoked.data)
+
     # check score funcs
     for name, func in get_score_funcs().items():
         if name in score_funcs_unsuited:
@@ -531,13 +564,13 @@ def test_ica_additional():
     # Test ica fiff export
     ica_raw = ica.get_sources(raw, start=0, stop=100)
     assert_true(ica_raw.last_samp - ica_raw.first_samp == 100)
-    assert_true(len(ica_raw._filenames) == 0)  # API consistency
+    assert_equal(len(ica_raw._filenames), 1)  # API consistency
     ica_chans = [ch for ch in ica_raw.ch_names if 'ICA' in ch]
     assert_true(ica.n_components_ == len(ica_chans))
     test_ica_fname = op.join(op.abspath(op.curdir), 'test-ica_raw.fif')
     ica.n_components = np.int32(ica.n_components)
     ica_raw.save(test_ica_fname, overwrite=True)
-    ica_raw2 = read_raw_fif(test_ica_fname, preload=True, add_eeg_ref=False)
+    ica_raw2 = read_raw_fif(test_ica_fname, preload=True)
     assert_allclose(ica_raw._data, ica_raw2._data, rtol=1e-5, atol=1e-4)
     ica_raw2.close()
     os.remove(test_ica_fname)
@@ -558,12 +591,23 @@ def test_ica_additional():
         ncomps_ = ica._check_n_pca_components(ncomps)
         assert_true(ncomps_ == expected)
 
+    ica = ICA()
+    ica.fit(raw, picks=picks[:5])
+    with warnings.catch_warnings(record=True):  # filter length
+        ica.find_bads_ecg(raw)
+    ica.find_bads_eog(epochs, ch_name='MEG 0121')
+    assert_array_equal(raw_data, raw[:][0])
+
+    raw.drop_channels(['MEG 0122'])
+    with warnings.catch_warnings(record=True):  # filter length
+        assert_raises(RuntimeError, ica.find_bads_eog, raw)
+        assert_raises(RuntimeError, ica.find_bads_ecg, raw)
+
 
 @requires_sklearn
 def test_run_ica():
     """Test run_ica function."""
-    raw = read_raw_fif(raw_fname, add_eeg_ref=False)
-    raw.crop(1.5, stop, copy=False).load_data()
+    raw = read_raw_fif(raw_fname).crop(1.5, stop).load_data()
     params = []
     params += [(None, -1, slice(2), [0, 1])]  # varicance, kurtosis idx
     params += [(None, 'MEG 1531')]  # ECG / EOG channel params
@@ -578,8 +622,7 @@ def test_run_ica():
 @requires_sklearn
 def test_ica_reject_buffer():
     """Test ICA data raw buffer rejection."""
-    raw = read_raw_fif(raw_fname, add_eeg_ref=False)
-    raw.crop(1.5, stop, copy=False).load_data()
+    raw = read_raw_fif(raw_fname).crop(1.5, stop).load_data()
     picks = pick_types(raw.info, meg=True, stim=False, ecg=False,
                        eog=False, exclude='bads')
     ica = ICA(n_components=3, max_pca_components=4, n_pca_components=4)
@@ -587,7 +630,7 @@ def test_ica_reject_buffer():
     with catch_logging() as drop_log:
         with warnings.catch_warnings(record=True):
             ica.fit(raw, picks[:5], reject=dict(mag=2.5e-12), decim=2,
-                    tstep=0.01, verbose=True)
+                    tstep=0.01, verbose=True, reject_by_annotation=False)
         assert_true(raw._data[:5, ::2].shape[1] - 4 == ica.n_samples_)
     log = [l for l in drop_log.getvalue().split('\n') if 'detected' in l]
     assert_equal(len(log), 1)
@@ -596,8 +639,7 @@ def test_ica_reject_buffer():
 @requires_sklearn
 def test_ica_twice():
     """Test running ICA twice."""
-    raw = read_raw_fif(raw_fname, add_eeg_ref=False)
-    raw.crop(1.5, stop, copy=False).load_data()
+    raw = read_raw_fif(raw_fname).crop(1.5, stop).load_data()
     picks = pick_types(raw.info, meg='grad', exclude='bads')
     n_components = 0.9
     max_pca_components = None
@@ -656,13 +698,12 @@ def test_bad_channels():
 @requires_sklearn
 def test_eog_channel():
     """Test that EOG channel is included when performing ICA."""
-    raw = read_raw_fif(raw_fname, preload=True, add_eeg_ref=False)
+    raw = read_raw_fif(raw_fname, preload=True)
     events = read_events(event_name)
     picks = pick_types(raw.info, meg=True, stim=True, ecg=False,
                        eog=True, exclude='bads')
     epochs = Epochs(raw, events, event_id, tmin, tmax, picks=picks,
-                    baseline=(None, 0), preload=True,
-                    add_eeg_ref=False)
+                    baseline=(None, 0), preload=True)
     n_components = 0.9
     ica = ICA(n_components=n_components, method='fastica')
     # Test case for MEG and EOG data. Should have EOG channel
@@ -681,4 +722,95 @@ def test_eog_channel():
         ica.fit(inst, picks=picks1)
         assert_false(any('EOG' in ch for ch in ica.ch_names))
 
+
+ at requires_sklearn
+def test_max_pca_components_none():
+    """Test max_pca_components=None."""
+    raw = read_raw_fif(raw_fname).crop(1.5, stop).load_data()
+    events = read_events(event_name)
+    picks = pick_types(raw.info, eeg=True, meg=False)
+    epochs = Epochs(raw, events, event_id, tmin, tmax, picks=picks,
+                    baseline=(None, 0), preload=True)
+
+    max_pca_components = None
+    n_components = 10
+    random_state = 12345
+
+    tempdir = _TempDir()
+    output_fname = op.join(tempdir, 'test_ica-ica.fif')
+
+    ica = ICA(max_pca_components=max_pca_components,
+              n_components=n_components, random_state=random_state)
+    with warnings.catch_warnings(record=True):  # convergence
+        ica.fit(epochs)
+    ica.save(output_fname)
+
+    ica = read_ica(output_fname)
+
+    # ICA.fit() replaced max_pca_components, which was previously None,
+    # with the appropriate integer value.
+    assert_equal(ica.max_pca_components, epochs.info['nchan'])
+    assert_equal(ica.n_components, 10)
+
+
+ at requires_sklearn
+def test_n_components_none():
+    """Test n_components=None."""
+    raw = read_raw_fif(raw_fname).crop(1.5, stop).load_data()
+    events = read_events(event_name)
+    picks = pick_types(raw.info, eeg=True, meg=False)
+    epochs = Epochs(raw, events, event_id, tmin, tmax, picks=picks,
+                    baseline=(None, 0), preload=True)
+
+    max_pca_components = 10
+    n_components = None
+    random_state = 12345
+
+    tempdir = _TempDir()
+    output_fname = op.join(tempdir, 'test_ica-ica.fif')
+
+    ica = ICA(max_pca_components=max_pca_components,
+              n_components=n_components, random_state=random_state)
+    with warnings.catch_warnings(record=True):  # convergence
+        ica.fit(epochs)
+    ica.save(output_fname)
+
+    ica = read_ica(output_fname)
+
+    # ICA.fit() replaced max_pca_components, which was previously None,
+    # with the appropriate integer value.
+    assert_equal(ica.max_pca_components, 10)
+    assert_is_none(ica.n_components)
+
+
+ at requires_sklearn
+def test_n_components_and_max_pca_components_none():
+    """Test n_components and max_pca_components=None."""
+    raw = read_raw_fif(raw_fname).crop(1.5, stop).load_data()
+    events = read_events(event_name)
+    picks = pick_types(raw.info, eeg=True, meg=False)
+    epochs = Epochs(raw, events, event_id, tmin, tmax, picks=picks,
+                    baseline=(None, 0), preload=True)
+
+    max_pca_components = None
+    n_components = None
+    random_state = 12345
+
+    tempdir = _TempDir()
+    output_fname = op.join(tempdir, 'test_ica-ica.fif')
+
+    ica = ICA(max_pca_components=max_pca_components,
+              n_components=n_components, random_state=random_state)
+    with warnings.catch_warnings(record=True):  # convergence
+        ica.fit(epochs)
+    ica.save(output_fname)
+
+    ica = read_ica(output_fname)
+
+    # ICA.fit() replaced max_pca_components, which was previously None,
+    # with the appropriate integer value.
+    assert_equal(ica.max_pca_components, epochs.info['nchan'])
+    assert_is_none(ica.n_components)
+
+
 run_tests_if_main()
diff --git a/mne/preprocessing/tests/test_maxwell.py b/mne/preprocessing/tests/test_maxwell.py
index 04df920..50c8dc7 100644
--- a/mne/preprocessing/tests/test_maxwell.py
+++ b/mne/preprocessing/tests/test_maxwell.py
@@ -5,12 +5,10 @@
 import os.path as op
 import warnings
 import numpy as np
-import sys
-import scipy
+
 from numpy.testing import assert_equal, assert_allclose
+import pytest
 from nose.tools import assert_true, assert_raises
-from nose.plugins.skip import SkipTest
-from distutils.version import LooseVersion
 
 from mne import compute_raw_covariance, pick_types
 from mne.chpi import read_head_pos, filter_chpi
@@ -18,15 +16,15 @@ from mne.forward import _prep_meg_channels
 from mne.cov import _estimate_rank_meeg_cov
 from mne.datasets import testing
 from mne.io import (read_raw_fif, proc_history, read_info, read_raw_bti,
-                    read_raw_kit, _BaseRaw)
+                    read_raw_kit, BaseRaw)
 from mne.preprocessing.maxwell import (
     maxwell_filter, _get_n_moments, _sss_basis_basic, _sh_complex_to_real,
     _sh_real_to_complex, _sh_negate, _bases_complex_to_real, _trans_sss_basis,
-    _bases_real_to_complex, _sph_harm, _prep_mf_coils)
+    _bases_real_to_complex, _prep_mf_coils)
+from mne.fixes import _get_sph_harm
 from mne.tests.common import assert_meg_snr
-from mne.utils import (_TempDir, run_tests_if_main, slow_test, catch_logging,
+from mne.utils import (_TempDir, run_tests_if_main, catch_logging,
                        requires_version, object_diff, buggy_mkl_svd)
-from mne.externals.six import PY3
 
 warnings.simplefilter('always')  # Always throw warnings
 
@@ -119,16 +117,15 @@ def _assert_n_free(raw_sss, lower, upper=None):
 
 def read_crop(fname, lims=(0, None)):
     """Read and crop."""
-    return read_raw_fif(fname, allow_maxshield='yes',
-                        add_eeg_ref=False).crop(*lims)
+    return read_raw_fif(fname, allow_maxshield='yes').crop(*lims)
 
 
- at slow_test
+ at pytest.mark.slowtest
 @testing.requires_testing_data
 def test_movement_compensation():
     """Test movement compensation."""
     temp_dir = _TempDir()
-    lims = (0, 4, False)
+    lims = (0, 4)
     raw = read_crop(raw_fname, lims).load_data()
     head_pos = read_head_pos(pos_fname)
 
@@ -196,18 +193,20 @@ def test_movement_compensation():
     head_pos_bad = head_pos.copy()
     head_pos_bad[0, 4] = 1.  # off by more than 1 m
     with warnings.catch_warnings(record=True) as w:
-        maxwell_filter(raw, head_pos=head_pos_bad, bad_condition='ignore')
+        maxwell_filter(raw.copy().crop(0, 0.1), head_pos=head_pos_bad,
+                       bad_condition='ignore')
     assert_true(any('greater than 1 m' in str(ww.message) for ww in w))
 
     # make sure numerical error doesn't screw it up, though
     head_pos_bad = head_pos.copy()
     head_pos_bad[0, 0] = raw.first_samp / raw.info['sfreq'] - 5e-4
-    raw_sss_tweak = maxwell_filter(raw, head_pos=head_pos_bad,
-                                   origin=mf_head_origin)
-    assert_meg_snr(raw_sss_tweak, raw_sss, 2., 10., chpi_med_tol=11)
+    raw_sss_tweak = maxwell_filter(
+        raw.copy().crop(0, 0.05), head_pos=head_pos_bad, origin=mf_head_origin)
+    assert_meg_snr(raw_sss_tweak, raw_sss.copy().crop(0, 0.05), 1.4, 8.,
+                   chpi_med_tol=5)
 
 
- at slow_test
+ at pytest.mark.slowtest
 def test_other_systems():
     """Test Maxwell filtering on KIT, BTI, and CTF files."""
     # KIT
@@ -288,26 +287,6 @@ def test_other_systems():
     assert_allclose(raw_sss._data, raw_sss_auto._data)
 
 
-def test_spherical_harmonics():
-    """Test spherical harmonic functions."""
-    from scipy.special import sph_harm
-    az, pol = np.meshgrid(np.linspace(0, 2 * np.pi, 30),
-                          np.linspace(0, np.pi, 20))
-    # As of Oct 16, 2015, Anancoda has a bug in scipy due to old compilers (?):
-    # https://github.com/ContinuumIO/anaconda-issues/issues/479
-    if (PY3 and
-            LooseVersion(scipy.__version__) >= LooseVersion('0.15') and
-            'Continuum Analytics' in sys.version):
-        raise SkipTest('scipy sph_harm bad in Py3k on Anaconda')
-
-    # Test our basic spherical harmonics
-    for degree in range(1, int_order):
-        for order in range(0, degree + 1):
-            sph = _sph_harm(order, degree, az, pol)
-            sph_scipy = sph_harm(order, degree, az, pol)
-            assert_allclose(sph, sph_scipy, atol=1e-7)
-
-
 def test_spherical_conversions():
     """Test spherical harmonic conversions."""
     # Test our real<->complex conversion functions
@@ -315,10 +294,10 @@ def test_spherical_conversions():
                           np.linspace(0, np.pi, 20))
     for degree in range(1, int_order):
         for order in range(0, degree + 1):
-            sph = _sph_harm(order, degree, az, pol)
+            sph = _get_sph_harm()(order, degree, az, pol)
             # ensure that we satisfy the conjugation property
             assert_allclose(_sh_negate(sph, order),
-                            _sph_harm(-order, degree, az, pol))
+                            _get_sph_harm()(-order, degree, az, pol))
             # ensure our conversion functions work
             sph_real_pos = _sh_complex_to_real(sph, order)
             sph_real_neg = _sh_complex_to_real(sph, -order)
@@ -445,6 +424,14 @@ def test_basic():
     assert_raises(ValueError, maxwell_filter, raw, origin='foo')
     assert_raises(ValueError, maxwell_filter, raw, origin=[0] * 4)
     assert_raises(ValueError, maxwell_filter, raw, mag_scale='foo')
+    raw_missing = raw.copy().load_data()
+    raw_missing.info['bads'] = ['MEG0111']
+    raw_missing.pick_types(meg=True)  # will be missing the bad
+    maxwell_filter(raw_missing)
+    with warnings.catch_warnings(record=True) as w:
+        maxwell_filter(raw_missing, calibration=fine_cal_fname)
+    assert_equal(len(w), 1)
+    assert_true('not in data' in str(w[0].message))
 
 
 @testing.requires_testing_data
@@ -493,7 +480,7 @@ def test_maxwell_filter_additional():
     assert_equal(cov_sss_rank, _get_n_moments(int_order))
 
 
- at slow_test
+ at pytest.mark.slowtest
 @testing.requires_testing_data
 def test_bads_reconstruction():
     """Test Maxwell filter reconstruction of bad channels."""
@@ -504,9 +491,10 @@ def test_bads_reconstruction():
     assert_meg_snr(raw_sss, read_crop(sss_bad_recon_fname), 300.)
 
 
+ at buggy_mkl_svd
 @requires_svd_convergence
 @testing.requires_testing_data
-def test_spatiotemporal_maxwell():
+def test_spatiotemporal():
     """Test Maxwell filter (tSSS) spatiotemporal processing."""
     # Load raw testing data
     raw = read_crop(raw_fname)
@@ -514,10 +502,13 @@ def test_spatiotemporal_maxwell():
     # Test that window is less than length of data
     assert_raises(ValueError, maxwell_filter, raw, st_duration=1000.)
 
-    # Check both 4 and 10 seconds because Elekta handles them differently
-    # This is to ensure that std/non-std tSSS windows are correctly handled
-    st_durations = [4., 10.]
-    tols = [325., 200.]
+    # We could check both 4 and 10 seconds because Elekta handles them
+    # differently (to ensure that std/non-std tSSS windows are correctly
+    # handled), but the 4-sec case should hopefully be sufficient.
+    st_durations = [4.]  # , 10.]
+    tols = [325.]  # , 200.]
+    kwargs = dict(origin=mf_head_origin, regularize=None,
+                  bad_condition='ignore')
     for st_duration, tol in zip(st_durations, tols):
         # Load tSSS data depending on st_duration and get data
         tSSS_fname = op.join(sss_path,
@@ -526,27 +517,15 @@ def test_spatiotemporal_maxwell():
         # Because Elekta's tSSS sometimes(!) lumps the tail window of data
         # onto the previous buffer if it's shorter than st_duration, we have to
         # crop the data here to compensate for Elekta's tSSS behavior.
-        if st_duration == 10.:
-            tsss_bench.crop(0, st_duration, copy=False)
+        # if st_duration == 10.:
+        #     tsss_bench.crop(0, st_duration)
+        #     raw.crop(0, st_duration)
 
         # Test sss computation at the standard head origin. Same cropping issue
         # as mentioned above.
-        if st_duration == 10.:
-            raw_tsss = maxwell_filter(raw.crop(0, st_duration),
-                                      origin=mf_head_origin,
-                                      st_duration=st_duration, regularize=None,
-                                      bad_condition='ignore')
-        else:
-            raw_tsss = maxwell_filter(raw, st_duration=st_duration,
-                                      origin=mf_head_origin, regularize=None,
-                                      bad_condition='ignore', verbose=True)
-            raw_tsss_2 = maxwell_filter(raw, st_duration=st_duration,
-                                        origin=mf_head_origin, regularize=None,
-                                        bad_condition='ignore', st_fixed=False,
-                                        verbose=True)
-            assert_meg_snr(raw_tsss, raw_tsss_2, 100., 1000.)
-            assert_equal(raw_tsss.estimate_rank(), 140)
-            assert_equal(raw_tsss_2.estimate_rank(), 140)
+        raw_tsss = maxwell_filter(
+            raw, st_duration=st_duration, **kwargs)
+        assert_equal(raw_tsss.estimate_rank(), 140)
         assert_meg_snr(raw_tsss, tsss_bench, tol)
         py_st = raw_tsss.info['proc_history'][0]['max_info']['max_st']
         assert_true(len(py_st) > 0)
@@ -558,55 +537,57 @@ def test_spatiotemporal_maxwell():
                   st_correlation=0.)
 
 
+ at pytest.mark.slowtest
 @requires_svd_convergence
 @testing.requires_testing_data
 def test_spatiotemporal_only():
     """Test tSSS-only processing."""
     # Load raw testing data
-    raw = read_crop(raw_fname, (0, 2)).load_data()
-    picks = pick_types(raw.info, meg='mag', exclude=())
-    power = np.sqrt(np.sum(raw[picks][0] ** 2))
+    tmax = 0.5
+    raw = read_crop(raw_fname, (0, tmax)).load_data()
+    picks = pick_types(raw.info, meg=True, exclude='bads')[::2]
+    raw.pick_channels([raw.ch_names[pick] for pick in picks])
+    mag_picks = pick_types(raw.info, meg='mag', exclude=())
+    power = np.sqrt(np.sum(raw[mag_picks][0] ** 2))
     # basics
-    raw_tsss = maxwell_filter(raw, st_duration=1., st_only=True)
-    assert_equal(raw_tsss.estimate_rank(), 366)
-    _assert_shielding(raw_tsss, power, 10)
-    # temporal proj will actually reduce spatial DOF with small windows!
-    raw_tsss = maxwell_filter(raw, st_duration=0.1, st_only=True)
-    assert_true(raw_tsss.estimate_rank() < 350)
-    _assert_shielding(raw_tsss, power, 40)
+    raw_tsss = maxwell_filter(raw, st_duration=tmax / 2., st_only=True)
+    assert_equal(len(raw.info['projs']), len(raw_tsss.info['projs']))
+    assert_equal(raw_tsss.estimate_rank(), len(picks))
+    _assert_shielding(raw_tsss, power, 9)
     # with movement
     head_pos = read_head_pos(pos_fname)
-    raw_tsss = maxwell_filter(raw, st_duration=1., st_only=True,
+    raw_tsss = maxwell_filter(raw, st_duration=tmax / 2., st_only=True,
                               head_pos=head_pos)
-    assert_equal(raw_tsss.estimate_rank(), 366)
-    _assert_shielding(raw_tsss, power, 12)
+    assert_equal(raw_tsss.estimate_rank(), len(picks))
+    _assert_shielding(raw_tsss, power, 9)
     with warnings.catch_warnings(record=True):  # st_fixed False
-        raw_tsss = maxwell_filter(raw, st_duration=1., st_only=True,
+        raw_tsss = maxwell_filter(raw, st_duration=tmax / 2., st_only=True,
                                   head_pos=head_pos, st_fixed=False)
-    assert_equal(raw_tsss.estimate_rank(), 366)
-    _assert_shielding(raw_tsss, power, 12)
+    assert_equal(raw_tsss.estimate_rank(), len(picks))
+    _assert_shielding(raw_tsss, power, 9)
     # should do nothing
-    raw_tsss = maxwell_filter(raw, st_duration=1., st_correlation=1.,
+    raw_tsss = maxwell_filter(raw, st_duration=tmax, st_correlation=1.,
                               st_only=True)
     assert_allclose(raw[:][0], raw_tsss[:][0])
     # degenerate
     assert_raises(ValueError, maxwell_filter, raw, st_only=True)  # no ST
     # two-step process equivalent to single-step process
-    raw_tsss = maxwell_filter(raw, st_duration=1., st_only=True)
+    raw_tsss = maxwell_filter(raw, st_duration=tmax, st_only=True)
     raw_tsss = maxwell_filter(raw_tsss)
-    raw_tsss_2 = maxwell_filter(raw, st_duration=1.)
+    raw_tsss_2 = maxwell_filter(raw, st_duration=tmax)
     assert_meg_snr(raw_tsss, raw_tsss_2, 1e5)
     # now also with head movement, and a bad MEG channel
     assert_equal(len(raw.info['bads']), 0)
-    raw.info['bads'] = ['EEG001', 'MEG2623']
-    raw_tsss = maxwell_filter(raw, st_duration=1., st_only=True,
+    bads = [raw.ch_names[0]]
+    raw.info['bads'] = list(bads)
+    raw_tsss = maxwell_filter(raw, st_duration=tmax, st_only=True,
                               head_pos=head_pos)
-    assert_equal(raw.info['bads'], ['EEG001', 'MEG2623'])
-    assert_equal(raw_tsss.info['bads'], ['EEG001', 'MEG2623'])  # don't reset
+    assert_equal(raw.info['bads'], bads)
+    assert_equal(raw_tsss.info['bads'], bads)  # don't reset
     raw_tsss = maxwell_filter(raw_tsss, head_pos=head_pos)
-    assert_equal(raw_tsss.info['bads'], ['EEG001'])  # do reset MEG bads
-    raw_tsss_2 = maxwell_filter(raw, st_duration=1., head_pos=head_pos)
-    assert_equal(raw_tsss_2.info['bads'], ['EEG001'])
+    assert_equal(raw_tsss.info['bads'], [])  # do reset MEG bads
+    raw_tsss_2 = maxwell_filter(raw, st_duration=tmax, head_pos=head_pos)
+    assert_equal(raw_tsss_2.info['bads'], [])
     assert_meg_snr(raw_tsss, raw_tsss_2, 1e5)
 
 
@@ -632,6 +613,20 @@ def test_fine_calibration():
     assert_allclose(py_cal['cal_chans'], mf_cal['cal_chans'])
     assert_allclose(py_cal['cal_corrs'], mf_cal['cal_corrs'],
                     rtol=1e-3, atol=1e-3)
+    # with missing channels
+    raw_missing = raw.copy().load_data()
+    raw_missing.info['bads'] = ['MEG0111', 'MEG0943']  # 1 mag, 1 grad
+    raw_missing.info._check_consistency()
+    raw_sss_bad = maxwell_filter(
+        raw_missing, calibration=fine_cal_fname, origin=mf_head_origin,
+        regularize=None, bad_condition='ignore')
+    raw_missing.pick_types()  # actually remove bads
+    raw_sss_bad.pick_channels(raw_missing.ch_names)  # remove them here, too
+    with warnings.catch_warnings(record=True):
+        raw_sss_missing = maxwell_filter(
+            raw_missing, calibration=fine_cal_fname, origin=mf_head_origin,
+            regularize=None, bad_condition='ignore')
+    assert_meg_snr(raw_sss_missing, raw_sss_bad, 1000., 10000.)
 
     # Test 3D SSS fine calibration (no equivalent func in MaxFilter yet!)
     # very low SNR as proc differs, eventually we should add a better test
@@ -644,7 +639,7 @@ def test_fine_calibration():
                   calibration=fine_cal_fname)
 
 
- at slow_test
+ at pytest.mark.slowtest
 @testing.requires_testing_data
 def test_regularization():
     """Test Maxwell filter regularization."""
@@ -765,7 +760,7 @@ def test_head_translation():
 def _assert_shielding(raw_sss, erm_power, shielding_factor, meg='mag'):
     """Helper to assert a minimum shielding factor using empty-room power."""
     picks = pick_types(raw_sss.info, meg=meg, ref_meg=False)
-    if isinstance(erm_power, _BaseRaw):
+    if isinstance(erm_power, BaseRaw):
         picks_erm = pick_types(raw_sss.info, meg=meg, ref_meg=False)
         assert_allclose(picks, picks_erm)
         erm_power = np.sqrt((erm_power[picks_erm][0] ** 2).sum())
@@ -777,14 +772,13 @@ def _assert_shielding(raw_sss, erm_power, shielding_factor, meg='mag'):
 
 
 @buggy_mkl_svd
- at slow_test
+ at pytest.mark.slowtest
 @requires_svd_convergence
 @testing.requires_testing_data
 def test_shielding_factor():
     """Test Maxwell filter shielding factor using empty room."""
-    raw_erm = read_crop(erm_fname).load_data()
-    picks = pick_types(raw_erm.info, meg='mag')
-    erm_power = raw_erm[picks][0]
+    raw_erm = read_crop(erm_fname).load_data().pick_types(meg=True)
+    erm_power = raw_erm[pick_types(raw_erm.info, meg='mag')][0]
     erm_power = np.sqrt(np.sum(erm_power * erm_power))
     erm_power_grad = raw_erm[pick_types(raw_erm.info, meg='grad')][0]
     erm_power_grad = np.sqrt(np.sum(erm_power * erm_power))
@@ -895,7 +889,7 @@ def test_shielding_factor():
     _assert_shielding(raw_sss, erm_power, 44)
 
 
- at slow_test
+ at pytest.mark.slowtest
 @requires_svd_convergence
 @testing.requires_testing_data
 def test_all():
@@ -930,7 +924,7 @@ def test_all():
         assert_meg_snr(sss_py, sss_mf, mins[ii], meds[ii], msg=rf)
 
 
- at slow_test
+ at pytest.mark.slowtest
 @requires_svd_convergence
 @testing.requires_testing_data
 def test_triux():
@@ -971,4 +965,12 @@ def test_triux():
                             st_duration=4., verbose=True)
     assert_meg_snr(sss_py, read_crop(tri_sss_st4_fname), 700., 1600)
 
+
+ at testing.requires_testing_data
+def test_MGH_cross_talk():
+    raw = read_crop(raw_fname, (0., 1.))
+    raw_sss = maxwell_filter(raw, cross_talk=ctc_mgh_fname)
+    py_ctc = raw_sss.info['proc_history'][0]['max_info']['sss_ctc']
+    assert_true(len(py_ctc) > 0)
+
 run_tests_if_main()
diff --git a/mne/preprocessing/tests/test_ssp.py b/mne/preprocessing/tests/test_ssp.py
index 0ec478c..5b0ea38 100644
--- a/mne/preprocessing/tests/test_ssp.py
+++ b/mne/preprocessing/tests/test_ssp.py
@@ -20,17 +20,15 @@ eog_times = np.array([0.5, 2.3, 3.6, 14.5])
 
 def test_compute_proj_ecg():
     """Test computation of ECG SSP projectors."""
-    raw = read_raw_fif(raw_fname, add_eeg_ref=False).crop(0, 10, copy=False)
+    raw = read_raw_fif(raw_fname).crop(0, 10)
     raw.load_data()
     for average in [False, True]:
         # For speed, let's not filter here (must also not reject then)
-        projs, events = compute_proj_ecg(raw, n_mag=2, n_grad=2, n_eeg=2,
-                                         ch_name='MEG 1531', bads=['MEG 2443'],
-                                         average=average, avg_ref=True,
-                                         no_proj=True, l_freq=None,
-                                         h_freq=None, reject=None,
-                                         tmax=dur_use, qrs_threshold=0.5,
-                                         filter_length=6000)
+        projs, events = compute_proj_ecg(
+            raw, n_mag=2, n_grad=2, n_eeg=2, ch_name='MEG 1531',
+            bads=['MEG 2443'], average=average, avg_ref=True, no_proj=True,
+            l_freq=None, h_freq=None, reject=None, tmax=dur_use,
+            qrs_threshold=0.5, filter_length=6000)
         assert_true(len(projs) == 7)
         # heart rate at least 0.5 Hz, but less than 3 Hz
         assert_true(events.shape[0] > 0.5 * dur_use and
@@ -51,18 +49,18 @@ def test_compute_proj_ecg():
         # without setting a bad channel, this should throw a warning
         with warnings.catch_warnings(record=True) as w:
             warnings.simplefilter('always')
-            projs, events = compute_proj_ecg(raw, n_mag=2, n_grad=2, n_eeg=2,
-                                             ch_name='MEG 1531', bads=[],
-                                             average=average, avg_ref=True,
-                                             no_proj=True, l_freq=None,
-                                             h_freq=None, tmax=dur_use)
+            projs, events, drop_log = compute_proj_ecg(
+                raw, n_mag=2, n_grad=2, n_eeg=2, ch_name='MEG 1531', bads=[],
+                average=average, avg_ref=True, no_proj=True, l_freq=None,
+                h_freq=None, tmax=dur_use, return_drop_log=True)
         assert_true(len(w) >= 1)
         assert_equal(projs, None)
+        assert_equal(len(events), len(drop_log))
 
 
 def test_compute_proj_eog():
     """Test computation of EOG SSP projectors."""
-    raw = read_raw_fif(raw_fname, add_eeg_ref=False).crop(0, 10, copy=False)
+    raw = read_raw_fif(raw_fname).crop(0, 10)
     raw.load_data()
     for average in [False, True]:
         n_projs_init = len(raw.info['projs'])
@@ -102,7 +100,7 @@ def test_compute_proj_eog():
 
 def test_compute_proj_parallel():
     """Test computation of ExG projectors using parallelization."""
-    raw_0 = read_raw_fif(raw_fname, add_eeg_ref=False).crop(0, 10, copy=False)
+    raw_0 = read_raw_fif(raw_fname).crop(0, 10)
     raw_0.load_data()
     raw = raw_0.copy()
     projs, _ = compute_proj_eog(raw, n_mag=2, n_grad=2, n_eeg=2,
diff --git a/mne/preprocessing/tests/test_stim.py b/mne/preprocessing/tests/test_stim.py
index 0ce802e..340cb14 100644
--- a/mne/preprocessing/tests/test_stim.py
+++ b/mne/preprocessing/tests/test_stim.py
@@ -23,17 +23,17 @@ def test_fix_stim_artifact():
     """Test fix stim artifact."""
     events = read_events(event_fname)
 
-    raw = read_raw_fif(raw_fname, add_eeg_ref=False)
+    raw = read_raw_fif(raw_fname)
     assert_raises(RuntimeError, fix_stim_artifact, raw)
 
-    raw = read_raw_fif(raw_fname, add_eeg_ref=False, preload=True)
+    raw = read_raw_fif(raw_fname, preload=True)
 
     # use window before stimulus in epochs
     tmin, tmax, event_id = -0.2, 0.5, 1
     picks = pick_types(raw.info, meg=True, eeg=True,
                        eog=True, stim=False, exclude='bads')
     epochs = Epochs(raw, events, event_id, tmin, tmax, picks=picks,
-                    preload=True, reject=None, add_eeg_ref=False)
+                    preload=True, reject=None)
     e_start = int(np.ceil(epochs.info['sfreq'] * epochs.tmin))
     tmin, tmax = -0.045, -0.015
     tmin_samp = int(-0.035 * epochs.info['sfreq']) - e_start
@@ -72,8 +72,7 @@ def test_fix_stim_artifact():
     # get epochs from raw with fixed data
     tmin, tmax, event_id = -0.2, 0.5, 1
     epochs = Epochs(raw, events, event_id, tmin, tmax, picks=picks,
-                    preload=True, reject=None, baseline=None,
-                    add_eeg_ref=False)
+                    preload=True, reject=None, baseline=None)
     e_start = int(np.ceil(epochs.info['sfreq'] * epochs.tmin))
     tmin_samp = int(-0.035 * epochs.info['sfreq']) - e_start
     tmax_samp = int(-0.015 * epochs.info['sfreq']) - e_start
diff --git a/mne/preprocessing/tests/test_xdawn.py b/mne/preprocessing/tests/test_xdawn.py
index 458a9b2..8adb6df 100644
--- a/mne/preprocessing/tests/test_xdawn.py
+++ b/mne/preprocessing/tests/test_xdawn.py
@@ -22,8 +22,7 @@ event_id = dict(cond2=2, cond3=3)
 
 def _get_data():
     """Get data."""
-    raw = read_raw_fif(raw_fname, add_eeg_ref=False, verbose=False,
-                       preload=True)
+    raw = read_raw_fif(raw_fname, verbose=False, preload=True)
     events = read_events(event_name)
     picks = pick_types(raw.info, meg=False, eeg=True, stim=False,
                        ecg=False, eog=False,
@@ -44,8 +43,7 @@ def test_xdawn_fit():
     # Get data
     raw, events, picks = _get_data()
     epochs = Epochs(raw, events, event_id, tmin, tmax, picks=picks,
-                    preload=True, baseline=None, verbose=False,
-                    add_eeg_ref=False)
+                    preload=True, baseline=None, verbose=False)
     # =========== Basic Fit test =================
     # Test base xdawn
     xd = Xdawn(n_components=2, correct_overlap='auto')
@@ -81,8 +79,7 @@ def test_xdawn_fit():
     # error
     # XXX This is a buggy test, the epochs here don't overlap
     epochs = Epochs(raw, events, event_id, tmin, tmax, picks=picks,
-                    preload=True, baseline=(None, 0), verbose=False,
-                    add_eeg_ref=False)
+                    preload=True, baseline=(None, 0), verbose=False)
 
     xd = Xdawn(n_components=2, correct_overlap=True)
     assert_raises(ValueError, xd.fit, epochs)
@@ -94,7 +91,7 @@ def test_xdawn_apply_transform():
     raw, events, picks = _get_data()
     raw.pick_types(eeg=True, meg=False)
     epochs = Epochs(raw, events, event_id, tmin, tmax, proj=False,
-                    add_eeg_ref=False, preload=True, baseline=None,
+                    preload=True, baseline=None,
                     verbose=False)
     n_components = 2
     # Fit Xdawn
@@ -107,10 +104,13 @@ def test_xdawn_apply_transform():
     # Apply on other thing should raise an error
     assert_raises(ValueError, xd.apply, 42)
 
-    # Transform on epochs
+    # Transform on Epochs
     xd.transform(epochs)
+    # Transform on Evoked
+    xd.transform(epochs.average())
     # Transform on ndarray
     xd.transform(epochs._data)
+    xd.transform(epochs._data[0])
     # Transform on someting else
     assert_raises(ValueError, xd.transform, 42)
 
@@ -130,8 +130,7 @@ def test_xdawn_regularization():
     # Get data
     raw, events, picks = _get_data()
     epochs = Epochs(raw, events, event_id, tmin, tmax, picks=picks,
-                    preload=True, baseline=None, verbose=False,
-                    add_eeg_ref=False)
+                    preload=True, baseline=None, verbose=False)
 
     # Test with overlapping events.
     # modify events to simulate one overlap
@@ -156,6 +155,12 @@ def test_xdawn_regularization():
     xd = Xdawn(n_components=2, correct_overlap=False,
                signal_cov=np.eye(len(picks)), reg=2)
     assert_raises(ValueError, xd.fit, epochs)
+    # With rank-deficient input
+    epochs.set_eeg_reference(['EEG 001'])
+    xd = Xdawn(correct_overlap=False, reg=None)
+    assert_raises(ValueError, xd.fit, epochs)
+    xd = Xdawn(correct_overlap=False, reg=0.5)
+    xd.fit(epochs)
 
 
 @requires_sklearn
@@ -164,8 +169,7 @@ def test_XdawnTransformer():
     # Get data
     raw, events, picks = _get_data()
     epochs = Epochs(raw, events, event_id, tmin, tmax, picks=picks,
-                    preload=True, baseline=None, verbose=False,
-                    add_eeg_ref=False)
+                    preload=True, baseline=None, verbose=False)
     X = epochs._data
     y = epochs.events[:, -1]
     # Fit
diff --git a/mne/preprocessing/xdawn.py b/mne/preprocessing/xdawn.py
index eaf9d38..a694797 100644
--- a/mne/preprocessing/xdawn.py
+++ b/mne/preprocessing/xdawn.py
@@ -7,12 +7,11 @@
 import numpy as np
 import copy as cp
 from scipy import linalg
-from .ica import _get_fast_dot
 from .. import EvokedArray, Evoked
 from ..cov import Covariance, _regularized_covariance
 from ..decoding import TransformerMixin, BaseEstimator
-from ..epochs import _BaseEpochs, EpochsArray
-from ..io import _BaseRaw
+from ..epochs import BaseEpochs, EpochsArray
+from ..io import BaseRaw
 from ..io.pick import _pick_data_channels
 from ..utils import logger
 from ..externals.six import iteritems, itervalues
@@ -61,7 +60,6 @@ def _least_square_evoked(epochs_data, events, tmin, sfreq):
     toeplitz : array, shape (n_class * n_components, n_channels)
         An concatenated array of toeplitz matrix for each event type.
     """
-
     n_epochs, n_channels, n_times = epochs_data.shape
     tmax = tmin + n_times / float(sfreq)
 
@@ -143,22 +141,6 @@ def _fit_xdawn(epochs_data, y, n_components, reg=None, signal_cov=None,
         The Xdawn patterns used to restore the signals for each event type.
     evokeds : array, shape (n_class, n_components, n_times)
         The independent evoked responses per condition.
-
-    References
-    ----------
-    [1] Rivet, B., Souloumiac, A., Attina, V., & Gibert, G. (2009). xDAWN
-    algorithm to enhance evoked potentials: application to brain-computer
-    interface. Biomedical Engineering, IEEE Transactions on, 56(8), 2035-2043.
-    [2] Rivet, B., Cecotti, H., Souloumiac, A., Maby, E., & Mattout, J. (2011,
-    August). Theoretical analysis of xDAWN algorithm: application to an
-    efficient sensor selection in a P300 BCI. In Signal Processing Conference,
-    2011 19th European (pp. 1382-1386). IEEE.
-
-
-    See Also
-    --------
-    CSP
-    XDawn
     """
     n_epochs, n_channels, n_times = epochs_data.shape
 
@@ -194,7 +176,11 @@ def _fit_xdawn(epochs_data, y, n_components, reg=None, signal_cov=None,
         evo_cov = np.matrix(_regularized_covariance(evo, reg))
 
         # Fit spatial filters
-        evals, evecs = linalg.eigh(evo_cov, signal_cov)
+        try:
+            evals, evecs = linalg.eigh(evo_cov, signal_cov)
+        except np.linalg.LinAlgError as exp:
+            raise ValueError('Could not compute eigenvalues, ensure '
+                             'proper regularization (%s)' % (exp,))
         evecs = evecs[:, np.argsort(evals)[::-1]]  # sort eigenvectors
         evecs /= np.apply_along_axis(np.linalg.norm, 0, evecs)
         _patterns = np.linalg.pinv(evecs.T)
@@ -240,21 +226,6 @@ class _XdawnTransformer(BaseEstimator, TransformerMixin):
         The Xdawn components used to decompose the data for each event type.
     patterns_ : array, shape (n_channels, n_channels)
         The Xdawn patterns used to restore the signals for each event type.
-
-    References
-    ----------
-    [1] Rivet, B., Souloumiac, A., Attina, V., & Gibert, G. (2009). xDAWN
-    algorithm to enhance evoked potentials: application to brain-computer
-    interface. Biomedical Engineering, IEEE Transactions on, 56(8), 2035-2043.
-    [2] Rivet, B., Cecotti, H., Souloumiac, A., Maby, E., & Mattout, J. (2011,
-    August). Theoretical analysis of xDAWN algorithm: application to an
-    efficient sensor selection in a P300 BCI. In Signal Processing Conference,
-    2011 19th European (pp. 1382-1386). IEEE.
-
-    See Also
-    --------
-    Xdawn
-    CSD
     """
 
     def __init__(self, n_components=2, reg=None, signal_cov=None):
@@ -339,8 +310,7 @@ class _XdawnTransformer(BaseEstimator, TransformerMixin):
                 self.n_components * len(self.classes_), n_comp))
 
         # Transform
-        fast_dot = _get_fast_dot()
-        return fast_dot(self.patterns_.T, X).transpose(1, 0, 2)
+        return np.dot(self.patterns_.T, X).transpose(1, 0, 2)
 
     def _check_Xy(self, X, y=None):
         """Check X and y types and dimensions."""
@@ -359,11 +329,11 @@ class _XdawnTransformer(BaseEstimator, TransformerMixin):
 class Xdawn(_XdawnTransformer):
     """Implementation of the Xdawn Algorithm.
 
-    Xdawn is a spatial filtering method designed to improve the signal
-    to signal + noise ratio (SSNR) of the ERP responses. Xdawn was originally
-    designed for P300 evoked potential by enhancing the target response with
-    respect to the non-target response. This implementation is a generalization
-    to any type of ERP.
+    Xdawn [1]_ [2]_ is a spatial filtering method designed to improve the
+    signal to signal + noise ratio (SSNR) of the ERP responses. Xdawn was
+    originally designed for P300 evoked potential by enhancing the target
+    response with respect to the non-target response. This implementation
+    is a generalization to any type of ERP.
 
     Parameters
     ----------
@@ -403,19 +373,22 @@ class Xdawn(_XdawnTransformer):
 
     See Also
     --------
-    CSP
+    mne.decoding.CSP, mne.decoding.SPoC
 
     References
     ----------
-    [1] Rivet, B., Souloumiac, A., Attina, V., & Gibert, G. (2009). xDAWN
-    algorithm to enhance evoked potentials: application to brain-computer
-    interface. Biomedical Engineering, IEEE Transactions on, 56(8), 2035-2043.
-
-    [2] Rivet, B., Cecotti, H., Souloumiac, A., Maby, E., & Mattout, J. (2011,
-    August). Theoretical analysis of xDAWN algorithm: application to an
-    efficient sensor selection in a P300 BCI. In Signal Processing Conference,
-    2011 19th European (pp. 1382-1386). IEEE.
+    .. [1] Rivet, B., Souloumiac, A., Attina, V., & Gibert, G. (2009). xDAWN
+           algorithm to enhance evoked potentials: application to
+           brain-computer interface. Biomedical Engineering, IEEE Transactions
+           on, 56(8), 2035-2043.
+
+    .. [2] Rivet, B., Cecotti, H., Souloumiac, A., Maby, E., & Mattout, J.
+           (2011, August). Theoretical analysis of xDAWN algorithm:
+           application to an efficient sensor selection in a P300 BCI. In
+           Signal Processing Conference, 2011 19th European (pp. 1382-1386).
+           IEEE.
     """
+
     def __init__(self, n_components=2, signal_cov=None, correct_overlap='auto',
                  reg=None):
         """Init."""
@@ -441,7 +414,7 @@ class Xdawn(_XdawnTransformer):
             The Xdawn instance.
         """
         # Check data
-        if not isinstance(epochs, _BaseEpochs):
+        if not isinstance(epochs, BaseEpochs):
             raise ValueError('epochs must be an Epochs object.')
         X = epochs.get_data()
         X = X[:, _pick_data_channels(epochs.info), :]
@@ -474,7 +447,7 @@ class Xdawn(_XdawnTransformer):
 
         # Main fitting function
         filters, patterns, evokeds = _fit_xdawn(
-            X, y,  n_components=n_components, reg=self.reg,
+            X, y, n_components=n_components, reg=self.reg,
             signal_cov=self.signal_cov, events=events, tmin=tmin, sfreq=sfreq)
 
         # Re-order filters and patterns according to event_id
@@ -492,23 +465,27 @@ class Xdawn(_XdawnTransformer):
             self.evokeds_[eid] = evoked
         return self
 
-    def transform(self, epochs):
+    def transform(self, inst):
         """Apply Xdawn dim reduction.
 
         Parameters
         ----------
-        epochs : Epochs | ndarray, shape (n_epochs, n_channels, n_times)
+        inst : Epochs | Evoked | ndarray, shape ([n_epochs, ]n_channels, n_times)
             Data on which Xdawn filters will be applied.
 
         Returns
         -------
-        X : ndarray, shape (n_epochs, n_components * n_event_types, n_times)
+        X : ndarray, shape ([n_epochs, ]n_components * n_event_types, n_times)
             Spatially filtered signals.
-        """
-        if isinstance(epochs, _BaseEpochs):
-            X = epochs.get_data()
-        elif isinstance(epochs, np.ndarray):
-            X = epochs
+        """  # noqa: E501
+        if isinstance(inst, BaseEpochs):
+            X = inst.get_data()
+        elif isinstance(inst, Evoked):
+            X = inst.data
+        elif isinstance(inst, np.ndarray):
+            X = inst
+            if X.ndim not in (2, 3):
+                raise ValueError('X must be 2D or 3D, got %s' % (X.ndim,))
         else:
             raise ValueError('Data input must be of Epoch type or numpy array')
 
@@ -516,7 +493,9 @@ class Xdawn(_XdawnTransformer):
                    for filt in itervalues(self.filters_)]
         filters = np.concatenate(filters, axis=0)
         X = np.dot(filters, X)
-        return X.transpose((1, 0, 2))
+        if X.ndim == 3:
+            X = X.transpose((1, 0, 2))
+        return X
 
     def apply(self, inst, event_id=None, include=None, exclude=None):
         """Remove selected components from the signal.
@@ -551,7 +530,7 @@ class Xdawn(_XdawnTransformer):
         if event_id is None:
             event_id = self.event_id_
 
-        if not isinstance(inst, (_BaseRaw, _BaseEpochs, Evoked)):
+        if not isinstance(inst, (BaseRaw, BaseEpochs, Evoked)):
             raise ValueError('Data input must be Raw, Epochs or Evoked type')
         picks = _pick_data_channels(inst.info)
 
@@ -562,10 +541,10 @@ class Xdawn(_XdawnTransformer):
         else:
             exclude = list(set(list(default_exclude) + list(exclude)))
 
-        if isinstance(inst, _BaseRaw):
+        if isinstance(inst, BaseRaw):
             out = self._apply_raw(raw=inst, include=include, exclude=exclude,
                                   event_id=event_id, picks=picks)
-        elif isinstance(inst, _BaseEpochs):
+        elif isinstance(inst, BaseEpochs):
             out = self._apply_epochs(epochs=inst, include=include, picks=picks,
                                      exclude=exclude, event_id=event_id)
         elif isinstance(inst, Evoked):
@@ -629,12 +608,10 @@ class Xdawn(_XdawnTransformer):
 
     def _pick_sources(self, data, include, exclude, eid):
         """Aux method."""
-        fast_dot = _get_fast_dot()
-
         logger.info('Transforming to Xdawn space')
 
         # Apply unmixing
-        sources = fast_dot(self.filters_[eid].T, data)
+        sources = np.dot(self.filters_[eid].T, data)
 
         if include not in (None, list()):
             mask = np.ones(len(sources), dtype=np.bool)
@@ -646,12 +623,11 @@ class Xdawn(_XdawnTransformer):
             sources[exclude_] = 0.
             logger.info('Zeroing out %i Xdawn components' % len(exclude_))
         logger.info('Inverse transforming to sensor space')
-        data = fast_dot(self.patterns_[eid], sources)
+        data = np.dot(self.patterns_[eid], sources)
 
         return data
 
     def inverse_transform(self):
-        """Not implemented, see Xdawn.apply() instead.
-        """
+        """Not implemented, see Xdawn.apply() instead."""
         # Exists because of _XdawnTransformer
         raise NotImplementedError('See Xdawn.apply()')
diff --git a/mne/proj.py b/mne/proj.py
index 44141c1..770ff8b 100644
--- a/mne/proj.py
+++ b/mne/proj.py
@@ -118,7 +118,7 @@ def _compute_proj(data, info, n_grad, n_mag, n_eeg, desc_prefix, verbose=None):
 @verbose
 def compute_proj_epochs(epochs, n_grad=2, n_mag=2, n_eeg=2, n_jobs=1,
                         desc_prefix=None, verbose=None):
-    """Compute SSP (spatial space projection) vectors on Epochs
+    """Compute SSP (spatial space projection) vectors on Epochs.
 
     Parameters
     ----------
@@ -136,7 +136,8 @@ def compute_proj_epochs(epochs, n_grad=2, n_mag=2, n_eeg=2, n_jobs=1,
         The description prefix to use. If None, one will be created based on
         the event_id, tmin, and tmax.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -162,7 +163,7 @@ def compute_proj_epochs(epochs, n_grad=2, n_mag=2, n_eeg=2, n_jobs=1,
 
 
 def _compute_cov_epochs(epochs, n_jobs):
-    """Helper function for computing epochs covariance"""
+    """Compute epochs covariance."""
     parallel, p_fun, _ = parallel_func(np.dot, n_jobs)
     data = parallel(p_fun(e, e.T) for e in epochs)
     n_epochs = len(data)
@@ -177,7 +178,7 @@ def _compute_cov_epochs(epochs, n_jobs):
 
 @verbose
 def compute_proj_evoked(evoked, n_grad=2, n_mag=2, n_eeg=2, verbose=None):
-    """Compute SSP (spatial space projection) vectors on Evoked
+    """Compute SSP (spatial space projection) vectors on Evoked.
 
     Parameters
     ----------
@@ -190,7 +191,8 @@ def compute_proj_evoked(evoked, n_grad=2, n_mag=2, n_eeg=2, verbose=None):
     n_eeg : int
         Number of vectors for EEG channels
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -209,7 +211,7 @@ def compute_proj_evoked(evoked, n_grad=2, n_mag=2, n_eeg=2, verbose=None):
 @verbose
 def compute_proj_raw(raw, start=0, stop=None, duration=1, n_grad=2, n_mag=2,
                      n_eeg=0, reject=None, flat=None, n_jobs=1, verbose=None):
-    """Compute SSP (spatial space projection) vectors on Raw
+    """Compute SSP (spatial space projection) vectors on Raw.
 
     Parameters
     ----------
@@ -236,7 +238,8 @@ def compute_proj_raw(raw, start=0, stop=None, duration=1, n_grad=2, n_mag=2,
     n_jobs : int
         Number of jobs to use to compute covariance.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -249,11 +252,10 @@ def compute_proj_raw(raw, start=0, stop=None, duration=1, n_grad=2, n_mag=2,
     """
     if duration is not None:
         events = make_fixed_length_events(raw, 999, start, stop, duration)
+        picks = pick_types(raw.info, meg=True, eeg=True, eog=True, ecg=True,
+                           emg=True, exclude='bads')
         epochs = Epochs(raw, events, None, tmin=0., tmax=duration,
-                        picks=pick_types(raw.info, meg=True, eeg=True,
-                                         eog=True, ecg=True, emg=True,
-                                         exclude='bads'),
-                        reject=reject, flat=flat, add_eeg_ref=False)
+                        picks=picks, reject=reject, flat=flat)
         data = _compute_cov_epochs(epochs, n_jobs)
         info = epochs.info
         if not stop:
@@ -278,7 +280,7 @@ def compute_proj_raw(raw, start=0, stop=None, duration=1, n_grad=2, n_mag=2,
 
 def sensitivity_map(fwd, projs=None, ch_type='grad', mode='fixed', exclude=[],
                     verbose=None):
-    """Compute sensitivity map
+    """Compute sensitivity map.
 
     Such maps are used to know how much sources are visible by a type
     of sensor, and how much projections shadow some sources.
@@ -300,7 +302,8 @@ def sensitivity_map(fwd, projs=None, ch_type='grad', mode='fixed', exclude=[],
         List of channels to exclude. If empty do not exclude any (default).
         If 'bads', exclude channels in fwd['info']['bads'].
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
diff --git a/mne/realtime/__init__.py b/mne/realtime/__init__.py
index cee63e9..d29a58f 100644
--- a/mne/realtime/__init__.py
+++ b/mne/realtime/__init__.py
@@ -1,4 +1,4 @@
-""" Module for realtime MEG data using mne_rt_server """
+"""Realtime MEG data processing with servers and clients."""
 
 # Authors: Christoph Dinh <chdinh at nmr.mgh.harvard.edu>
 #          Martin Luessi <mluessi at nmr.mgh.harvard.edu>
diff --git a/mne/realtime/client.py b/mne/realtime/client.py
index e17e102..d25a9ab 100644
--- a/mne/realtime/client.py
+++ b/mne/realtime/client.py
@@ -1,10 +1,11 @@
-from __future__ import print_function
 # Authors: Christoph Dinh <chdinh at nmr.mgh.harvard.edu>
 #          Martin Luessi <mluessi at nmr.mgh.harvard.edu>
 #          Matti Hamalainen <msh at nmr.mgh.harvard.edu>
 #
 # License: BSD (3-clause)
 
+from __future__ import print_function
+
 import socket
 import time
 from ..externals.six.moves import StringIO
@@ -24,7 +25,7 @@ MNE_RT_SET_CLIENT_ALIAS = 2
 
 
 def _recv_tag_raw(sock):
-    """Read a tag and the associated data from a socket
+    """Read a tag and the associated data from a socket.
 
     Parameters
     ----------
@@ -61,7 +62,7 @@ def _recv_tag_raw(sock):
 
 
 def _buffer_recv_worker(rt_client, nchan):
-    """Worker thread that constantly receives buffers"""
+    """Worker thread that constantly receives buffers."""
     try:
         for raw_buffer in rt_client.raw_buffers(nchan):
             rt_client._push_raw_buffer(raw_buffer)
@@ -72,7 +73,7 @@ def _buffer_recv_worker(rt_client, nchan):
 
 
 class RtClient(object):
-    """Realtime Client
+    """Realtime Client.
 
     Client to communicate with mne_rt_server
 
@@ -80,22 +81,20 @@ class RtClient(object):
     ----------
     host : str
         Hostname (or IP address) of the host where mne_rt_server is running.
-
     cmd_port : int
         Port to use for the command connection.
-
     data_port : int
         Port to use for the data connection.
-
     timeout : float
         Communication timeout in seconds.
-
     verbose : bool, str, int, or None
-        Log verbosity see mne.verbose.
+        Log verbosity (see :func:`mne.verbose` and
+        :ref:`Logging documentation <tut_logging>` for more).
     """
+
     @verbose
     def __init__(self, host, cmd_port=4217, data_port=4218, timeout=1.0,
-                 verbose=None):
+                 verbose=None):  # noqa: D102
         self._host = host
         self._data_port = data_port
         self._cmd_port = cmd_port
@@ -130,7 +129,7 @@ class RtClient(object):
         self._recv_callbacks = list()
 
     def _send_command(self, command):
-        """Send a command to the server
+        """Send a command to the server.
 
         Parameters
         ----------
@@ -142,7 +141,6 @@ class RtClient(object):
         resp : str
             The response from the server.
         """
-
         logger.debug('Sending command: %s' % command)
         command += '\n'
         self._cmd_sock.sendall(command.encode('utf-8'))
@@ -168,7 +166,7 @@ class RtClient(object):
         return ''.join(buf)
 
     def _send_fiff_command(self, command, data=None):
-        """Send a command through the data connection as a fiff tag
+        """Send a command through the data connection as a fiff tag.
 
         Parameters
         ----------
@@ -197,7 +195,7 @@ class RtClient(object):
         self._data_sock.sendall(msg)
 
     def get_measurement_info(self):
-        """Get the measurement information
+        """Get the measurement information.
 
         Returns
         -------
@@ -230,7 +228,7 @@ class RtClient(object):
         return info
 
     def set_client_alias(self, alias):
-        """Set client alias
+        """Set client alias.
 
         Parameters
         ----------
@@ -240,7 +238,7 @@ class RtClient(object):
         self._send_fiff_command(MNE_RT_SET_CLIENT_ALIAS, alias)
 
     def get_client_id(self):
-        """Get the client ID
+        """Get the client ID.
 
         Returns
         -------
@@ -260,16 +258,16 @@ class RtClient(object):
         return client_id
 
     def start_measurement(self):
-        """Start the measurement"""
+        """Start the measurement."""
         cmd = 'start %d' % self._client_id
         self._send_command(cmd)
 
     def stop_measurement(self):
-        """Stop the measurement"""
+        """Stop the measurement."""
         self._send_command('stop-all')
 
     def start_receive_thread(self, nchan):
-        """Start the receive thread
+        """Start the receive thread.
 
         If the measurement has not been started, it will also be started.
 
@@ -278,7 +276,6 @@ class RtClient(object):
         nchan : int
             The number of channels in the data.
         """
-
         if self._recv_thread is None:
             self.start_measurement()
 
@@ -287,7 +284,7 @@ class RtClient(object):
             self._recv_thread.start()
 
     def stop_receive_thread(self, stop_measurement=False):
-        """Stop the receive thread
+        """Stop the receive thread.
 
         Parameters
         ----------
@@ -302,7 +299,7 @@ class RtClient(object):
             self.stop_measurement()
 
     def register_receive_callback(self, callback):
-        """Register a raw buffer receive callback
+        """Register a raw buffer receive callback.
 
         Parameters
         ----------
@@ -314,7 +311,7 @@ class RtClient(object):
             self._recv_callbacks.append(callback)
 
     def unregister_receive_callback(self, callback):
-        """Unregister a raw buffer receive callback
+        """Unregister a raw buffer receive callback.
 
         Parameters
         ----------
@@ -325,12 +322,12 @@ class RtClient(object):
             self._recv_callbacks.remove(callback)
 
     def _push_raw_buffer(self, raw_buffer):
-        """Push raw buffer to clients using callbacks"""
+        """Push raw buffer to clients using callbacks."""
         for callback in self._recv_callbacks:
             callback(raw_buffer)
 
     def read_raw_buffer(self, nchan):
-        """Read a single buffer with raw data
+        """Read a single buffer with raw data.
 
         Parameters
         ----------
@@ -355,7 +352,7 @@ class RtClient(object):
         return raw_buffer
 
     def raw_buffers(self, nchan):
-        """Return an iterator over raw buffers
+        """Return an iterator over raw buffers.
 
         Parameters
         ----------
diff --git a/mne/realtime/epochs.py b/mne/realtime/epochs.py
index 9e5110b..f9fe089 100644
--- a/mne/realtime/epochs.py
+++ b/mne/realtime/epochs.py
@@ -12,12 +12,12 @@ import numpy as np
 
 from .. import pick_channels
 from ..utils import logger, verbose
-from ..epochs import _BaseEpochs
+from ..epochs import BaseEpochs
 from ..event import _find_events
 
 
-class RtEpochs(_BaseEpochs):
-    """Realtime Epochs
+class RtEpochs(BaseEpochs):
+    """Realtime Epochs.
 
     Can receive epochs in real time from an RtClient.
 
@@ -61,8 +61,6 @@ class RtEpochs(_BaseEpochs):
         interval is used.
     picks : array-like of int | None (default)
         Indices of channels to include (if None, all channels are used).
-    name : string
-        Comment that describes the Evoked data created.
     reject : dict | None
         Rejection parameters based on peak-to-peak amplitude.
         Valid keys are 'grad' | 'mag' | 'eeg' | 'eog' | 'ecg'.
@@ -98,11 +96,6 @@ class RtEpochs(_BaseEpochs):
         either turn off baseline correction, as this may introduce a DC
         shift, or set baseline correction to use the entire time interval
         (will yield equivalent results but be slower).
-    add_eeg_ref : bool
-        If True, an EEG average reference will be added (unless one
-        already exists). The default value of True in 0.13 will change to
-        False in 0.14, and the parameter will be removed in 0.15. Use
-        :func:`mne.set_eeg_reference` instead.
     isi_max : float
         The maximmum time in seconds between epochs. If no epoch
         arrives in the next isi_max seconds the RtEpochs stops.
@@ -116,8 +109,9 @@ class RtEpochs(_BaseEpochs):
 
         See :func:`mne.find_events` for detailed explanation of these options.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
-        Defaults to client.verbose.
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more). Defaults to
+        client.verbose.
 
     Attributes
     ----------
@@ -132,12 +126,13 @@ class RtEpochs(_BaseEpochs):
     verbose : bool, str, int, or None
         See above.
     """
+
     @verbose
     def __init__(self, client, event_id, tmin, tmax, stim_channel='STI 014',
                  sleep_time=0.1, baseline=(None, 0), picks=None,
-                 name='Unknown', reject=None, flat=None, proj=True,
+                 reject=None, flat=None, proj=True,
                  decim=1, reject_tmin=None, reject_tmax=None, detrend=None,
-                 add_eeg_ref=None, isi_max=2., find_events=None, verbose=None):
+                 isi_max=2., find_events=None, verbose=None):  # noqa: D102
         info = client.get_measurement_info()
 
         # the measurement info of the data as we receive it
@@ -145,12 +140,12 @@ class RtEpochs(_BaseEpochs):
 
         verbose = client.verbose if verbose is None else verbose
 
-        # call _BaseEpochs constructor
+        # call BaseEpochs constructor
         super(RtEpochs, self).__init__(
             info, None, None, event_id, tmin, tmax, baseline, picks=picks,
-            name=name, reject=reject, flat=flat, decim=decim,
+            reject=reject, flat=flat, decim=decim,
             reject_tmin=reject_tmin, reject_tmax=reject_tmax, detrend=detrend,
-            add_eeg_ref=add_eeg_ref, verbose=verbose, proj=True)
+            verbose=verbose, proj=True)
 
         self._client = client
 
@@ -212,7 +207,7 @@ class RtEpochs(_BaseEpochs):
         return np.array(self._events)
 
     def start(self):
-        """Start receiving epochs
+        """Start receiving epochs.
 
         The measurement will be started if it has not already been started.
         """
@@ -227,7 +222,7 @@ class RtEpochs(_BaseEpochs):
             self._last_time = np.inf  # init delay counter. Will stop iters
 
     def stop(self, stop_receive_thread=False, stop_measurement=False):
-        """Stop receiving epochs
+        """Stop receiving epochs.
 
         Parameters
         ----------
@@ -248,7 +243,7 @@ class RtEpochs(_BaseEpochs):
             self._client.stop_receive_thread(stop_measurement=stop_measurement)
 
     def next(self, return_event_id=False):
-        """To make iteration over epochs easy.
+        """Make iteration over epochs easy.
 
         Parameters
         ----------
@@ -284,8 +279,7 @@ class RtEpochs(_BaseEpochs):
                                    'not receiving epochs, cannot get epochs!')
 
     def _get_data(self):
-        """Return the data for n_epochs epochs"""
-
+        """Return the data for n_epochs epochs."""
         epochs = list()
         for epoch in self:
             epochs.append(epoch)
@@ -295,7 +289,7 @@ class RtEpochs(_BaseEpochs):
         return data
 
     def _process_raw_buffer(self, raw_buffer):
-        """Process raw buffer (callback from RtClient)
+        """Process raw buffer (callback from RtClient).
 
         Note: Do not print log messages during regular use. It will be printed
         asynchronously which is annoying when working in an interactive shell.
@@ -320,11 +314,36 @@ class RtEpochs(_BaseEpochs):
 
         # detect events
         data = np.abs(raw_buffer[self._stim_picks]).astype(np.int)
-        data = np.atleast_2d(data)
-        buff_events = _find_events(data, self._first_samp, verbose=verbose,
-                                   **self._find_events_kwargs)
+        # if there is a previous buffer check the last samples from it too
+        if self._last_buffer is not None:
+            prev_data = self._last_buffer[self._stim_picks,
+                                          -raw_buffer.shape[1]:].astype(np.int)
+            data = np.concatenate((prev_data, data), axis=1)
+            data = np.atleast_2d(data)
+            buff_events = _find_events(data,
+                                       self._first_samp - raw_buffer.shape[1],
+                                       verbose=verbose,
+                                       **self._find_events_kwargs)
+        else:
+            data = np.atleast_2d(data)
+            buff_events = _find_events(data, self._first_samp, verbose=verbose,
+                                       **self._find_events_kwargs)
 
         events = self._event_backlog
+
+        # remove events before the last epoch processed
+        min_event_samp = self._first_samp - \
+            int(self._find_events_kwargs['min_samples'])
+        if len(self._event_backlog) > 0:
+            backlog_samps = np.array(self._event_backlog)[:, 0]
+            min_event_samp = backlog_samps[-1] + 1
+
+        if buff_events.shape[0] > 0:
+            valid_events_idx = buff_events[:, 0] >= min_event_samp
+            buff_events = buff_events[valid_events_idx]
+
+        # add events from this buffer to the list of events
+        # processed so far
         for event_id in self.event_id.values():
             idx = np.where(buff_events[:, -1] == event_id)[0]
             events.extend(zip(list(buff_events[idx, 0]),
@@ -375,7 +394,7 @@ class RtEpochs(_BaseEpochs):
             self._last_buffer[:, -n_buffer:] = raw_buffer
 
     def _append_epoch_to_queue(self, epoch, event_samp, event_id):
-        """Append a (raw) epoch to queue
+        """Append a (raw) epoch to queue.
 
         Note: Do not print log messages during regular use. It will be printed
         asynchronously which is annyoing when working in an interactive shell.
@@ -409,7 +428,7 @@ class RtEpochs(_BaseEpochs):
         else:
             self._n_bad += 1
 
-    def __repr__(self):
+    def __repr__(self):  # noqa: D105
         s = 'good / bad epochs received: %d / %d, epochs in queue: %d, '\
             % (self._n_good, self._n_bad, len(self._epoch_queue))
         s += ', tmin : %s (s)' % self.tmin
diff --git a/mne/realtime/fieldtrip_client.py b/mne/realtime/fieldtrip_client.py
index 878f81e..3ba161e 100644
--- a/mne/realtime/fieldtrip_client.py
+++ b/mne/realtime/fieldtrip_client.py
@@ -19,7 +19,6 @@ from ..externals.FieldTrip import Client as FtClient
 
 def _buffer_recv_worker(ft_client):
     """Worker thread that constantly receives buffers."""
-
     try:
         for raw_buffer in ft_client.iter_raw_buffers():
             ft_client._push_raw_buffer(raw_buffer)
@@ -30,7 +29,7 @@ def _buffer_recv_worker(ft_client):
 
 
 class FieldTripClient(object):
-    """ Realtime FieldTrip client
+    """Realtime FieldTrip client.
 
     Parameters
     ----------
@@ -51,10 +50,13 @@ class FieldTripClient(object):
     buffer_size : int
         Size of each buffer in terms of number of samples.
     verbose : bool, str, int, or None
-        Log verbosity see mne.verbose.
+        Log verbosity (see :func:`mne.verbose` and
+        :ref:`Logging documentation <tut_logging>` for more).
     """
+
     def __init__(self, info=None, host='localhost', port=1972, wait_max=30,
-                 tmin=None, tmax=np.inf, buffer_size=1000, verbose=None):
+                 tmin=None, tmax=np.inf, buffer_size=1000,
+                 verbose=None):  # noqa: D102
         self.verbose = verbose
 
         self.info = info
@@ -69,7 +71,7 @@ class FieldTripClient(object):
         self._recv_thread = None
         self._recv_callbacks = list()
 
-    def __enter__(self):
+    def __enter__(self):  # noqa: D105
         # instantiate Fieldtrip client and connect
         self.ft_client = FtClient()
 
@@ -125,14 +127,11 @@ class FieldTripClient(object):
 
         return self
 
-    def __exit__(self, type, value, traceback):
+    def __exit__(self, type, value, traceback):  # noqa: D105
         self.ft_client.disconnect()
 
     def _guess_measurement_info(self):
-        """
-        Creates a minimal Info dictionary required for epoching, averaging
-        et al.
-        """
+        """Create a minimal Info dictionary for epoching, averaging, etc."""
         if self.info is None:
             warn('Info dictionary not provided. Trying to guess it from '
                  'FieldTrip Header object')
@@ -147,13 +146,17 @@ class FieldTripClient(object):
             # channel dictionary list
             info['chs'] = []
 
+            # unrecognized channels
+            chs_unknown = []
+
             for idx, ch in enumerate(self.ft_header.labels):
                 this_info = dict()
 
                 this_info['scanno'] = idx
 
                 # extract numerical part of channel name
-                this_info['logno'] = int(re.findall('[^\W\d_]+|\d+', ch)[-1])
+                this_info['logno'] = \
+                    int(re.findall(r'[^\W\d_]+|\d+', ch)[-1])
 
                 if ch.startswith('EEG'):
                     this_info['kind'] = FIFF.FIFFV_EEG_CH
@@ -171,13 +174,19 @@ class FieldTripClient(object):
                     this_info['kind'] = FIFF.FIFFV_ECG_CH
                 elif ch.startswith('MISC'):
                     this_info['kind'] = FIFF.FIFFV_MISC_CH
+                elif ch.startswith('SYS'):
+                    this_info['kind'] = FIFF.FIFFV_SYST_CH
+                else:
+                    # cannot guess channel type, mark as MISC and warn later
+                    this_info['kind'] = FIFF.FIFFV_MISC_CH
+                    chs_unknown.append(ch)
 
                 # Fieldtrip already does calibration
                 this_info['range'] = 1.0
                 this_info['cal'] = 1.0
 
                 this_info['ch_name'] = ch
-                this_info['loc'] = None
+                this_info['loc'] = np.zeros(12)
 
                 if ch.startswith('EEG'):
                     this_info['coord_frame'] = FIFF.FIFFV_COORD_HEAD
@@ -200,6 +209,11 @@ class FieldTripClient(object):
                 info._update_redundant()
                 info._check_consistency()
 
+            if chs_unknown:
+                msg = ('Following channels in the FieldTrip header were '
+                       'unrecognized and marked as MISC: ')
+                warn(msg + ', '.join(chs_unknown))
+
         else:
 
             # XXX: the data in real-time mode and offline mode
@@ -217,7 +231,7 @@ class FieldTripClient(object):
         return info
 
     def get_measurement_info(self):
-        """Returns the measurement info.
+        """Return the measurement info.
 
         Returns
         -------
@@ -227,7 +241,7 @@ class FieldTripClient(object):
         return self.info
 
     def get_data_as_epoch(self, n_samples=1024, picks=None):
-        """Returns last n_samples from current time.
+        """Return last n_samples from current time.
 
         Parameters
         ----------
@@ -244,7 +258,7 @@ class FieldTripClient(object):
 
         See Also
         --------
-        Epochs.iter_evoked
+        mne.Epochs.iter_evoked
         """
         ft_header = self.ft_client.getHeader()
         last_samp = ft_header.nSamples - 1
@@ -278,7 +292,7 @@ class FieldTripClient(object):
             self._recv_callbacks.append(callback)
 
     def unregister_receive_callback(self, callback):
-        """Unregister a raw buffer receive callback
+        """Unregister a raw buffer receive callback.
 
         Parameters
         ----------
@@ -303,7 +317,6 @@ class FieldTripClient(object):
         nchan : int
             The number of channels in the data.
         """
-
         if self._recv_thread is None:
 
             self._recv_thread = threading.Thread(target=_buffer_recv_worker,
@@ -312,7 +325,7 @@ class FieldTripClient(object):
             self._recv_thread.start()
 
     def stop_receive_thread(self, stop_measurement=False):
-        """Stop the receive thread
+        """Stop the receive thread.
 
         Parameters
         ----------
@@ -324,14 +337,13 @@ class FieldTripClient(object):
             self._recv_thread = None
 
     def iter_raw_buffers(self):
-        """Return an iterator over raw buffers
+        """Return an iterator over raw buffers.
 
         Returns
         -------
         raw_buffer : generator
             Generator for iteration over raw buffers.
         """
-
         iter_times = zip(range(self.tmin_samp, self.tmax_samp,
                                self.buffer_size),
                          range(self.tmin_samp + self.buffer_size - 1,
diff --git a/mne/realtime/mockclient.py b/mne/realtime/mockclient.py
index 8795b88..b8b59dc 100644
--- a/mne/realtime/mockclient.py
+++ b/mne/realtime/mockclient.py
@@ -10,16 +10,18 @@ from ..event import find_events
 
 
 class MockRtClient(object):
-    """Mock Realtime Client
+    """Mock Realtime Client.
 
     Parameters
     ----------
     raw : instance of Raw object
         The raw object which simulates the RtClient
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
     """
-    def __init__(self, raw, verbose=None):
+
+    def __init__(self, raw, verbose=None):  # noqa: D102
         self.raw = raw
         self.info = copy.deepcopy(self.raw.info)
         self.verbose = verbose
@@ -28,7 +30,7 @@ class MockRtClient(object):
         self._last = dict()  # Last index for the event
 
     def get_measurement_info(self):
-        """Returns the measurement info.
+        """Return the measurement info.
 
         Returns
         -------
@@ -115,7 +117,6 @@ class MockRtClient(object):
         data : 2D array with shape [n_channels, n_times]
             The epochs that are being simulated
         """
-
         # Get the list of all events
         events = find_events(self.raw, stim_channel=stim_channel,
                              verbose=False, output='onset',
@@ -156,7 +157,7 @@ class MockRtClient(object):
             return None
 
     def register_receive_callback(self, x):
-        """API boilerplate
+        """Fake API boilerplate.
 
         Parameters
         ----------
@@ -166,7 +167,7 @@ class MockRtClient(object):
         pass
 
     def start_receive_thread(self, x):
-        """API boilerplate
+        """Fake API boilerplate.
 
         Parameters
         ----------
@@ -176,15 +177,15 @@ class MockRtClient(object):
         pass
 
     def unregister_receive_callback(self, x):
-        """API boilerplate
+        """Fake API boilerplate.
 
         Parameters
         ----------
         x : None
             Not used.
-        """
+        """  # noqa: D401
         pass
 
     def _stop_receive_thread(self):
-        """API boilerplate"""
+        """Fake API boilerplate."""
         pass
diff --git a/mne/realtime/stim_server_client.py b/mne/realtime/stim_server_client.py
index 21d16c8..f6f4755 100644
--- a/mne/realtime/stim_server_client.py
+++ b/mne/realtime/stim_server_client.py
@@ -13,7 +13,7 @@ from ..utils import logger, verbose
 
 
 class _ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
-    """Creates a threaded TCP server
+    """Create a threaded TCP server.
 
     Parameters
     ----------
@@ -26,7 +26,7 @@ class _ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
     """
 
     def __init__(self, server_address, request_handler_class,
-                 stim_server):
+                 stim_server):  # noqa: D102
 
         # Basically, this server is the same as a normal TCPServer class
         # except that it has an additional attribute stim_server
@@ -43,8 +43,7 @@ class _TriggerHandler(socketserver.BaseRequestHandler):
     """Request handler on the server side."""
 
     def handle(self):
-        """Method to handle requests on the server side."""
-
+        """Handle requests on the server side."""
         self.request.settimeout(None)
 
         while self.server.stim_server._running:
@@ -82,7 +81,7 @@ class _TriggerHandler(socketserver.BaseRequestHandler):
 
 
 class StimServer(object):
-    """Stimulation Server
+    """Stimulation Server.
 
     Server to communicate with StimClient(s).
 
@@ -98,14 +97,14 @@ class StimServer(object):
     StimClient
     """
 
-    def __init__(self, port=4218, n_clients=1):
+    def __init__(self, port=4218, n_clients=1):  # noqa: D102
 
         # Start a threaded TCP server, binding to localhost on specified port
         self._data = _ThreadedTCPServer(('', port),
                                         _TriggerHandler, self)
         self.n_clients = n_clients
 
-    def __enter__(self):
+    def __enter__(self):  # noqa: D105
         # This is done to avoid "[Errno 98] Address already in use"
         self._data.allow_reuse_address = True
         self._data.server_bind()
@@ -123,21 +122,22 @@ class StimServer(object):
         self._clients = list()
         return self
 
-    def __exit__(self, type, value, traceback):
+    def __exit__(self, type, value, traceback):  # noqa: D105
         self.shutdown()
 
     @verbose
     def start(self, timeout=np.inf, verbose=None):
-        """Method to start the server.
+        """Start the server.
 
         Parameters
         ----------
         timeout : float
             Maximum time to wait for clients to be added.
         verbose : bool, str, int, or None
-            If not None, override default verbose level (see mne.verbose).
+            If not None, override default verbose level (see
+            :func:`mne.verbose` and :ref:`Logging documentation <tut_logging>`
+            for more).
         """
-
         # Start server
         if not self._running:
             logger.info('RtServer: Start')
@@ -165,9 +165,10 @@ class StimServer(object):
         sock : instance of socket.socket
             The client socket.
         verbose : bool, str, int, or None
-            If not None, override default verbose level (see mne.verbose).
+            If not None, override default verbose level (see
+            :func:`mne.verbose` and :ref:`Logging documentation <tut_logging>`
+            for more).
         """
-
         logger.info("Adding client with ip = %s" % ip)
 
         client = dict(ip=ip, id=len(self._clients), running=False, socket=sock)
@@ -177,14 +178,15 @@ class StimServer(object):
 
     @verbose
     def shutdown(self, verbose=None):
-        """Method to shutdown the client and server.
+        """Shutdown the client and server.
 
         Parameters
         ----------
         verbose : bool, str, int, or None
-            If not None, override default verbose level (see mne.verbose).
+            If not None, override default verbose level (see
+            :func:`mne.verbose` and :ref:`Logging documentation <tut_logging>`
+            for more).
         """
-
         logger.info("Shutting down ...")
 
         # stop running all the clients
@@ -200,20 +202,21 @@ class StimServer(object):
 
     @verbose
     def add_trigger(self, trigger, verbose=None):
-        """Method to add a trigger.
+        """Add a trigger.
 
         Parameters
         ----------
         trigger : int
             The trigger to be added to the queue for sending to StimClient.
         verbose : bool, str, int, or None
-            If not None, override default verbose level (see mne.verbose).
+            If not None, override default verbose level (see
+            :func:`mne.verbose` and :ref:`Logging documentation <tut_logging>`
+            for more).
 
         See Also
         --------
         StimClient.get_trigger
         """
-
         for client in self._clients:
             client_id = client['id']
             logger.info("Sending trigger %d to client %d"
@@ -222,7 +225,7 @@ class StimServer(object):
 
 
 class StimClient(object):
-    """Stimulation Client
+    """Stimulation Client.
 
     Client to communicate with StimServer
 
@@ -235,7 +238,8 @@ class StimClient(object):
     timeout : float
         Communication timeout in seconds.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     See Also
     --------
@@ -243,8 +247,8 @@ class StimClient(object):
     """
 
     @verbose
-    def __init__(self, host, port=4218, timeout=5.0, verbose=None):
-
+    def __init__(self, host, port=4218, timeout=5.0,
+                 verbose=None):  # noqa: D102
         try:
             logger.info("Setting up client socket")
             self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
@@ -270,19 +274,21 @@ class StimClient(object):
                                'is running.' % (host, port))
 
     def close(self):
-        """Close the socket object"""
+        """Close the socket object."""
         self._sock.close()
 
     @verbose
     def get_trigger(self, timeout=5.0, verbose=None):
-        """Method to get triggers from StimServer.
+        """Get triggers from StimServer.
 
         Parameters
         ----------
         timeout : float
             maximum time to wait for a valid trigger from the server
         verbose : bool, str, int, or None
-            If not None, override default verbose level (see mne.verbose).
+            If not None, override default verbose level (see
+            :func:`mne.verbose` and :ref:`Logging documentation <tut_logging>`
+            for more).
 
         See Also
         --------
diff --git a/mne/realtime/tests/test_mockclient.py b/mne/realtime/tests/test_mockclient.py
index 5bd3217..82657d0 100644
--- a/mne/realtime/tests/test_mockclient.py
+++ b/mne/realtime/tests/test_mockclient.py
@@ -18,21 +18,19 @@ events = read_events(event_name)
 def test_mockclient():
     """Test the RtMockClient."""
 
-    raw = mne.io.read_raw_fif(raw_fname, preload=True, verbose=False,
-                              add_eeg_ref=False)
+    raw = mne.io.read_raw_fif(raw_fname, preload=True, verbose=False)
     picks = mne.pick_types(raw.info, meg='grad', eeg=False, eog=True,
                            stim=True, exclude=raw.info['bads'])
 
     event_id, tmin, tmax = 1, -0.2, 0.5
 
     epochs = Epochs(raw, events[:7], event_id=event_id, tmin=tmin, tmax=tmax,
-                    picks=picks, baseline=(None, 0), preload=True,
-                    add_eeg_ref=False)
+                    picks=picks, baseline=(None, 0), preload=True)
     data = epochs.get_data()
 
     rt_client = MockRtClient(raw)
     rt_epochs = RtEpochs(rt_client, event_id, tmin, tmax, picks=picks,
-                         isi_max=0.5, add_eeg_ref=False)
+                         isi_max=0.5)
 
     rt_epochs.start()
     rt_client.send_data(rt_epochs, picks, tmin=0, tmax=10, buffer_size=1000)
@@ -46,15 +44,14 @@ def test_mockclient():
 def test_get_event_data():
     """Test emulation of realtime data stream."""
 
-    raw = mne.io.read_raw_fif(raw_fname, preload=True, verbose=False,
-                              add_eeg_ref=False)
+    raw = mne.io.read_raw_fif(raw_fname, preload=True, verbose=False)
     picks = mne.pick_types(raw.info, meg='grad', eeg=False, eog=True,
                            stim=True, exclude=raw.info['bads'])
 
     event_id, tmin, tmax = 2, -0.1, 0.3
     epochs = Epochs(raw, events, event_id=event_id,
                     tmin=tmin, tmax=tmax, picks=picks, baseline=None,
-                    preload=True, proj=False, add_eeg_ref=False)
+                    preload=True, proj=False)
 
     data = epochs.get_data()[0, :, :]
 
@@ -69,8 +66,7 @@ def test_get_event_data():
 def test_find_events():
     """Test find_events in rt_epochs."""
 
-    raw = mne.io.read_raw_fif(raw_fname, preload=True, verbose=False,
-                              add_eeg_ref=False)
+    raw = mne.io.read_raw_fif(raw_fname, preload=True, verbose=False)
     picks = mne.pick_types(raw.info, meg='grad', eeg=False, eog=True,
                            stim=True, exclude=raw.info['bads'])
 
@@ -98,7 +94,7 @@ def test_find_events():
     rt_client = MockRtClient(raw)
     rt_epochs = RtEpochs(rt_client, event_id, tmin, tmax, picks=picks,
                          stim_channel='STI 014', isi_max=0.5,
-                         find_events=find_events, add_eeg_ref=False)
+                         find_events=find_events)
     rt_client.send_data(rt_epochs, picks, tmin=0, tmax=10, buffer_size=1000)
     rt_epochs.start()
     events = [5, 6]
@@ -111,7 +107,7 @@ def test_find_events():
     rt_client = MockRtClient(raw)
     rt_epochs = RtEpochs(rt_client, event_id, tmin, tmax, picks=picks,
                          stim_channel='STI 014', isi_max=0.5,
-                         find_events=find_events, add_eeg_ref=False)
+                         find_events=find_events)
     rt_client.send_data(rt_epochs, picks, tmin=0, tmax=10, buffer_size=1000)
     rt_epochs.start()
     events = [5, 6, 5, 6]
@@ -124,7 +120,7 @@ def test_find_events():
     rt_client = MockRtClient(raw)
     rt_epochs = RtEpochs(rt_client, event_id, tmin, tmax, picks=picks,
                          stim_channel='STI 014', isi_max=0.5,
-                         find_events=find_events, add_eeg_ref=False)
+                         find_events=find_events)
     rt_client.send_data(rt_epochs, picks, tmin=0, tmax=10, buffer_size=1000)
     rt_epochs.start()
     events = [5]
@@ -132,12 +128,12 @@ def test_find_events():
         assert_true(ev.comment == str(events[ii]))
     assert_true(ii == 0)
 
-    # ouput='step', consecutive=True
+    # output='step', consecutive=True
     find_events = dict(output='step', consecutive=True)
     rt_client = MockRtClient(raw)
     rt_epochs = RtEpochs(rt_client, event_id, tmin, tmax, picks=picks,
                          stim_channel='STI 014', isi_max=0.5,
-                         find_events=find_events, add_eeg_ref=False)
+                         find_events=find_events)
     rt_client.send_data(rt_epochs, picks, tmin=0, tmax=10, buffer_size=1000)
     rt_epochs.start()
     events = [5, 6, 5, 0, 6, 0]
@@ -145,4 +141,47 @@ def test_find_events():
         assert_true(ev.comment == str(events[ii]))
     assert_true(ii == 5)
 
+    # Reset some data for ease of comparison
+    raw._first_samps[0] = 0
+    raw.info['sfreq'] = 1000
+    # Test that we can handle events at the beginning of the buffer
+    raw._data[stim_channel_idx, :] = 0
+    raw._data[stim_channel_idx, 1000:1005] = 5
+    raw._update_times()
+
+    # Check that we find events that start at the beginning of the buffer
+    find_events = dict(consecutive=False)
+    rt_client = MockRtClient(raw)
+    rt_epochs = RtEpochs(rt_client, event_id, tmin, tmax, picks=picks,
+                         stim_channel='STI 014', isi_max=0.5,
+                         find_events=find_events)
+    rt_client.send_data(rt_epochs, picks, tmin=0, tmax=10, buffer_size=1000)
+    rt_epochs.start()
+    events = [5]
+    for ii, ev in enumerate(rt_epochs.iter_evoked()):
+        assert_true(ev.comment == str(events[ii]))
+    assert_true(ii == 0)
+
+    # Reset some data for ease of comparison
+    raw._first_samps[0] = 0
+    raw.info['sfreq'] = 1000
+    # Test that we can handle events over different buffers
+    raw._data[stim_channel_idx, :] = 0
+    raw._data[stim_channel_idx, 997:1003] = 5
+    raw._update_times()
+    for min_dur in [0.002, 0.004]:
+        find_events = dict(consecutive=False, min_duration=min_dur)
+        rt_client = MockRtClient(raw)
+        rt_epochs = RtEpochs(rt_client, event_id, tmin, tmax, picks=picks,
+                             stim_channel='STI 014', isi_max=0.5,
+                             find_events=find_events)
+        rt_client.send_data(rt_epochs, picks, tmin=0, tmax=10,
+                            buffer_size=1000)
+        rt_epochs.start()
+        events = [5]
+        for ii, ev in enumerate(rt_epochs.iter_evoked()):
+            assert_true(ev.comment == str(events[ii]))
+        assert_true(ii == 0)
+
+
 run_tests_if_main()
diff --git a/mne/report.py b/mne/report.py
index 0861ab3..2e44597 100644
--- a/mne/report.py
+++ b/mne/report.py
@@ -1,5 +1,4 @@
-"""Generate html report from MNE database
-"""
+"""Generate self-contained HTML reports from MNE objects."""
 
 # Authors: Alex Gramfort <alexandre.gramfort at telecom-paristech.fr>
 #          Mainak Jas <mainak at neuro.hut.fi>
@@ -15,14 +14,14 @@ import codecs
 import time
 from glob import glob
 import base64
-from datetime import datetime as dt
 
 import numpy as np
 
 from . import read_evokeds, read_events, pick_types, read_cov
-from .io import Raw, read_info
-from .utils import _TempDir, logger, verbose, get_subjects_dir, warn
-from .viz import plot_events, plot_trans, plot_cov
+from .io import Raw, read_info, _stamp_to_dt
+from .utils import (_TempDir, logger, verbose, get_subjects_dir, warn,
+                    _import_mlab)
+from .viz import plot_events, plot_alignment, plot_cov
 from .viz._3d import _plot_mri_contours
 from .forward import read_forward_solution
 from .epochs import read_epochs
@@ -47,19 +46,19 @@ SECTION_ORDER = ['raw', 'events', 'epochs', 'evoked', 'covariance', 'trans',
 
 def _fig_to_img(function=None, fig=None, image_format='png',
                 scale=None, **kwargs):
-    """Wrapper function to plot figure and create a binary image"""
-
+    """Plot figure and create a binary image."""
     import matplotlib.pyplot as plt
     from matplotlib.figure import Figure
     if not isinstance(fig, Figure) and function is None:
         from scipy.misc import imread
         mlab = None
         try:
-            from mayavi import mlab  # noqa
-        except:  # on some systems importing Mayavi raises SystemExit (!)
-            warn('Could not import mayavi. Trying to render'
+            mlab = _import_mlab()
+        # on some systems importing Mayavi raises SystemExit (!)
+        except Exception as e:
+            warn('Could not import mayavi (%r). Trying to render'
                  '`mayavi.core.scene.Scene` figure instances'
-                 ' will throw an error.')
+                 ' will throw an error.' % (e,))
         tempdir = _TempDir()
         temp_fname = op.join(tempdir, 'test')
         if fig.scene is not None:
@@ -80,8 +79,11 @@ def _fig_to_img(function=None, fig=None, image_format='png',
     output = BytesIO()
     if scale is not None:
         _scale_mpl_figure(fig, scale)
-    fig.savefig(output, format=image_format, bbox_inches='tight',
-                dpi=fig.get_dpi())
+    logger.debug('Saving figure %s with dpi %s'
+                 % (fig.get_size_inches(), fig.get_dpi()))
+    # We don't use bbox_inches='tight' here because it can break
+    # newer matplotlib, and should only save a little bit of space
+    fig.savefig(output, format=image_format, dpi=fig.get_dpi())
     plt.close(fig)
     output = output.getvalue()
     return (output if image_format == 'svg' else
@@ -89,7 +91,7 @@ def _fig_to_img(function=None, fig=None, image_format='png',
 
 
 def _scale_mpl_figure(fig, scale):
-    """Magic scaling helper
+    """Magic scaling helper.
 
     Keeps font-size and artist sizes constant
     0.5 : current font - 4pt
@@ -128,8 +130,7 @@ def _figs_to_mrislices(sl, n_jobs, **kwargs):
 
 
 def _iterate_trans_views(function, **kwargs):
-    """Auxiliary function to iterate over views in trans fig.
-    """
+    """Auxiliary function to iterate over views in trans fig."""
     from scipy.misc import imread
     import matplotlib.pyplot as plt
     import mayavi
@@ -153,7 +154,7 @@ def _iterate_trans_views(function, **kwargs):
         ax.axis('off')
 
     mayavi.mlab.close(fig)
-    img = _fig_to_img(fig=fig2)
+    img = _fig_to_img(fig=fig2, image_format='png')
     return img
 
 ###############################################################################
@@ -161,9 +162,7 @@ def _iterate_trans_views(function, **kwargs):
 
 
 def _is_bad_fname(fname):
-    """Auxiliary function for identifying bad file naming patterns
-       and highlighting them in red in the TOC.
-    """
+    """Identify bad file naming patterns and highlight them in the TOC."""
     if fname.endswith('(whitened)'):
         fname = fname[:-11]
 
@@ -174,7 +173,7 @@ def _is_bad_fname(fname):
 
 
 def _get_fname(fname):
-    """Get fname without -#-"""
+    """Get fname without -#-."""
     if '-#-' in fname:
         fname = fname.split('-#-')[0]
     else:
@@ -184,9 +183,7 @@ def _get_fname(fname):
 
 
 def _get_toc_property(fname):
-    """Auxiliary function to assign class names to TOC
-       list elements to allow toggling with buttons.
-    """
+    """Assign class names to TOC elements to allow toggling with buttons."""
     if fname.endswith(('-eve.fif', '-eve.fif.gz')):
         div_klass = 'events'
         tooltip = fname
@@ -240,9 +237,9 @@ def _get_toc_property(fname):
     return div_klass, tooltip, text
 
 
-def _iterate_files(report, fnames, info, cov, baseline, sfreq, on_error):
-    """Auxiliary function to parallel process in batch mode.
-    """
+def _iterate_files(report, fnames, info, cov, baseline, sfreq, on_error,
+                   image_format):
+    """Parallel process in batch mode."""
     htmls, report_fnames, report_sectionlabels = [], [], []
 
     def _update_html(html, report_fname, report_sectionlabel):
@@ -271,25 +268,26 @@ def _iterate_files(report, fnames, info, cov, baseline, sfreq, on_error):
                 report_sectionlabel = 'inverse'
             elif fname.endswith(('-ave.fif', '-ave.fif.gz')):
                 if cov is not None:
-                    html = report._render_whitened_evoked(fname, cov, baseline)
+                    html = report._render_whitened_evoked(fname, cov, baseline,
+                                                          image_format)
                     report_fname = fname + ' (whitened)'
                     report_sectionlabel = 'evoked'
                     _update_html(html, report_fname, report_sectionlabel)
 
-                html = report._render_evoked(fname, baseline)
+                html = report._render_evoked(fname, baseline, image_format)
                 report_fname = fname
                 report_sectionlabel = 'evoked'
             elif fname.endswith(('-eve.fif', '-eve.fif.gz')):
-                html = report._render_eve(fname, sfreq)
+                html = report._render_eve(fname, sfreq, image_format)
                 report_fname = fname
                 report_sectionlabel = 'events'
             elif fname.endswith(('-epo.fif', '-epo.fif.gz')):
-                html = report._render_epochs(fname)
+                html = report._render_epochs(fname, image_format)
                 report_fname = fname
                 report_sectionlabel = 'epochs'
             elif (fname.endswith(('-cov.fif', '-cov.fif.gz')) and
                   report.info_fname is not None):
-                html = report._render_cov(fname, info)
+                html = report._render_cov(fname, info, image_format)
                 report_fname = fname
                 report_sectionlabel = 'covariance'
             elif (fname.endswith(('-trans.fif', '-trans.fif.gz')) and
@@ -320,10 +318,8 @@ def _iterate_files(report, fnames, info, cov, baseline, sfreq, on_error):
 # IMAGE FUNCTIONS
 
 
-def _build_image(data, cmap='gray'):
-    """Build an image encoded in base64.
-    """
-
+def _build_image_png(data, cmap='gray'):
+    """Build an image encoded in base64."""
     import matplotlib.pyplot as plt
     from matplotlib.figure import Figure
     from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
@@ -342,8 +338,7 @@ def _build_image(data, cmap='gray'):
 
 
 def _iterate_sagittal_slices(array, limits=None):
-    """Iterate sagittal slice.
-    """
+    """Iterate sagittal slices."""
     shape = array.shape[0]
     for ind in range(shape):
         if limits and ind not in limits:
@@ -352,8 +347,7 @@ def _iterate_sagittal_slices(array, limits=None):
 
 
 def _iterate_axial_slices(array, limits=None):
-    """Iterate axial slice.
-    """
+    """Iterate axial slices."""
     shape = array.shape[1]
     for ind in range(shape):
         if limits and ind not in limits:
@@ -362,8 +356,7 @@ def _iterate_axial_slices(array, limits=None):
 
 
 def _iterate_coronal_slices(array, limits=None):
-    """Iterate coronal slice.
-    """
+    """Iterate coronal slices."""
     shape = array.shape[2]
     for ind in range(shape):
         if limits and ind not in limits:
@@ -371,41 +364,44 @@ def _iterate_coronal_slices(array, limits=None):
         yield ind, np.flipud(np.rot90(array[:, :, ind]))
 
 
-def _iterate_mri_slices(name, ind, global_id, slides_klass, data, cmap,
-                        image_format='png'):
-    """Auxiliary function for parallel processing of mri slices.
-    """
+def _iterate_mri_slices(name, ind, global_id, slides_klass, data, cmap):
+    """Auxiliary function for parallel processing of mri slices."""
     img_klass = 'slideimg-%s' % name
 
     caption = u'Slice %s %s' % (name, ind)
     slice_id = '%s-%s-%s' % (name, global_id, ind)
     div_klass = 'span12 %s' % slides_klass
-    img = _build_image(data, cmap=cmap)
+    img = _build_image_png(data, cmap=cmap)
     first = True if ind == 0 else False
-    html = _build_html_image(img, slice_id, div_klass,
-                             img_klass, caption, first)
+    html = _build_html_image(img, slice_id, div_klass, img_klass, caption,
+                             first, image_format='png')
     return ind, html
 
 
 ###############################################################################
 # HTML functions
 
-def _build_html_image(img, id, div_klass, img_klass, caption=None, show=True):
-    """Build a html image from a slice array.
-    """
+def _build_html_image(img, id, div_klass, img_klass, caption=None,
+                      show=True, image_format='png'):
+    """Build a html image from a slice array."""
     html = []
     add_style = u'' if show else u'style="display: none"'
     html.append(u'<li class="%s" id="%s" %s>' % (div_klass, id, add_style))
     html.append(u'<div class="thumbnail">')
-    html.append(u'<img class="%s" alt="" style="width:90%%;" '
-                'src="data:image/png;base64,%s">'
-                % (img_klass, img))
+    if image_format == 'png':
+        html.append(u'<img class="%s" alt="" style="width:90%%;" '
+                    'src="data:image/png;base64,%s">'
+                    % (img_klass, img))
+    else:
+        html.append(u'<div style="text-align:center;" class="%s">%s</div>'
+                    % (img_klass, img))
     html.append(u'</div>')
     if caption:
         html.append(u'<h4>%s</h4>' % caption)
     html.append(u'</li>')
     return u'\n'.join(html)
 
+
 slider_template = HTMLTemplate(u"""
 <script>$("#{{slider_id}}").slider({
                        range: "min",
@@ -446,8 +442,7 @@ slider_full_template = Template(u"""
 
 def _build_html_slider(slices_range, slides_klass, slider_id,
                        start_value=None):
-    """Build an html slider for a given slices range and a slices klass.
-    """
+    """Build an html slider for a given slices range and a slices klass."""
     if start_value is None:
         start_value = slices_range[len(slices_range) // 2]
     return slider_template.substitute(slider_id=slider_id,
@@ -758,13 +753,27 @@ toc_list = Template(u"""
 
 
 def _check_scale(scale):
-    """Helper to ensure valid scale value is passed"""
+    """Ensure valid scale value is passed."""
     if np.isscalar(scale) and scale <= 0:
         raise ValueError('scale must be positive, not %s' % scale)
 
 
+def _check_image_format(rep, image_format):
+    """Ensure fmt is valid."""
+    if image_format not in ('png', 'svg'):
+        if rep is None:
+            raise ValueError('image_format must be "svg" or "png", got %s'
+                             % (image_format,))
+        elif image_format is not None:
+            raise ValueError('image_format must be one of "svg", "png", or '
+                             'None, got %s' % (image_format,))
+        else:  # rep is not None and image_format is None
+            image_format = rep.image_format
+    return image_format
+
+
 class Report(object):
-    """Object for rendering HTML
+    """Object for rendering HTML.
 
     Parameters
     ----------
@@ -789,8 +798,16 @@ class Report(object):
         interval is used.
         The baseline (a, b) includes both endpoints, i.e. all
         timepoints t such that a <= t <= b.
+    image_format : str
+        Default image format to use (default is 'png').
+        SVG uses vector graphics, so fidelity is higher but can increase
+        file size and browser image rendering time as well.
+
+        .. versionadded:: 0.15
+
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Notes
     -----
@@ -801,14 +818,14 @@ class Report(object):
 
     def __init__(self, info_fname=None, subjects_dir=None,
                  subject=None, title=None, cov_fname=None, baseline=None,
-                 verbose=None):
-
+                 image_format='png', verbose=None):  # noqa: D102
         self.info_fname = info_fname
         self.cov_fname = cov_fname
         self.baseline = baseline
         self.subjects_dir = get_subjects_dir(subjects_dir, raise_error=False)
         self.subject = subject
         self.title = title
+        self.image_format = _check_image_format(None, image_format)
         self.verbose = verbose
 
         self.initial_id = 0
@@ -839,18 +856,16 @@ class Report(object):
         return s
 
     def __len__(self):
-        """The number of items in report."""
+        """Return the number of items in report."""
         return len(self.fnames)
 
     def _get_id(self):
-        """Get id of plot.
-        """
+        """Get id of plot."""
         self.initial_id += 1
         return self.initial_id
 
     def _validate_input(self, items, captions, section, comments=None):
-        """Validate input.
-        """
+        """Validate input."""
         if not isinstance(items, (list, tuple)):
             items = [items]
         if not isinstance(captions, (list, tuple)):
@@ -876,9 +891,7 @@ class Report(object):
 
     def _add_figs_to_section(self, figs, captions, section='custom',
                              image_format='png', scale=None, comments=None):
-        """Auxiliary method for `add_section` and `add_figs_to_section`.
-        """
-
+        """Auxiliary method for `add_section` and `add_figs_to_section`."""
         figs, captions, comments = self._validate_input(figs, captions,
                                                         section, comments)
         _check_scale(scale)
@@ -903,7 +916,7 @@ class Report(object):
             self.html.append(html)
 
     def add_figs_to_section(self, figs, captions, section='custom',
-                            scale=None, image_format='png', comments=None):
+                            scale=None, image_format=None, comments=None):
         """Append custom user-defined figures.
 
         Parameters
@@ -923,12 +936,15 @@ class Report(object):
             the relative scaling (might not work for scale <= 1 depending on
             font sizes). If function, should take a figure object as input
             parameter. Defaults to None.
-        image_format : {'png', 'svg'}
-            The image format to be used for the report. Defaults to 'png'.
+        image_format : str | None
+            The image format to be used for the report, can be 'png' or 'svd'.
+            None (default) will use the default specified during Report
+            class construction.
         comments : None | str | list of str
             A string of text or a list of strings of text to be appended after
             the figure.
         """
+        image_format = _check_image_format(self, image_format)
         return self._add_figs_to_section(figs=figs, captions=captions,
                                          section=section, scale=scale,
                                          image_format=image_format,
@@ -977,10 +993,8 @@ class Report(object):
                                  "'svg' are supported. Got %s" % image_format)
 
             # Convert image to binary string.
-            output = BytesIO()
             with open(fname, 'rb') as f:
-                output.write(f.read())
-            img = base64.b64encode(output.getvalue()).decode('ascii')
+                img = base64.b64encode(f.read()).decode('ascii')
             html = image_template.substitute(img=img, id=global_id,
                                              image_format=image_format,
                                              div_klass=div_klass,
@@ -1025,7 +1039,7 @@ class Report(object):
 
     def add_bem_to_section(self, subject, caption='BEM', section='bem',
                            decim=2, n_jobs=1, subjects_dir=None):
-        """Renders a bem slider html str.
+        """Render a bem slider html str.
 
         Parameters
         ----------
@@ -1061,8 +1075,8 @@ class Report(object):
         self.html.extend(html)
 
     def add_slider_to_section(self, figs, captions=None, section='custom',
-                              title='Slider', scale=None, image_format='png'):
-        """Renders a slider of figs to the report.
+                              title='Slider', scale=None, image_format=None):
+        """Render a slider of figs to the report.
 
         Parameters
         ----------
@@ -1085,15 +1099,17 @@ class Report(object):
             the relative scaling (might not work for scale <= 1 depending on
             font sizes). If function, should take a figure object as input
             parameter. Defaults to None.
-        image_format : {'png', 'svg'}
-            The image format to be used for the report. Defaults to 'png'.
+        image_format : str | None
+            The image format to be used for the report, can be 'png' or 'svd'.
+            None (default) will use the default specified during Report
+            class construction.
 
         Notes
         -----
         .. versionadded:: 0.10.0
         """
-
         _check_scale(scale)
+        image_format = _check_image_format(self, image_format)
         if not isinstance(figs[0], list):
             figs = [figs]
         else:
@@ -1133,7 +1149,8 @@ class Report(object):
             slice_id = '%s-%s-%s' % (name, global_id, sl[ii])
             first = True if ii == 0 else False
             slices.append(_build_html_image(img, slice_id, div_klass,
-                          img_klass, caption, first))
+                          img_klass, caption, first,
+                          image_format=image_format))
         # Render the slider
         slider_id = 'select-%s-%s' % (name, global_id)
         # Render the slices
@@ -1155,8 +1172,7 @@ class Report(object):
     # HTML rendering
     def _render_one_axis(self, slices_iter, name, global_id, cmap,
                          n_elements, n_jobs):
-        """Render one axis of the array.
-        """
+        """Render one axis of the array."""
         global_id = global_id or name
         html = []
         slices, slices_range = [], []
@@ -1184,9 +1200,7 @@ class Report(object):
     # global rendering functions
     @verbose
     def _init_render(self, verbose=None):
-        """Initialize the renderer.
-        """
-
+        """Initialize the renderer."""
         inc_fnames = ['jquery-1.10.2.min.js', 'jquery-ui.min.js',
                       'bootstrap.min.js', 'jquery-ui.min.css',
                       'bootstrap.min.css']
@@ -1208,8 +1222,9 @@ class Report(object):
 
     @verbose
     def parse_folder(self, data_path, pattern='*.fif', n_jobs=1, mri_decim=2,
-                     sort_sections=True, on_error='warn', verbose=None):
-        """Renders all the files in the folder.
+                     sort_sections=True, on_error='warn', image_format=None,
+                     verbose=None):
+        r"""Render all the files in the folder.
 
         Parameters
         ----------
@@ -1231,9 +1246,18 @@ class Report(object):
         on_error : str
             What to do if a file cannot be rendered. Can be 'ignore',
             'warn' (default), or 'raise'.
+        image_format : str | None
+            The image format to be used for the report, can be 'png' or 'svd'.
+            None (default) will use the default specified during Report
+            class construction.
+
+            .. versionadded:: 0.15
         verbose : bool, str, int, or None
-            If not None, override default verbose level (see mne.verbose).
+            If not None, override default verbose level (see
+            :func:`mne.verbose` and :ref:`Logging documentation <tut_logging>`
+            for more).
         """
+        image_format = _check_image_format(self, image_format)
         valid_errors = ['ignore', 'warn', 'raise']
         if on_error not in valid_errors:
             raise ValueError('on_error must be one of %s, not %s'
@@ -1272,7 +1296,8 @@ class Report(object):
                     'time)' % len(fnames))
         use_jobs = min(n_jobs, max(1, len(fnames)))
         parallel, p_fun, _ = parallel_func(_iterate_files, use_jobs)
-        r = parallel(p_fun(self, fname, info, cov, baseline, sfreq, on_error)
+        r = parallel(p_fun(self, fname, info, cov, baseline, sfreq, on_error,
+                           image_format)
                      for fname in np.array_split(fnames, use_jobs))
         htmls, report_fnames, report_sectionlabels = zip(*r)
 
@@ -1311,7 +1336,6 @@ class Report(object):
         overwrite : bool
             If True, overwrite report if it already exists.
         """
-
         if fname is None:
             if not hasattr(self, 'data_path'):
                 self.data_path = op.dirname(__file__)
@@ -1354,9 +1378,7 @@ class Report(object):
 
     @verbose
     def _render_toc(self, verbose=None):
-        """Render the Table of Contents.
-        """
-
+        """Render the Table of Contents."""
         logger.info('Rendering : Table of Contents')
 
         html_toc = u'<div id="container">'
@@ -1434,8 +1456,7 @@ class Report(object):
 
     def _render_array(self, array, global_id=None, cmap='gray',
                       limits=None, n_jobs=1):
-        """Render mri without bem contours.
-        """
+        """Render mri without bem contours (only PNG)."""
         html = []
         html.append(u'<div class="row">')
         # Axial
@@ -1465,8 +1486,7 @@ class Report(object):
 
     def _render_one_bem_axis(self, mri_fname, surf_fnames, global_id,
                              shape, orientation='coronal', decim=2, n_jobs=1):
-        """Render one axis of bem contours.
-        """
+        """Render one axis of bem contours (only PNG)."""
         orientation_name2axis = dict(sagittal=0, axial=1, coronal=2)
         orientation_axis = orientation_name2axis[orientation]
         n_slices = shape[orientation_axis]
@@ -1489,7 +1509,8 @@ class Report(object):
             caption = u'Slice %s %s' % (name, sl[ii])
             first = True if ii == 0 else False
             slices.append(_build_html_image(img, slice_id, div_klass,
-                          img_klass, caption, first))
+                                            img_klass, caption, first,
+                                            image_format='png'))
 
         # Render the slider
         slider_id = 'select-%s-%s' % (name, global_id)
@@ -1502,9 +1523,8 @@ class Report(object):
         html.append(u'</div>')
         return '\n'.join(html)
 
-    def _render_image(self, image, cmap='gray', n_jobs=1):
-        """Render one slice of mri without bem.
-        """
+    def _render_image_png(self, image, cmap='gray', n_jobs=1):
+        """Render one slice of mri without bem as a PNG."""
         import nibabel as nib
 
         global_id = self._get_id()
@@ -1523,16 +1543,13 @@ class Report(object):
         html = u'<li class="mri" id="%d">\n' % global_id
         html += u'<h2>%s</h2>\n' % name
         html += self._render_array(data, global_id=global_id,
-                                   cmap=cmap, limits=limits,
-                                   n_jobs=n_jobs)
+                                   cmap=cmap, limits=limits, n_jobs=n_jobs)
         html += u'</li>\n'
         return html
 
     def _render_raw(self, raw_fname):
-        """Render raw.
-        """
+        """Render raw (only text)."""
         global_id = self._get_id()
-        div_klass = 'raw'
         caption = u'Raw : %s' % raw_fname
 
         raw = Raw(raw_fname)
@@ -1552,23 +1569,18 @@ class Report(object):
             ecg = 'Not available'
         meas_date = raw.info['meas_date']
         if meas_date is not None:
-            meas_date = dt.fromtimestamp(meas_date[0]).strftime("%B %d, %Y")
+            meas_date = _stamp_to_dt(meas_date).strftime("%B %d, %Y") + ' GMT'
         tmin = raw.first_samp / raw.info['sfreq']
         tmax = raw.last_samp / raw.info['sfreq']
 
-        html = raw_template.substitute(div_klass=div_klass,
-                                       id=global_id,
-                                       caption=caption,
-                                       info=raw.info,
-                                       meas_date=meas_date,
-                                       n_eeg=n_eeg, n_grad=n_grad,
-                                       n_mag=n_mag, eog=eog,
-                                       ecg=ecg, tmin=tmin, tmax=tmax)
+        html = raw_template.substitute(
+            div_klass='raw', id=global_id, caption=caption, info=raw.info,
+            meas_date=meas_date, n_eeg=n_eeg, n_grad=n_grad, n_mag=n_mag,
+            eog=eog, ecg=ecg, tmin=tmin, tmax=tmax)
         return html
 
     def _render_forward(self, fwd_fname):
-        """Render forward.
-        """
+        """Render forward."""
         div_klass = 'forward'
         caption = u'Forward: %s' % fwd_fname
         fwd = read_forward_solution(fwd_fname)
@@ -1581,8 +1593,7 @@ class Report(object):
         return html
 
     def _render_inverse(self, inv_fname):
-        """Render inverse.
-        """
+        """Render inverse."""
         div_klass = 'inverse'
         caption = u'Inverse: %s' % inv_fname
         inv = read_inverse_operator(inv_fname)
@@ -1594,27 +1605,24 @@ class Report(object):
                                         repr=repr_inv)
         return html
 
-    def _render_evoked(self, evoked_fname, baseline=None, figsize=None):
-        """Render evoked.
-        """
+    def _render_evoked(self, evoked_fname, baseline, image_format):
+        """Render evoked."""
+        logger.debug('Evoked: Reading %s' % evoked_fname)
         evokeds = read_evokeds(evoked_fname, baseline=baseline, verbose=False)
 
         html = []
-        for ev in evokeds:
+        for ei, ev in enumerate(evokeds):
             global_id = self._get_id()
-
             kwargs = dict(show=False)
-            img = _fig_to_img(ev.plot, **kwargs)
-
+            logger.debug('Evoked: Plotting instance %s/%s'
+                         % (ei + 1, len(evokeds)))
+            img = _fig_to_img(ev.plot, image_format=image_format,
+                              **kwargs)
             caption = u'Evoked : %s (%s)' % (evoked_fname, ev.comment)
-            div_klass = 'evoked'
-            img_klass = 'evoked'
-            show = True
-            html.append(image_template.substitute(img=img, id=global_id,
-                                                  div_klass=div_klass,
-                                                  img_klass=img_klass,
-                                                  caption=caption,
-                                                  show=show))
+            html.append(image_template.substitute(
+                img=img, id=global_id, div_klass='evoked',
+                img_klass='evoked', caption=caption, show=True,
+                image_format=image_format))
             has_types = []
             if len(pick_types(ev.info, meg=False, eeg=True)) > 0:
                 has_types.append('eeg')
@@ -1623,134 +1631,100 @@ class Report(object):
             if len(pick_types(ev.info, meg='mag', eeg=False)) > 0:
                 has_types.append('mag')
             for ch_type in has_types:
-                kwargs.update(ch_type=ch_type)
-                img = _fig_to_img(ev.plot_topomap, **kwargs)
+                logger.debug('    Topomap type %s' % ch_type)
+                img = _fig_to_img(ev.plot_topomap, ch_type=ch_type,
+                                  image_format=image_format,
+                                  **kwargs)
                 caption = u'Topomap (ch_type = %s)' % ch_type
-                html.append(image_template.substitute(img=img,
-                                                      div_klass=div_klass,
-                                                      img_klass=img_klass,
-                                                      caption=caption,
-                                                      show=show))
-
+                html.append(image_template.substitute(
+                    img=img, div_klass='evoked', img_klass='evoked',
+                    caption=caption, show=True, image_format=image_format))
+        logger.debug('Evoked: done')
         return '\n'.join(html)
 
-    def _render_eve(self, eve_fname, sfreq=None):
-        """Render events.
-        """
+    def _render_eve(self, eve_fname, sfreq, image_format):
+        """Render events."""
         global_id = self._get_id()
         events = read_events(eve_fname)
-
         kwargs = dict(events=events, sfreq=sfreq, show=False)
-        img = _fig_to_img(plot_events, **kwargs)
-
+        img = _fig_to_img(plot_events, image_format=image_format,
+                          **kwargs)
         caption = 'Events : ' + eve_fname
-        div_klass = 'events'
-        img_klass = 'events'
-        show = True
-
-        html = image_template.substitute(img=img, id=global_id,
-                                         div_klass=div_klass,
-                                         img_klass=img_klass,
-                                         caption=caption,
-                                         show=show)
+        html = image_template.substitute(
+            img=img, id=global_id, div_klass='events', img_klass='events',
+            caption=caption, show=True, image_format=image_format)
         return html
 
-    def _render_epochs(self, epo_fname):
-        """Render epochs.
-        """
+    def _render_epochs(self, epo_fname, image_format):
+        """Render epochs."""
         global_id = self._get_id()
-
         epochs = read_epochs(epo_fname)
         kwargs = dict(subject=self.subject, show=False)
-        img = _fig_to_img(epochs.plot_drop_log, **kwargs)
+        img = _fig_to_img(epochs.plot_drop_log, image_format=image_format,
+                          **kwargs)
         caption = 'Epochs : ' + epo_fname
-        div_klass = 'epochs'
-        img_klass = 'epochs'
         show = True
-        html = image_template.substitute(img=img, id=global_id,
-                                         div_klass=div_klass,
-                                         img_klass=img_klass,
-                                         caption=caption,
-                                         show=show)
+        html = image_template.substitute(
+            img=img, id=global_id, div_klass='epochs', img_klass='epochs',
+            caption=caption, show=show, image_format=image_format)
         return html
 
-    def _render_cov(self, cov_fname, info_fname):
-        """Render cov.
-        """
+    def _render_cov(self, cov_fname, info_fname, image_format):
+        """Render cov."""
         global_id = self._get_id()
         cov = read_cov(cov_fname)
         fig, _ = plot_cov(cov, info_fname, show=False)
-        img = _fig_to_img(fig=fig)
+        img = _fig_to_img(fig=fig, image_format=image_format)
         caption = 'Covariance : %s (n_samples: %s)' % (cov_fname, cov.nfree)
-        div_klass = 'covariance'
-        img_klass = 'covariance'
         show = True
-        html = image_template.substitute(img=img, id=global_id,
-                                         div_klass=div_klass,
-                                         img_klass=img_klass,
-                                         caption=caption,
-                                         show=show)
+        html = image_template.substitute(
+            img=img, id=global_id, div_klass='covariance',
+            img_klass='covariance', caption=caption, show=show,
+            image_format=image_format)
         return html
 
-    def _render_whitened_evoked(self, evoked_fname, noise_cov, baseline):
-        """Show whitened evoked.
-        """
-        global_id = self._get_id()
-
+    def _render_whitened_evoked(self, evoked_fname, noise_cov, baseline,
+                                image_format):
+        """Render whitened evoked."""
         evokeds = read_evokeds(evoked_fname, verbose=False)
-
         html = []
         for ev in evokeds:
-
             ev = read_evokeds(evoked_fname, ev.comment, baseline=baseline,
                               verbose=False)
-
             global_id = self._get_id()
-
             kwargs = dict(noise_cov=noise_cov, show=False)
-            img = _fig_to_img(ev.plot_white, **kwargs)
+            img = _fig_to_img(ev.plot_white, image_format=image_format,
+                              **kwargs)
 
             caption = u'Whitened evoked : %s (%s)' % (evoked_fname, ev.comment)
-            div_klass = 'evoked'
-            img_klass = 'evoked'
             show = True
-            html.append(image_template.substitute(img=img, id=global_id,
-                                                  div_klass=div_klass,
-                                                  img_klass=img_klass,
-                                                  caption=caption,
-                                                  show=show))
+            html.append(image_template.substitute(
+                img=img, id=global_id, div_klass='evoked',
+                img_klass='evoked', caption=caption, show=show,
+                image_format=image_format))
         return '\n'.join(html)
 
-    def _render_trans(self, trans, path, info, subject,
-                      subjects_dir, image_format='png'):
-        """Render trans.
-        """
+    def _render_trans(self, trans, path, info, subject, subjects_dir):
+        """Render trans (only PNG)."""
         kwargs = dict(info=info, trans=trans, subject=subject,
                       subjects_dir=subjects_dir)
         try:
-            img = _iterate_trans_views(function=plot_trans, **kwargs)
+            img = _iterate_trans_views(function=plot_alignment, **kwargs)
         except IOError:
-            img = _iterate_trans_views(function=plot_trans, source='head',
-                                       **kwargs)
+            img = _iterate_trans_views(function=plot_alignment,
+                                       surfaces=['head'], **kwargs)
 
         if img is not None:
             global_id = self._get_id()
-            caption = 'Trans : ' + trans
-            div_klass = 'trans'
-            img_klass = 'trans'
-            show = True
-            html = image_template.substitute(img=img, id=global_id,
-                                             div_klass=div_klass,
-                                             img_klass=img_klass,
-                                             caption=caption,
-                                             width=75,
-                                             show=show)
+            html = image_template.substitute(
+                img=img, id=global_id, div_klass='trans',
+                img_klass='trans', caption='Trans : ' + trans, width=75,
+                show=True, image_format='png')
             return html
 
     def _render_bem(self, subject, subjects_dir, decim, n_jobs,
                     section='mri', caption='BEM'):
-        """Render mri+bem.
-        """
+        """Render mri+bem (only PNG)."""
         import nibabel as nib
 
         subjects_dir = get_subjects_dir(subjects_dir, raise_error=True)
@@ -1765,7 +1739,8 @@ class Report(object):
 
         if not op.isdir(bem_path):
             warn('Subject bem directory "%s" does not exist' % bem_path)
-            return self._render_image(mri_fname, cmap='gray', n_jobs=n_jobs)
+            return self._render_image_png(mri_fname, cmap='gray',
+                                          n_jobs=n_jobs)
 
         surf_fnames = []
         for surf_name in ['*inner_skull', '*outer_skull', '*outer_skin']:
@@ -1777,7 +1752,7 @@ class Report(object):
                 continue
         if len(surf_fnames) == 0:
             warn('No surfaces found at all, rendering empty MRI')
-            return self._render_image(mri_fname, cmap='gray')
+            return self._render_image_png(mri_fname, cmap='gray')
         # XXX : find a better way to get max range of slices
         nim = nib.load(mri_fname)
         data = nim.get_data()
@@ -1810,7 +1785,6 @@ class Report(object):
 
 
 def _clean_varnames(s):
-
     # Remove invalid characters
     s = re.sub('[^0-9a-zA-Z_]', '', s)
 
@@ -1820,8 +1794,7 @@ def _clean_varnames(s):
 
 
 def _recursive_search(path, pattern):
-    """Auxiliary function for recursive_search of the directory.
-    """
+    """Auxiliary function for recursive_search of the directory."""
     filtered_files = list()
     for dirpath, dirnames, files in os.walk(path):
         for f in fnmatch.filter(files, pattern):
@@ -1834,10 +1807,8 @@ def _recursive_search(path, pattern):
 
 
 def _fix_global_ids(html):
-    """Auxiliary function for fixing the global_ids after reordering in
-       _render_toc().
-    """
-    html = re.sub('id="\d+"', 'id="###"', html)
+    """Fix the global_ids after reordering in _render_toc()."""
+    html = re.sub(r'id="\d+"', 'id="###"', html)
     global_id = 1
     while len(re.findall('id="###"', html)) > 0:
         html = re.sub('id="###"', 'id="%s"' % global_id, html, count=1)
diff --git a/mne/selection.py b/mne/selection.py
index f3e3e9d..f3db692 100644
--- a/mne/selection.py
+++ b/mne/selection.py
@@ -20,7 +20,7 @@ _EEG_SELECTIONS = ['EEG 1-32', 'EEG 33-64', 'EEG 65-96', 'EEG 97-128']
 
 @verbose
 def read_selection(name, fname=None, info=None, verbose=None):
-    """Read channel selection from file
+    """Read channel selection from file.
 
     By default, the selections used in ``mne_browse_raw`` are supported.
     Additional selections can be added by specifying a selection file (e.g.
@@ -57,14 +57,14 @@ def read_selection(name, fname=None, info=None, verbose=None):
         of channel names to return, e.g. ``'MEG 0111'`` for old Neuromag
         systems and ``'MEG0111'`` for new ones.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
     sel : list of string
         List with channel names in the selection.
     """
-
     # convert name to list of string
     if not isinstance(name, (list, tuple)):
         name = [name]
@@ -124,7 +124,7 @@ def read_selection(name, fname=None, info=None, verbose=None):
 
 
 def _divide_to_regions(info, add_stim=True):
-    """Divides channels to regions by positions."""
+    """Divide channels to regions by positions."""
     from scipy.stats import zscore
     picks = _pick_data_channels(info, exclude=[])
     chs_in_lobe = len(picks) // 4
@@ -148,12 +148,14 @@ def _divide_to_regions(info, add_stim=True):
     # Because of the way the sides are divided, there may be outliers in the
     # temporal lobes. Here we switch the sides for these outliers. For other
     # lobes it is not a big problem because of the vicinity of the lobes.
-    zs = np.abs(zscore(x[rt]))
-    outliers = np.array(rt)[np.where(zs > 2.)[0]]
+    with np.errstate(invalid='ignore'):  # invalid division, greater compare
+        zs = np.abs(zscore(x[rt]))
+        outliers = np.array(rt)[np.where(zs > 2.)[0]]
     rt = list(np.setdiff1d(rt, outliers))
 
-    zs = np.abs(zscore(x[lt]))
-    outliers = np.append(outliers, (np.array(lt)[np.where(zs > 2.)[0]]))
+    with np.errstate(invalid='ignore'):  # invalid division, greater compare
+        zs = np.abs(zscore(x[lt]))
+        outliers = np.append(outliers, (np.array(lt)[np.where(zs > 2.)[0]]))
     lt = list(np.setdiff1d(lt, outliers))
 
     l_mean = np.mean(x[lt])
@@ -175,7 +177,7 @@ def _divide_to_regions(info, add_stim=True):
 
 
 def _divide_side(lobe, x):
-    """Helper for making a separation between left and right lobe evenly."""
+    """Make a separation between left and right lobe evenly."""
     lobe = np.asarray(lobe)
     median = np.median(x[lobe])
 
diff --git a/mne/simulation/__init__.py b/mne/simulation/__init__.py
index 7140854..c5a9bad 100644
--- a/mne/simulation/__init__.py
+++ b/mne/simulation/__init__.py
@@ -1,5 +1,4 @@
-"""Data simulation code
-"""
+"""Data simulation code."""
 
 from .evoked import add_noise_evoked, simulate_evoked, simulate_noise_evoked
 from .raw import simulate_raw
diff --git a/mne/simulation/evoked.py b/mne/simulation/evoked.py
index b137222..306b7df 100644
--- a/mne/simulation/evoked.py
+++ b/mne/simulation/evoked.py
@@ -5,24 +5,25 @@
 # License: BSD (3-clause)
 import copy
 import warnings
+import math
 
 import numpy as np
 
 from ..io.pick import pick_channels_cov
 from ..forward import apply_forward
-from ..utils import check_random_state, verbose, _time_mask
+from ..utils import check_random_state, verbose, _time_mask, warn, deprecated
 
 
 @verbose
-def simulate_evoked(fwd, stc, info, cov, snr=3., tmin=None, tmax=None,
-                    iir_filter=None, random_state=None, verbose=None):
-    """Generate noisy evoked data
+def simulate_evoked(fwd, stc, info, cov, nave=30, tmin=None, tmax=None,
+                    iir_filter=None, random_state=None, snr=None, use_cps=None,
+                    verbose=None):
+    """Generate noisy evoked data.
 
     .. note:: No projections from ``info`` will be present in the
               output ``evoked``. You can use e.g.
               :func:`evoked.add_proj <mne.Evoked.add_proj>` or
-              :func:`evoked.add_eeg_average_proj
-              <mne.Evoked.add_eeg_average_proj>`
+              :func:`evoked.set_eeg_reference <mne.Evoked.set_eeg_reference>`
               to add them afterward as necessary.
 
     Parameters
@@ -35,21 +36,32 @@ def simulate_evoked(fwd, stc, info, cov, snr=3., tmin=None, tmax=None,
         Measurement info to generate the evoked.
     cov : Covariance object
         The noise covariance.
-    snr : float
-        signal to noise ratio in dB. It corresponds to
-        10 * log10( var(signal) / var(noise) ).
+    nave : int
+        Number of averaged epochs (defaults to 30).
+
+        .. versionadded:: 0.15.0
     tmin : float | None
         start of time interval to estimate SNR. If None first time point
-        is used.
+        is used. tmin is deprecated and will be removed in 0.16.
     tmax : float | None
         start of time interval to estimate SNR. If None last time point
-        is used.
+        is used. tmax is deprecated and will be removed in 0.16.
     iir_filter : None | array
         IIR filter coefficients (denominator) e.g. [1, -1, 0.2].
     random_state : None | int | np.random.RandomState
         To specify the random generator state.
+    snr : float
+        signal to noise ratio in dB. It corresponds to
+        ``10 * log10( var(signal) / var(noise) )``.
+        snr is deprecated and will be removed in 0.16.
+    use_cps : None | bool (default None)
+        Whether to use cortical patch statistics to define normal
+        orientations when converting to fixed orientation (if necessary).
+
+        .. versionadded:: 0.15
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -64,20 +76,52 @@ def simulate_evoked(fwd, stc, info, cov, snr=3., tmin=None, tmax=None,
 
     Notes
     -----
+    To make the equivalence between snr and nave, when the snr is given
+    instead of nave::
+
+        nave = (1 / 10 ** ((actual_snr - snr)) / 20) ** 2
+
+    where actual_snr is the snr to the generated noise before scaling.
+
     .. versionadded:: 0.10.0
     """
-    evoked = apply_forward(fwd, stc, info)
-    if snr < np.inf:
+    if snr is not None:
+        warn('snr is deprecated and will be removed in 0.16. Set it to '
+             'None to remove this warning. Set nave parameter instead.',
+             DeprecationWarning)
+
+    if tmin is not None:
+        warn('tmin is deprecated and will be removed in 0.16. Set it to '
+             'None to remove this warning.',
+             DeprecationWarning)
+
+    if tmax is not None:
+        warn('tmax is deprecated and will be removed in 0.16. Set it to '
+             'None to remove this warning.',
+             DeprecationWarning)
+
+    evoked = apply_forward(fwd, stc, info, use_cps=use_cps)
+    if nave < np.inf:
         noise = simulate_noise_evoked(evoked, cov, iir_filter, random_state)
-        evoked_noise = add_noise_evoked(evoked, noise, snr, tmin=tmin,
-                                        tmax=tmax)
-    else:
-        evoked_noise = evoked
-    return evoked_noise
+
+        # Convert snr to nave before deprecation
+        if snr == np.inf:
+            nave = snr
+        elif snr is not None:
+            tmask = _time_mask(evoked.times, tmin, tmax,
+                               sfreq=evoked.info['sfreq'])
+            tmp = \
+                10 * np.log10(np.mean((evoked.data[:, tmask] ** 2)) /
+                              np.mean((noise.data ** 2)))
+            nave = 1. / 10 ** ((tmp - float(snr)) / 10)
+
+        evoked.data += noise.data / math.sqrt(nave)
+        evoked.nave = np.int(nave)
+    return evoked
 
 
 def simulate_noise_evoked(evoked, cov, iir_filter=None, random_state=None):
-    """Creates noise as a multivariate Gaussian
+    """Create noise as a multivariate Gaussian.
 
     The spatial covariance of the noise is given from the cov matrix.
 
@@ -108,7 +152,7 @@ def simulate_noise_evoked(evoked, cov, iir_filter=None, random_state=None):
 
 
 def _generate_noise(info, cov, iir_filter, random_state, n_samples, zi=None):
-    """Helper to create spatially colored and temporally IIR-filtered noise"""
+    """Create spatially colored and temporally IIR-filtered noise."""
     from scipy.signal import lfilter
     noise_cov = pick_channels_cov(cov, include=info['ch_names'], exclude=[])
     if set(info['ch_names']) != set(noise_cov.ch_names):
@@ -132,8 +176,9 @@ def _generate_noise(info, cov, iir_filter, random_state, n_samples, zi=None):
     return noise, zf
 
 
+ at deprecated('add_noise_evoked will be deprecated and removed in 0.16.')
 def add_noise_evoked(evoked, noise, snr, tmin=None, tmax=None):
-    """Adds noise to evoked object with specified SNR.
+    """Add noise to evoked object with specified SNR.
 
     SNR is computed in the interval from tmin to tmax.
 
@@ -158,8 +203,8 @@ def add_noise_evoked(evoked, noise, snr, tmin=None, tmax=None):
     """
     evoked = copy.deepcopy(evoked)
     tmask = _time_mask(evoked.times, tmin, tmax, sfreq=evoked.info['sfreq'])
-    tmp = 10 * np.log10(np.mean((evoked.data[:, tmask] ** 2).ravel()) /
-                        np.mean((noise.data ** 2).ravel()))
+    tmp = 10 * np.log10(np.mean((evoked.data[:, tmask] ** 2)) /
+                        np.mean((noise.data ** 2)))
     noise.data = 10 ** ((tmp - float(snr)) / 20) * noise.data
     evoked.data += noise.data
     return evoked
diff --git a/mne/simulation/metrics.py b/mne/simulation/metrics.py
index aede064..b7fd59e 100644
--- a/mne/simulation/metrics.py
+++ b/mne/simulation/metrics.py
@@ -12,7 +12,7 @@ from scipy.linalg import norm
 
 
 def _check_stc(stc1, stc2):
-    """Helper for checking that stcs are compatible"""
+    """Check that stcs are compatible."""
     if stc1.data.shape != stc2.data.shape:
         raise ValueError('Data in stcs must have the same size')
     if np.all(stc1.times != stc2.times):
@@ -20,7 +20,7 @@ def _check_stc(stc1, stc2):
 
 
 def source_estimate_quantification(stc1, stc2, metric='rms'):
-    """Helper function to calculate matrix similarities.
+    """Calculate matrix similarities.
 
     Parameters
     ----------
@@ -43,8 +43,6 @@ def source_estimate_quantification(stc1, stc2, metric='rms'):
         * rms: Root mean square of difference between stc data matrices.
         * cosine: Normalized correlation of all elements in stc data matrices.
 
-    Notes
-    -----
     .. versionadded:: 0.10.0
     """
     known_metrics = ['rms', 'cosine']
diff --git a/mne/simulation/raw.py b/mne/simulation/raw.py
index bd16ad4..108a23a 100644
--- a/mne/simulation/raw.py
+++ b/mne/simulation/raw.py
@@ -11,27 +11,30 @@ import numpy as np
 
 from .evoked import _generate_noise
 from ..event import _get_stim_channel
+from ..filter import _Interp2
 from ..io.pick import pick_types, pick_info, pick_channels
 from ..source_estimate import VolSourceEstimate
 from ..cov import make_ad_hoc_cov, read_cov
 from ..bem import fit_sphere_to_headshape, make_sphere_model, read_bem_solution
-from ..io import RawArray, _BaseRaw
-from ..chpi import read_head_pos, head_pos_to_trans_rot_t, _get_hpi_info
+from ..io import RawArray, BaseRaw
+from ..chpi import (read_head_pos, head_pos_to_trans_rot_t, _get_hpi_info,
+                    _get_hpi_initial_fit)
 from ..io.constants import FIFF
 from ..forward import (_magnetic_dipole_field_vec, _merge_meg_eeg_fwds,
                        _stc_src_sel, convert_forward_solution,
                        _prepare_for_forward, _transform_orig_meg_coils,
                        _compute_forwards, _to_forward_dict)
 from ..transforms import _get_trans, transform_surface_to
-from ..source_space import _ensure_src, _points_outside_surface
+from ..source_space import (_ensure_src, _points_outside_surface,
+                            _adjust_patch_info)
 from ..source_estimate import _BaseSourceEstimate
-from ..utils import logger, verbose, check_random_state, warn
+from ..utils import logger, verbose, check_random_state, warn, _pl
 from ..parallel import check_n_jobs
 from ..externals.six import string_types
 
 
 def _log_ch(start, info, ch):
-    """Helper to log channel information"""
+    """Log channel information."""
     if ch is not None:
         extra, just, ch = ' stored on channel:', 50, info['ch_names'][ch]
     else:
@@ -43,8 +46,8 @@ def _log_ch(start, info, ch):
 def simulate_raw(raw, stc, trans, src, bem, cov='simple',
                  blink=False, ecg=False, chpi=False, head_pos=None,
                  mindist=1.0, interp='cos2', iir_filter=None, n_jobs=1,
-                 random_state=None, verbose=None):
-    """Simulate raw data
+                 random_state=None, use_cps=None, verbose=None):
+    u"""Simulate raw data.
 
     Head movements can optionally be simulated using the ``head_pos``
     parameter.
@@ -91,14 +94,15 @@ def simulate_raw(raw, stc, trans, src, bem, cov='simple',
         matrices. If None, the original head position (from
         ``info['dev_head_t']``) will be used. If tuple, should have the
         same format as data returned by `head_pos_to_trans_rot_t`.
-        If array, should be of the form returned by `read_head_pos`.
+        If array, should be of the form returned by
+        :func:`mne.chpi.read_head_pos`.
     mindist : float
         Minimum distance between sources and the inner skull boundary
         to use during forward calculation.
     interp : str
-        Either 'cos2', 'linear', or 'zero', the type of forward-solution
-        interpolation to use between forward solutions at different
-        head positions.
+        Either 'hann', 'cos2', 'linear', or 'zero', the type of
+        forward-solution interpolation to use between forward solutions
+        at different head positions.
     iir_filter : None | array
         IIR filter coefficients (denominator) e.g. [1, -1, 0.2].
     n_jobs : int
@@ -106,8 +110,12 @@ def simulate_raw(raw, stc, trans, src, bem, cov='simple',
     random_state : None | int | np.random.RandomState
         The random generator state used for blink, ECG, and sensor
         noise randomization.
+    use_cps : None | bool (default None)
+        Whether to use cortical patch statistics to define normal
+        orientations. Only used when surf_ori and/or force_fixed are True.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -116,10 +124,10 @@ def simulate_raw(raw, stc, trans, src, bem, cov='simple',
 
     See Also
     --------
-    read_head_pos
+    mne.chpi.read_head_pos
     simulate_evoked
     simulate_stc
-    simalute_sparse_stc
+    simulate_sparse_stc
 
     Notes
     -----
@@ -168,7 +176,7 @@ def simulate_raw(raw, stc, trans, src, bem, cov='simple',
     .. [1] Bentivoglio et al. "Analysis of blink rate patterns in normal
            subjects" Movement Disorders, 1997 Nov;12(6):1028-34.
     """
-    if not isinstance(raw, _BaseRaw):
+    if not isinstance(raw, BaseRaw):
         raise TypeError('raw should be an instance of Raw')
     times, info, first_samp = raw.times, raw.info, raw.first_samp
     raw_verbose = raw.verbose
@@ -185,53 +193,48 @@ def simulate_raw(raw, stc, trans, src, bem, cov='simple',
     n_jobs = check_n_jobs(n_jobs)
 
     rng = check_random_state(random_state)
-    if interp not in ('cos2', 'linear', 'zero'):
-        raise ValueError('interp must be "cos2", "linear", or "zero"')
-
-    if head_pos is None:  # use pos from file
-        dev_head_ts = [info['dev_head_t']] * 2
-        offsets = np.array([0, len(times)])
-        interp = 'zero'
-    # Use position data to simulate head movement
+    interper = _Interp2(interp)
+
+    if head_pos is None:  # use pos from info['dev_head_t']
+        head_pos = dict()
+    if isinstance(head_pos, string_types):  # can be a head pos file
+        head_pos = read_head_pos(head_pos)
+    if isinstance(head_pos, np.ndarray):  # can be head_pos quats
+        head_pos = head_pos_to_trans_rot_t(head_pos)
+    if isinstance(head_pos, tuple):  # can be quats converted to trans, rot, t
+        transs, rots, ts = head_pos
+        ts -= first_samp / info['sfreq']  # MF files need reref
+        dev_head_ts = [np.r_[np.c_[r, t[:, np.newaxis]], [[0, 0, 0, 1]]]
+                       for r, t in zip(rots, transs)]
+        del transs, rots
+    elif isinstance(head_pos, dict):
+        ts = np.array(list(head_pos.keys()), float)
+        ts.sort()
+        dev_head_ts = [head_pos[float(tt)] for tt in ts]
     else:
-        if isinstance(head_pos, string_types):
-            head_pos = read_head_pos(head_pos)
-        if isinstance(head_pos, np.ndarray):
-            head_pos = head_pos_to_trans_rot_t(head_pos)
-        if isinstance(head_pos, tuple):  # can be an already-loaded pos file
-            transs, rots, ts = head_pos
-            ts -= first_samp / info['sfreq']  # MF files need reref
-            dev_head_ts = [np.r_[np.c_[r, t[:, np.newaxis]], [[0, 0, 0, 1]]]
-                           for r, t in zip(rots, transs)]
-            del transs, rots
-        elif isinstance(head_pos, dict):
-            ts = np.array(list(head_pos.keys()), float)
-            ts.sort()
-            dev_head_ts = [head_pos[float(tt)] for tt in ts]
-        else:
-            raise TypeError('unknown head_pos type %s' % type(head_pos))
-        bad = ts < 0
-        if bad.any():
-            raise RuntimeError('All position times must be >= 0, found %s/%s'
-                               '< 0' % (bad.sum(), len(bad)))
-        bad = ts > times[-1]
-        if bad.any():
-            raise RuntimeError('All position times must be <= t_end (%0.1f '
-                               'sec), found %s/%s bad values (is this a split '
-                               'file?)' % (times[-1], bad.sum(), len(bad)))
-        if ts[0] > 0:
-            ts = np.r_[[0.], ts]
-            dev_head_ts.insert(0, info['dev_head_t']['trans'])
-        dev_head_ts = [{'trans': d, 'to': info['dev_head_t']['to'],
-                        'from': info['dev_head_t']['from']}
-                       for d in dev_head_ts]
-        if ts[-1] < times[-1]:
-            dev_head_ts.append(dev_head_ts[-1])
-            ts = np.r_[ts, [times[-1]]]
-        offsets = raw.time_as_index(ts)
-        offsets[-1] = len(times)  # fix for roundoff error
-        assert offsets[-2] != offsets[-1]
-        del ts
+        raise TypeError('unknown head_pos type %s' % type(head_pos))
+    bad = ts < 0
+    if bad.any():
+        raise RuntimeError('All position times must be >= 0, found %s/%s'
+                           '< 0' % (bad.sum(), len(bad)))
+    bad = ts > times[-1]
+    if bad.any():
+        raise RuntimeError('All position times must be <= t_end (%0.1f '
+                           'sec), found %s/%s bad values (is this a split '
+                           'file?)' % (times[-1], bad.sum(), len(bad)))
+    # If it doesn't start at zero, insert one at t=0
+    if len(ts) == 0 or ts[0] > 0:
+        ts = np.r_[[0.], ts]
+        dev_head_ts.insert(0, info['dev_head_t']['trans'])
+    dev_head_ts = [{'trans': d, 'to': info['dev_head_t']['to'],
+                    'from': info['dev_head_t']['from']}
+                   for d in dev_head_ts]
+    offsets = raw.time_as_index(ts)
+    offsets = np.concatenate([offsets, [len(times)]])
+    assert offsets[-2] != offsets[-1]
+    assert np.array_equal(offsets, np.unique(offsets))
+    assert len(offsets) == len(dev_head_ts) + 1
+    del ts
 
     src = _ensure_src(src, verbose=False)
     if isinstance(bem, string_types):
@@ -241,12 +244,10 @@ def simulate_raw(raw, stc, trans, src, bem, cov='simple',
             cov = make_ad_hoc_cov(info, verbose=False)
         else:
             cov = read_cov(cov, verbose=False)
-    assert np.array_equal(offsets, np.unique(offsets))
-    assert len(offsets) == len(dev_head_ts)
     approx_events = int((len(times) / info['sfreq']) /
                         (stc.times[-1] - stc.times[0]))
     logger.info('Provided parameters will provide approximately %s event%s'
-                % (approx_events, '' if approx_events == 1 else 's'))
+                % (approx_events, _pl(approx_events)))
 
     # Extract necessary info
     meeg_picks = pick_types(info, meg=True, eeg=True, exclude=[])  # for sim
@@ -254,8 +255,8 @@ def simulate_raw(raw, stc, trans, src, bem, cov='simple',
     fwd_info = pick_info(info, meeg_picks)
     fwd_info['projs'] = []  # Ensure no 'projs' applied
     logger.info('Setting up raw simulation: %s position%s, "%s" interpolation'
-                % (len(dev_head_ts), 's' if len(dev_head_ts) != 1 else '',
-                   interp))
+                % (len(dev_head_ts), _pl(dev_head_ts), interp))
+    del interp
 
     verts = stc.vertices
     verts = [verts] if isinstance(stc, VolSourceEstimate) else verts
@@ -269,7 +270,8 @@ def simulate_raw(raw, stc, trans, src, bem, cov='simple',
     ecg = ecg and len(meg_picks) > 0
     chpi = chpi and len(meg_picks) > 0
     if chpi:
-        hpi_freqs, hpi_rrs, hpi_pick, hpi_ons = _get_hpi_info(info)[:4]
+        hpi_freqs, hpi_pick, hpi_ons = _get_hpi_info(info)
+        hpi_rrs = _get_hpi_initial_fit(info, verbose='error')
         hpi_nns = hpi_rrs / np.sqrt(np.sum(hpi_rrs * hpi_rrs,
                                            axis=1))[:, np.newaxis]
         # turn on cHPI in file
@@ -351,8 +353,9 @@ def simulate_raw(raw, stc, trans, src, bem, cov='simple',
     used = np.zeros(len(times), bool)
     stc_indices = np.arange(len(times)) % len(stc.times)
     raw_data[meeg_picks, :] = 0.
-    hpi_mag = 70e-9
-    last_fwd = last_fwd_chpi = last_fwd_blink = last_fwd_ecg = src_sel = None
+    if chpi:
+        sinusoids = 70e-9 * np.sin(2 * np.pi * hpi_freqs[:, np.newaxis] *
+                                   (np.arange(len(times)) / info['sfreq']))
     zf = None  # final filter conditions for the noise
     # don't process these any more if no MEG present
     for fi, (fwd, fwd_blink, fwd_ecg, fwd_chpi) in \
@@ -362,8 +365,8 @@ def simulate_raw(raw, stc, trans, src, bem, cov='simple',
         # must be fixed orientation
         # XXX eventually we could speed this up by allowing the forward
         # solution code to only compute the normal direction
-        fwd = convert_forward_solution(fwd, surf_ori=True,
-                                       force_fixed=True, verbose=False)
+        fwd = convert_forward_solution(fwd, surf_ori=True, force_fixed=True,
+                                       use_cps=use_cps, verbose=False)
         if blink:
             fwd_blink = fwd_blink['sol']['data']
             for ii in range(len(blink_rrs)):
@@ -382,86 +385,59 @@ def simulate_raw(raw, stc, trans, src, bem, cov='simple',
                                          hpi_nns[ii])
             fwd_chpi = fwd_chpi[:, :len(hpi_rrs)].copy()
 
-        if src_sel is None:
+        interper['fwd'] = fwd['sol']['data']
+        interper['fwd_blink'] = fwd_blink
+        interper['fwd_ecg'] = fwd_ecg
+        interper['fwd_chpi'] = fwd_chpi
+        if fi == 0:
             src_sel = _stc_src_sel(fwd['src'], stc)
             verts = stc.vertices
             verts = [verts] if isinstance(stc, VolSourceEstimate) else verts
             diff_ = sum([len(v) for v in verts]) - len(src_sel)
             if diff_ != 0:
                 warn('%s STC vertices omitted due to fwd calculation' % diff_)
-        if last_fwd is None:
-            last_fwd, last_fwd_blink, last_fwd_ecg, last_fwd_chpi = \
-                fwd, fwd_blink, fwd_ecg, fwd_chpi
+            stc_data = stc.data[src_sel]
+            del stc, src_sel, diff_, verts
             continue
+        del fwd, fwd_blink, fwd_ecg, fwd_chpi
 
-        # set up interpolation
-        n_pts = offsets[fi] - offsets[fi - 1]
-        if interp == 'zero':
-            interps = None
-        else:
-            if interp == 'linear':
-                interps = np.linspace(1, 0, n_pts, endpoint=False)
-            else:  # interp == 'cos2':
-                interps = np.cos(0.5 * np.pi * np.arange(n_pts)) ** 2
-            interps = np.array([interps, 1 - interps])
-
-        assert not used[offsets[fi - 1]:offsets[fi]].any()
-        event_idxs = np.where(stc_indices[offsets[fi - 1]:offsets[fi]] ==
-                              stc_event_idx)[0] + offsets[fi - 1]
+        start, stop = offsets[fi - 1:fi + 1]
+        assert not used[start:stop].any()
+        event_idxs = np.where(stc_indices[start:stop] ==
+                              stc_event_idx)[0] + start
         if stim:
             raw_data[event_ch, event_idxs] = fi
 
         logger.info('  Simulating data for %0.3f-%0.3f sec with %s event%s'
-                    % (tuple(offsets[fi - 1:fi + 1] / info['sfreq']) +
-                       (len(event_idxs), '' if len(event_idxs) == 1 else 's')))
-
-        # Process data in large chunks to save on memory
-        chunk_size = 10000
-        chunks = np.concatenate((np.arange(offsets[fi - 1], offsets[fi],
-                                           chunk_size), [offsets[fi]]))
-        for start, stop in zip(chunks[:-1], chunks[1:]):
-            assert stop - start <= chunk_size
-
-            used[start:stop] = True
-            if interp == 'zero':
-                this_interp = None
-            else:
-                this_interp = interps[:, start - chunks[0]:stop - chunks[0]]
-            time_sl = slice(start, stop)
-            this_t = np.arange(start, stop) / info['sfreq']
-            stc_idxs = stc_indices[time_sl]
-
-            # simulate brain data
-            raw_data[meeg_picks, time_sl] = \
-                _interp(last_fwd['sol']['data'], fwd['sol']['data'],
-                        stc.data[:, stc_idxs][src_sel], this_interp)
-
-            # add sensor noise, ECG, blink, cHPI
-            if cov is not None:
-                noise, zf = _generate_noise(fwd_info, cov, iir_filter, rng,
-                                            len(stc_idxs), zi=zf)
-                raw_data[meeg_picks, time_sl] += noise
-            if blink:
-                raw_data[meeg_picks, time_sl] += \
-                    _interp(last_fwd_blink, fwd_blink, blink_data[:, time_sl],
-                            this_interp)
-            if ecg:
-                raw_data[meg_picks, time_sl] += \
-                    _interp(last_fwd_ecg, fwd_ecg, ecg_data[:, time_sl],
-                            this_interp)
-            if chpi:
-                sinusoids = np.zeros((len(hpi_freqs), len(stc_idxs)))
-                for fidx, freq in enumerate(hpi_freqs):
-                    sinusoids[fidx] = 2 * np.pi * freq * this_t
-                    sinusoids[fidx] = hpi_mag * np.sin(sinusoids[fidx])
-                raw_data[meg_picks, time_sl] += \
-                    _interp(last_fwd_chpi, fwd_chpi, sinusoids, this_interp)
-
-        assert used[offsets[fi - 1]:offsets[fi]].all()
-
-        # prepare for next iteration
-        last_fwd, last_fwd_blink, last_fwd_ecg, last_fwd_chpi = \
-            fwd, fwd_blink, fwd_ecg, fwd_chpi
+                    % (start / info['sfreq'], stop / info['sfreq'],
+                       len(event_idxs), _pl(event_idxs)))
+
+        used[start:stop] = True
+        time_sl = slice(start, stop)
+        stc_idxs = stc_indices[time_sl]
+
+        # simulate brain data
+        this_data = raw_data[:, time_sl]
+        this_data[meeg_picks] = 0.
+        interper.n_samp = stop - start
+        interper.interpolate('fwd', stc_data,
+                             this_data, meeg_picks, stc_idxs)
+        if blink:
+            interper.interpolate('fwd_blink', blink_data[:, time_sl],
+                                 this_data, meeg_picks)
+        if ecg:
+            interper.interpolate('fwd_ecg', ecg_data[:, time_sl],
+                                 this_data, meg_picks)
+        if chpi:
+            interper.interpolate('fwd_chpi', sinusoids[:, time_sl],
+                                 this_data, meg_picks)
+        # add sensor noise, ECG, blink, cHPI
+        if cov is not None:
+            noise, zf = _generate_noise(fwd_info, cov, iir_filter, rng,
+                                        len(stc_idxs), zi=zf)
+            raw_data[meeg_picks, time_sl] += noise
+            this_data[meeg_picks] += noise
+        assert used[start:stop].all()
     assert used.all()
     raw = RawArray(raw_data, info, first_samp=first_samp, verbose=False)
     raw.verbose = raw_verbose
@@ -471,7 +447,7 @@ def simulate_raw(raw, stc, trans, src, bem, cov='simple',
 
 def _iter_forward_solutions(info, trans, src, bem, exg_bem, dev_head_ts,
                             mindist, hpi_rrs, blink_rrs, ecg_rrs, n_jobs):
-    """Calculate a forward solution for a subject"""
+    """Calculate a forward solution for a subject."""
     mri_head_t, trans = _get_trans(trans)
     logger.info('Setting up forward solutions')
     megcoils, meg_info, compcoils, megnames, eegels, eegnames, rr, info, \
@@ -487,12 +463,16 @@ def _iter_forward_solutions(info, trans, src, bem, exg_bem, dev_head_ts,
                                      [None], ['eeg'], n_jobs,
                                      verbose=False)[0]
         eegblink = _to_forward_dict(eegblink, eegnames)
+    else:
+        eegblink = None
 
     # short circuit here if there are no MEG channels (don't need to iterate)
     if len(pick_types(info, meg=True)) == 0:
         eegfwd.update(**update_kwargs)
         for _ in dev_head_ts:
             yield eegfwd, eegblink, None, None
+        # extra one to fill last buffer
+        yield eegfwd, eegblink, None, None
         return
 
     coord_frame = FIFF.FIFFV_COORD_HEAD
@@ -500,8 +480,9 @@ def _iter_forward_solutions(info, trans, src, bem, exg_bem, dev_head_ts,
         idx = np.where(np.array([s['id'] for s in bem['surfs']]) ==
                        FIFF.FIFFV_BEM_SURF_ID_BRAIN)[0]
         assert len(idx) == 1
+        # make a copy so it isn't mangled in use
         bem_surf = transform_surface_to(bem['surfs'][idx[0]], coord_frame,
-                                        mri_head_t)
+                                        mri_head_t, copy=True)
     for ti, dev_head_t in enumerate(dev_head_ts):
         # Could be *slightly* more efficient not to do this N times,
         # but the cost here is tiny compared to actual fwd calculation
@@ -546,10 +527,12 @@ def _iter_forward_solutions(info, trans, src, bem, exg_bem, dev_head_ts,
         if hpi_rrs is not None:
             fwd_chpi = _magnetic_dipole_field_vec(hpi_rrs, megcoils).T
         yield fwd, fwd_blink, fwd_ecg, fwd_chpi
+    # need an extra one to fill last buffer
+    yield fwd, fwd_blink, fwd_ecg, fwd_chpi
 
 
 def _restrict_source_space_to(src, vertices):
-    """Helper to trim down a source space"""
+    """Trim down a source space."""
     assert len(src) == len(vertices)
     src = deepcopy(src)
     for s, v in zip(src, vertices):
@@ -557,20 +540,9 @@ def _restrict_source_space_to(src, vertices):
         s['nuse'] = len(v)
         s['vertno'] = v
         s['inuse'][s['vertno']] = 1
-        del s['pinfo']
-        del s['nuse_tri']
-        del s['use_tris']
-        del s['patch_inds']
+        for key in ('nuse_tri', 'use_tris'):
+            if key in s:
+                del s[key]
+        # This will fix 'patch_info' and 'pinfo'
+        _adjust_patch_info(s, verbose=False)
     return src
-
-
-def _interp(data_1, data_2, stc_data, interps):
-    """Helper to interpolate"""
-    out_data = np.dot(data_1, stc_data)
-    if interps is not None:
-        out_data *= interps[0]
-        data_1 = np.dot(data_1, stc_data)
-        data_1 *= interps[1]
-        out_data += data_1
-        del data_1
-    return out_data
diff --git a/mne/simulation/source.py b/mne/simulation/source.py
index 90b6588..a5496bf 100644
--- a/mne/simulation/source.py
+++ b/mne/simulation/source.py
@@ -16,7 +16,7 @@ from ..externals.six.moves import zip
 
 def select_source_in_label(src, label, random_state=None, location='random',
                            subject=None, subjects_dir=None, surf='sphere'):
-    """Select source positions using a label
+    """Select source positions using a label.
 
     Parameters
     ----------
@@ -91,7 +91,7 @@ def simulate_sparse_stc(src, n_dipoles, times,
                         data_fun=lambda t: 1e-7 * np.sin(20 * np.pi * t),
                         labels=None, random_state=None, location='random',
                         subject=None, subjects_dir=None, surf='sphere'):
-    """Generate sparse (n_dipoles) sources time courses from data_fun
+    """Generate sparse (n_dipoles) sources time courses from data_fun.
 
     This function randomly selects ``n_dipoles`` vertices in the whole
     cortex or one single vertex (randomly in or in the center of) each
@@ -217,7 +217,7 @@ def simulate_sparse_stc(src, n_dipoles, times,
 
 
 def simulate_stc(src, labels, stc_data, tmin, tstep, value_fun=None):
-    """Simulate sources time courses from waveforms and labels
+    """Simulate sources time courses from waveforms and labels.
 
     This function generates a source estimate with extended sources by
     filling the labels with the waveforms given in stc_data.
diff --git a/mne/simulation/tests/test_evoked.py b/mne/simulation/tests/test_evoked.py
index 00418f9..a16857f 100644
--- a/mne/simulation/tests/test_evoked.py
+++ b/mne/simulation/tests/test_evoked.py
@@ -6,16 +6,16 @@ import os.path as op
 
 import numpy as np
 from numpy.testing import (assert_array_almost_equal, assert_array_equal,
-                           assert_almost_equal)
+                           assert_equal, assert_allclose)
 from nose.tools import assert_true, assert_raises
 import warnings
 
+from mne import (read_cov, read_forward_solution, convert_forward_solution,
+                 pick_types_forward, read_evokeds)
 from mne.datasets import testing
-from mne import read_forward_solution
 from mne.simulation import simulate_sparse_stc, simulate_evoked
-from mne import read_cov
 from mne.io import read_raw_fif
-from mne import pick_types_forward, read_evokeds
+from mne.cov import regularize
 from mne.utils import run_tests_if_main
 
 warnings.simplefilter('always')
@@ -35,15 +35,18 @@ cov_fname = op.join(op.dirname(__file__), '..', '..', 'io', 'tests',
 def test_simulate_evoked():
     """Test simulation of evoked data."""
 
-    raw = read_raw_fif(raw_fname, add_eeg_ref=False)
-    fwd = read_forward_solution(fwd_fname, force_fixed=True)
+    raw = read_raw_fif(raw_fname)
+    fwd = read_forward_solution(fwd_fname)
+    fwd = convert_forward_solution(fwd, force_fixed=True, use_cps=False)
     fwd = pick_types_forward(fwd, meg=True, eeg=True, exclude=raw.info['bads'])
     cov = read_cov(cov_fname)
 
     evoked_template = read_evokeds(ave_fname, condition=0, baseline=None)
     evoked_template.pick_types(meg=True, eeg=True, exclude=raw.info['bads'])
 
-    snr = 6  # dB
+    cov = regularize(cov, evoked_template.info)
+    nave = evoked_template.nave
+
     tmin = -0.1
     sfreq = 1000.  # Hz
     tstep = 1. / sfreq
@@ -51,15 +54,16 @@ def test_simulate_evoked():
     times = np.linspace(tmin, tmin + n_samples * tstep, n_samples)
 
     # Generate times series for 2 dipoles
-    stc = simulate_sparse_stc(fwd['src'], n_dipoles=2, times=times)
-    stc._data *= 1e-9
+    stc = simulate_sparse_stc(fwd['src'], n_dipoles=2, times=times,
+                              random_state=42)
 
     # Generate noisy evoked data
     iir_filter = [1, -0.9]
-    evoked = simulate_evoked(fwd, stc, evoked_template.info, cov, snr,
-                             tmin=0.0, tmax=0.2, iir_filter=iir_filter)
+    evoked = simulate_evoked(fwd, stc, evoked_template.info, cov,
+                             iir_filter=iir_filter, nave=nave)
     assert_array_almost_equal(evoked.times, stc.times)
     assert_true(len(evoked.data) == len(fwd['sol']['data']))
+    assert_equal(evoked.nave, nave)
 
     # make a vertex that doesn't exist in fwd, should throw error
     stc_bad = stc.copy()
@@ -67,29 +71,23 @@ def test_simulate_evoked():
     stc_bad.vertices[0][0] = mv + 1
 
     assert_raises(RuntimeError, simulate_evoked, fwd, stc_bad,
-                  evoked_template.info, cov, snr, tmin=0.0, tmax=0.2)
-    evoked_1 = simulate_evoked(fwd, stc, evoked_template.info, cov, np.inf,
-                               tmin=0.0, tmax=0.2)
-    evoked_2 = simulate_evoked(fwd, stc, evoked_template.info, cov, np.inf,
-                               tmin=0.0, tmax=0.2)
+                  evoked_template.info, cov)
+    evoked_1 = simulate_evoked(fwd, stc, evoked_template.info, cov,
+                               nave=np.inf)
+    evoked_2 = simulate_evoked(fwd, stc, evoked_template.info, cov,
+                               nave=np.inf)
     assert_array_equal(evoked_1.data, evoked_2.data)
 
-    # test snr definition in dB
-    evoked_noise = simulate_evoked(fwd, stc, evoked_template.info, cov,
-                                   snr=snr, tmin=None, tmax=None,
-                                   iir_filter=None)
-    evoked_clean = simulate_evoked(fwd, stc, evoked_template.info, cov,
-                                   snr=np.inf, tmin=None, tmax=None,
-                                   iir_filter=None)
-    noise = evoked_noise.data - evoked_clean.data
-
-    empirical_snr = 10 * np.log10(np.mean((evoked_clean.data ** 2).ravel()) /
-                                  np.mean((noise ** 2).ravel()))
-
-    assert_almost_equal(snr, empirical_snr, decimal=5)
+    # Test the equivalence snr to nave
+    with warnings.catch_warnings(record=True):  # deprecation
+        evoked = simulate_evoked(fwd, stc, evoked_template.info, cov,
+                                 snr=6, random_state=42)
+    assert_allclose(np.linalg.norm(evoked.data, ord='fro'),
+                    0.00078346820226502716)
 
     cov['names'] = cov.ch_names[:-2]  # Error channels are different.
     assert_raises(ValueError, simulate_evoked, fwd, stc, evoked_template.info,
-                  cov, snr=3., tmin=None, tmax=None, iir_filter=None)
+                  cov, nave=nave, iir_filter=None)
+
 
 run_tests_if_main()
diff --git a/mne/simulation/tests/test_metrics.py b/mne/simulation/tests/test_metrics.py
index c6915ea..d8f60ca 100644
--- a/mne/simulation/tests/test_metrics.py
+++ b/mne/simulation/tests/test_metrics.py
@@ -44,7 +44,7 @@ def test_metrics():
     stc_bad = stc2.copy().crop(0, 0.5)
     assert_raises(ValueError, source_estimate_quantification, stc1, stc_bad)
     stc_bad = stc2.copy()
-    stc_bad.times -= 0.1
+    stc_bad.tmin -= 0.1
     assert_raises(ValueError, source_estimate_quantification, stc1, stc_bad)
     assert_raises(ValueError, source_estimate_quantification, stc1, stc2,
                   metric='foo')
diff --git a/mne/simulation/tests/test_raw.py b/mne/simulation/tests/test_raw.py
index 0b1a310..e28eddb 100644
--- a/mne/simulation/tests/test_raw.py
+++ b/mne/simulation/tests/test_raw.py
@@ -11,19 +11,22 @@ from copy import deepcopy
 import numpy as np
 from numpy.testing import assert_allclose, assert_array_equal
 from nose.tools import assert_true, assert_raises, assert_equal
+import pytest
 
 from mne import (read_source_spaces, pick_types, read_trans, read_cov,
                  make_sphere_model, create_info, setup_volume_source_space,
                  find_events, Epochs, fit_dipole, transform_surface_to,
-                 make_ad_hoc_cov, SourceEstimate, setup_source_space)
-from mne.chpi import (_calculate_chpi_positions, read_head_pos,
-                      _get_hpi_info, head_pos_to_trans_rot_t)
-from mne.tests.test_chpi import _compare_positions
+                 make_ad_hoc_cov, SourceEstimate, setup_source_space,
+                 read_bem_solution, make_forward_solution,
+                 convert_forward_solution)
+from mne.chpi import _calculate_chpi_positions, read_head_pos, _get_hpi_info
+from mne.tests.test_chpi import _assert_quats
 from mne.datasets import testing
 from mne.simulation import simulate_sparse_stc, simulate_raw
+from mne.source_space import _compare_source_spaces
 from mne.io import read_raw_fif, RawArray
 from mne.time_frequency import psd_welch
-from mne.utils import _TempDir, run_tests_if_main, requires_version, slow_test
+from mne.utils import _TempDir, run_tests_if_main
 
 
 warnings.simplefilter('always')
@@ -38,6 +41,7 @@ subjects_dir = op.join(data_path, 'subjects')
 bem_path = op.join(subjects_dir, 'sample', 'bem')
 src_fname = op.join(bem_path, 'sample-oct-2-src.fif')
 bem_fname = op.join(bem_path, 'sample-320-320-320-bem-sol.fif')
+bem_1_fname = op.join(bem_path, 'sample-320-bem-sol.fif')
 
 raw_chpi_fname = op.join(data_path, 'SSS', 'test_move_anon_raw.fif')
 pos_fname = op.join(data_path, 'SSS', 'test_move_anon_raw_subsampled.pos')
@@ -57,8 +61,7 @@ def _make_stc(raw, src):
 def _get_data():
     """Helper to get some starting data."""
     # raw with ECG channel
-    raw = read_raw_fif(raw_fname, add_eeg_ref=False)
-    raw.crop(0., 5.0, copy=False).load_data()
+    raw = read_raw_fif(raw_fname).crop(0., 5.0).load_data()
     data_picks = pick_types(raw.info, meg=True, eeg=True)
     other_picks = pick_types(raw.info, meg=False, stim=True, eog=True)
     picks = np.sort(np.concatenate((data_picks[::16], other_picks)))
@@ -66,8 +69,7 @@ def _get_data():
     raw.info.normalize_proj()
     ecg = RawArray(np.zeros((1, len(raw.times))),
                    create_info(['ECG 063'], raw.info['sfreq'], 'ecg'))
-    for key in ('dev_head_t', 'buffer_size_sec', 'highpass', 'lowpass',
-                'filename', 'dig'):
+    for key in ('dev_head_t', 'buffer_size_sec', 'highpass', 'lowpass', 'dig'):
         ecg.info[key] = raw.info[key]
     raw.add_channels([ecg])
 
@@ -101,18 +103,19 @@ def test_simulate_raw_sphere():
     #
     raw_sim = simulate_raw(raw, stc, trans, src, sphere, read_cov(cov_fname),
                            head_pos=head_pos_sim,
-                           blink=True, ecg=True, random_state=seed)
+                           blink=True, ecg=True, random_state=seed,
+                           use_cps=True)
     raw_sim_2 = simulate_raw(raw, stc, trans_fname, src_fname, sphere,
                              cov_fname, head_pos=head_pos_sim,
-                             blink=True, ecg=True, random_state=seed)
+                             blink=True, ecg=True, random_state=seed,
+                             use_cps=True)
     assert_array_equal(raw_sim_2[:][0], raw_sim[:][0])
     # Test IO on processed data
     tempdir = _TempDir()
     test_outname = op.join(tempdir, 'sim_test_raw.fif')
     raw_sim.save(test_outname)
 
-    raw_sim_loaded = read_raw_fif(test_outname, preload=True, proj=False,
-                                  add_eeg_ref=False)
+    raw_sim_loaded = read_raw_fif(test_outname, preload=True)
     assert_allclose(raw_sim_loaded[:][0], raw_sim[:][0], rtol=1e-6, atol=1e-20)
     del raw_sim, raw_sim_2
     # with no cov (no noise) but with artifacts, most time periods should match
@@ -120,10 +123,12 @@ def test_simulate_raw_sphere():
     for ecg, eog in ((True, False), (False, True), (True, True)):
         raw_sim_3 = simulate_raw(raw, stc, trans, src, sphere,
                                  cov=None, head_pos=head_pos_sim,
-                                 blink=eog, ecg=ecg, random_state=seed)
+                                 blink=eog, ecg=ecg, random_state=seed,
+                                 use_cps=True)
         raw_sim_4 = simulate_raw(raw, stc, trans, src, sphere,
                                  cov=None, head_pos=head_pos_sim,
-                                 blink=False, ecg=False, random_state=seed)
+                                 blink=False, ecg=False, random_state=seed,
+                                 use_cps=True)
         picks = np.arange(len(raw.ch_names))
         diff_picks = pick_types(raw.info, meg=False, ecg=ecg, eog=eog)
         these_picks = np.setdiff1d(picks, diff_picks)
@@ -138,74 +143,87 @@ def test_simulate_raw_sphere():
     # make sure it works with EEG-only and MEG-only
     raw_sim_meg = simulate_raw(raw.copy().pick_types(meg=True, eeg=False),
                                stc, trans, src, sphere, cov=None,
-                               ecg=True, blink=True, random_state=seed)
+                               ecg=True, blink=True, random_state=seed,
+                               use_cps=True)
     raw_sim_eeg = simulate_raw(raw.copy().pick_types(meg=False, eeg=True),
                                stc, trans, src, sphere, cov=None,
-                               ecg=True, blink=True, random_state=seed)
+                               ecg=True, blink=True, random_state=seed,
+                               use_cps=True)
     raw_sim_meeg = simulate_raw(raw.copy().pick_types(meg=True, eeg=True),
                                 stc, trans, src, sphere, cov=None,
-                                ecg=True, blink=True, random_state=seed)
+                                ecg=True, blink=True, random_state=seed,
+                                use_cps=True)
     assert_allclose(np.concatenate((raw_sim_meg[:][0], raw_sim_eeg[:][0])),
                     raw_sim_meeg[:][0], rtol=1e-7, atol=1e-20)
     del raw_sim_meg, raw_sim_eeg, raw_sim_meeg
 
     # check that different interpolations are similar given small movements
-    raw_sim_cos = simulate_raw(raw, stc, trans, src, sphere,
-                               head_pos=head_pos_sim,
-                               random_state=seed)
-    raw_sim_lin = simulate_raw(raw, stc, trans, src, sphere,
-                               head_pos=head_pos_sim, interp='linear',
-                               random_state=seed)
-    assert_allclose(raw_sim_cos[:][0], raw_sim_lin[:][0],
-                    rtol=1e-5, atol=1e-20)
-    del raw_sim_cos, raw_sim_lin
+    raw_sim = simulate_raw(raw, stc, trans, src, sphere, cov=None,
+                           head_pos=head_pos_sim, interp='linear',
+                           use_cps=True)
+    raw_sim_hann = simulate_raw(raw, stc, trans, src, sphere, cov=None,
+                                head_pos=head_pos_sim, interp='hann',
+                                use_cps=True)
+    assert_allclose(raw_sim[:][0], raw_sim_hann[:][0], rtol=1e-1, atol=1e-14)
+    del raw_sim, raw_sim_hann
 
     # Make impossible transform (translate up into helmet) and ensure failure
     head_pos_sim_err = deepcopy(head_pos_sim)
     head_pos_sim_err[1.][2, 3] -= 0.1  # z trans upward 10cm
     assert_raises(RuntimeError, simulate_raw, raw, stc, trans, src, sphere,
-                  ecg=False, blink=False, head_pos=head_pos_sim_err)
+                  ecg=False, blink=False, head_pos=head_pos_sim_err,
+                  use_cps=True)
     assert_raises(RuntimeError, simulate_raw, raw, stc, trans, src,
                   bem_fname, ecg=False, blink=False,
-                  head_pos=head_pos_sim_err)
+                  head_pos=head_pos_sim_err, use_cps=True)
     # other degenerate conditions
-    assert_raises(TypeError, simulate_raw, 'foo', stc, trans, src, sphere)
-    assert_raises(TypeError, simulate_raw, raw, 'foo', trans, src, sphere)
+    assert_raises(TypeError, simulate_raw, 'foo', stc, trans, src, sphere,
+                  use_cps=True)
+    assert_raises(TypeError, simulate_raw, raw, 'foo', trans, src, sphere,
+                  use_cps=True)
     assert_raises(ValueError, simulate_raw, raw, stc.copy().crop(0, 0),
-                  trans, src, sphere)
+                  trans, src, sphere, use_cps=True)
     stc_bad = stc.copy()
     stc_bad.tstep += 0.1
-    assert_raises(ValueError, simulate_raw, raw, stc_bad, trans, src, sphere)
+    assert_raises(ValueError, simulate_raw, raw, stc_bad, trans, src, sphere,
+                  use_cps=True)
     assert_raises(RuntimeError, simulate_raw, raw, stc, trans, src, sphere,
-                  chpi=True)  # no cHPI info
+                  chpi=True, use_cps=True)  # no cHPI info
     assert_raises(ValueError, simulate_raw, raw, stc, trans, src, sphere,
-                  interp='foo')
+                  interp='foo', use_cps=True)
     assert_raises(TypeError, simulate_raw, raw, stc, trans, src, sphere,
-                  head_pos=1.)
+                  head_pos=1., use_cps=True)
     assert_raises(RuntimeError, simulate_raw, raw, stc, trans, src, sphere,
-                  head_pos=pos_fname)  # ends up with t>t_end
+                  head_pos=pos_fname, use_cps=True)  # ends up with t>t_end
     head_pos_sim_err = deepcopy(head_pos_sim)
     head_pos_sim_err[-1.] = head_pos_sim_err[1.]  # negative time
     assert_raises(RuntimeError, simulate_raw, raw, stc, trans, src, sphere,
-                  head_pos=head_pos_sim_err)
+                  head_pos=head_pos_sim_err, use_cps=True)
     raw_bad = raw.copy()
     raw_bad.info['dig'] = None
     assert_raises(RuntimeError, simulate_raw, raw_bad, stc, trans, src, sphere,
-                  blink=True)
+                  blink=True, use_cps=True)
 
 
+ at pytest.mark.slowtest
 @testing.requires_testing_data
 def test_simulate_raw_bem():
     """Test simulation of raw data with BEM."""
     raw, src, stc, trans, sphere = _get_data()
-    src = setup_source_space('sample', None, 'oct1', subjects_dir=subjects_dir)
+    src = setup_source_space('sample', 'oct1', subjects_dir=subjects_dir)
+    for s in src:
+        s['nuse'] = 3
+        s['vertno'] = src[1]['vertno'][:3]
+        s['inuse'].fill(0)
+        s['inuse'][s['vertno']] = 1
     # use different / more complete STC here
     vertices = [s['vertno'] for s in src]
     stc = SourceEstimate(np.eye(sum(len(v) for v in vertices)), vertices,
                          0, 1. / raw.info['sfreq'])
-    raw_sim_sph = simulate_raw(raw, stc, trans, src, sphere, cov=None)
+    raw_sim_sph = simulate_raw(raw, stc, trans, src, sphere, cov=None,
+                               use_cps=True)
     raw_sim_bem = simulate_raw(raw, stc, trans, src, bem_fname, cov=None,
-                               n_jobs=2)
+                               n_jobs=2, use_cps=True)
     # some components (especially radial) might not match that well,
     # so just make sure that most components have high correlation
     assert_array_equal(raw_sim_sph.ch_names, raw_sim_bem.ch_names)
@@ -213,7 +231,8 @@ def test_simulate_raw_bem():
     n_ch = len(picks)
     corr = np.corrcoef(raw_sim_sph[picks][0], raw_sim_bem[picks][0])
     assert_array_equal(corr.shape, (2 * n_ch, 2 * n_ch))
-    assert_true(np.median(np.diag(corr[:n_ch, -n_ch:])) > 0.65)
+    med_corr = np.median(np.diag(corr[:n_ch, -n_ch:]))
+    assert_true(med_corr > 0.65, msg=med_corr)
     # do some round-trip localization
     for s in src:
         transform_surface_to(s, 'head', trans)
@@ -222,45 +241,86 @@ def test_simulate_raw_bem():
     cov = make_ad_hoc_cov(raw.info)
     # The tolerance for the BEM is surprisingly high (28) but I get the same
     # result when using MNE-C and Xfit, even when using a proper 5120 BEM :(
-    for use_raw, bem, tol in ((raw_sim_sph, sphere, 1),
-                              (raw_sim_bem, bem_fname, 28)):
+    for use_raw, bem, tol in ((raw_sim_sph, sphere, 2),
+                              (raw_sim_bem, bem_fname, 31)):
         events = find_events(use_raw, 'STI 014')
-        assert_equal(len(locs), 12)  # oct1 count
-        evoked = Epochs(use_raw, events, 1, 0, tmax, baseline=None,
-                        add_eeg_ref=False).average()
+        assert_equal(len(locs), 6)
+        evoked = Epochs(use_raw, events, 1, 0, tmax, baseline=None).average()
         assert_equal(len(evoked.times), len(locs))
         fits = fit_dipole(evoked, cov, bem, trans, min_dist=1.)[0].pos
         diffs = np.sqrt(np.sum((locs - fits) ** 2, axis=-1)) * 1000
-        assert_true(np.median(diffs) < tol)
+        med_diff = np.median(diffs)
+        assert_true(med_diff < tol, msg='%s: %s' % (bem, med_diff))
+
+
+ at testing.requires_testing_data
+def test_simulate_round_trip():
+    """Test simulate_raw round trip calculations."""
+    # Check a diagonal round-trip
+    raw, src, stc, trans, sphere = _get_data()
+    raw.pick_types(meg=True, stim=True)
+    bem = read_bem_solution(bem_1_fname)
+    old_bem = bem.copy()
+    old_src = src.copy()
+    old_trans = trans.copy()
+    fwd = make_forward_solution(raw.info, trans, src, bem)
+    # no omissions
+    assert (sum(len(s['vertno']) for s in src) ==
+            sum(len(s['vertno']) for s in fwd['src']) ==
+            36)
+    # make sure things were not modified
+    assert (old_bem['surfs'][0]['coord_frame'] ==
+            bem['surfs'][0]['coord_frame'])
+    assert trans == old_trans
+    _compare_source_spaces(src, old_src)
+    data = np.eye(fwd['nsource'])
+    raw.crop(0, (len(data) - 1) / raw.info['sfreq'])
+    stc = SourceEstimate(data, [s['vertno'] for s in fwd['src']],
+                         0, 1. / raw.info['sfreq'])
+    for use_cps in (False, True):
+        this_raw = simulate_raw(raw, stc, trans, src, bem, cov=None,
+                                use_cps=use_cps)
+        this_raw.pick_types(meg=True, eeg=True)
+        assert (old_bem['surfs'][0]['coord_frame'] ==
+                bem['surfs'][0]['coord_frame'])
+        assert trans == old_trans
+        _compare_source_spaces(src, old_src)
+        this_fwd = convert_forward_solution(fwd, force_fixed=True,
+                                            use_cps=use_cps)
+        assert_allclose(this_raw[:][0], this_fwd['sol']['data'],
+                        atol=1e-12, rtol=1e-6)
 
 
- at slow_test
- at requires_version('numpy', '1.7')
- at requires_version('scipy', '0.12')
+ at pytest.mark.slowtest
 @testing.requires_testing_data
 def test_simulate_raw_chpi():
     """Test simulation of raw data with cHPI."""
-    raw = read_raw_fif(raw_chpi_fname, allow_maxshield='yes',
-                       add_eeg_ref=False)
+    raw = read_raw_fif(raw_chpi_fname, allow_maxshield='yes')
+    picks = np.arange(len(raw.ch_names))
+    picks = np.setdiff1d(picks, pick_types(raw.info, meg=True, eeg=True)[::4])
+    raw.load_data().pick_channels([raw.ch_names[pick] for pick in picks])
+    raw.info.normalize_proj()
     sphere = make_sphere_model('auto', 'auto', raw.info)
     # make sparse spherical source space
     sphere_vol = tuple(sphere['r0'] * 1000.) + (sphere.radius * 1000.,)
     src = setup_volume_source_space('sample', sphere=sphere_vol, pos=70.)
     stc = _make_stc(raw, src)
     # simulate data with cHPI on
-    raw_sim = simulate_raw(raw, stc, None, src, sphere, cov=None, chpi=False)
+    raw_sim = simulate_raw(raw, stc, None, src, sphere, cov=None, chpi=False,
+                           interp='zero', use_cps=True)
     # need to trim extra samples off this one
     raw_chpi = simulate_raw(raw, stc, None, src, sphere, cov=None, chpi=True,
-                            head_pos=pos_fname)
+                            head_pos=pos_fname, interp='zero',
+                            use_cps=True)
     # test cHPI indication
-    hpi_freqs, _, hpi_pick, hpi_ons = _get_hpi_info(raw.info)[:4]
+    hpi_freqs, hpi_pick, hpi_ons = _get_hpi_info(raw.info)
     assert_allclose(raw_sim[hpi_pick][0], 0.)
     assert_allclose(raw_chpi[hpi_pick][0], hpi_ons.sum())
     # test that the cHPI signals make some reasonable values
     picks_meg = pick_types(raw.info, meg=True, eeg=False)
     picks_eeg = pick_types(raw.info, meg=False, eeg=True)
 
-    for picks in [picks_meg, picks_eeg]:
+    for picks in [picks_meg[:3], picks_eeg[:3]]:
         psd_sim, freqs_sim = psd_welch(raw_sim, picks=picks)
         psd_chpi, freqs_chpi = psd_welch(raw_chpi, picks=picks)
 
@@ -274,11 +334,9 @@ def test_simulate_raw_chpi():
             assert_allclose(psd_sim, psd_chpi, atol=1e-20)
 
     # test localization based on cHPI information
-    quats_sim = _calculate_chpi_positions(raw_chpi)
-    trans_sim, rot_sim, t_sim = head_pos_to_trans_rot_t(quats_sim)
-    trans, rot, t = head_pos_to_trans_rot_t(read_head_pos(pos_fname))
-    t -= raw.first_samp / raw.info['sfreq']
-    _compare_positions((trans, rot, t), (trans_sim, rot_sim, t_sim),
-                       max_dist=0.005)
+    quats_sim = _calculate_chpi_positions(raw_chpi, t_step_min=10.)
+    quats = read_head_pos(pos_fname)
+    _assert_quats(quats, quats_sim, dist_tol=5e-3, angle_tol=3.5)
+
 
 run_tests_if_main()
diff --git a/mne/simulation/tests/test_source.py b/mne/simulation/tests/test_source.py
index ddd8973..e09bb6d 100644
--- a/mne/simulation/tests/test_source.py
+++ b/mne/simulation/tests/test_source.py
@@ -5,7 +5,8 @@ from numpy.testing import assert_array_almost_equal, assert_array_equal
 from nose.tools import assert_true, assert_raises, assert_equal
 
 from mne.datasets import testing
-from mne import read_label, read_forward_solution, pick_types_forward
+from mne import (read_label, read_forward_solution, pick_types_forward,
+                 convert_forward_solution)
 from mne.label import Label
 from mne.simulation.source import simulate_stc, simulate_sparse_stc
 from mne.utils import run_tests_if_main
@@ -21,7 +22,8 @@ subjects_dir = op.join(data_path, 'subjects')
 
 
 def read_forward_solution_meg(*args, **kwargs):
-    fwd = read_forward_solution(*args, **kwargs)
+    fwd = read_forward_solution(*args)
+    fwd = convert_forward_solution(fwd, **kwargs)
     fwd = pick_types_forward(fwd, meg=True, eeg=False)
     return fwd
 
@@ -29,7 +31,7 @@ def read_forward_solution_meg(*args, **kwargs):
 @testing.requires_testing_data
 def test_simulate_stc():
     """ Test generation of source estimate """
-    fwd = read_forward_solution_meg(fname_fwd, force_fixed=True)
+    fwd = read_forward_solution_meg(fname_fwd, force_fixed=True, use_cps=True)
     labels = [read_label(op.join(data_path, 'MEG', 'sample', 'labels',
                          '%s.label' % label)) for label in label_names]
     mylabels = []
@@ -99,7 +101,7 @@ def test_simulate_stc():
 @testing.requires_testing_data
 def test_simulate_sparse_stc():
     """ Test generation of sparse source estimate """
-    fwd = read_forward_solution_meg(fname_fwd, force_fixed=True)
+    fwd = read_forward_solution_meg(fname_fwd, force_fixed=True, use_cps=True)
     labels = [read_label(op.join(data_path, 'MEG', 'sample', 'labels',
                          '%s.label' % label)) for label in label_names]
 
@@ -147,7 +149,7 @@ def test_simulate_sparse_stc():
 @testing.requires_testing_data
 def test_generate_stc_single_hemi():
     """ Test generation of source estimate, single hemi """
-    fwd = read_forward_solution_meg(fname_fwd, force_fixed=True)
+    fwd = read_forward_solution_meg(fname_fwd, force_fixed=True, use_cps=True)
     labels_single_hemi = [read_label(op.join(data_path, 'MEG', 'sample',
                                              'labels', '%s.label' % label))
                           for label in label_names_single_hemi]
@@ -208,7 +210,7 @@ def test_generate_stc_single_hemi():
 @testing.requires_testing_data
 def test_simulate_sparse_stc_single_hemi():
     """ Test generation of sparse source estimate """
-    fwd = read_forward_solution_meg(fname_fwd, force_fixed=True)
+    fwd = read_forward_solution_meg(fname_fwd, force_fixed=True, use_cps=True)
     n_times = 10
     tmin = 0
     tstep = 1e-3
diff --git a/mne/source_estimate.py b/mne/source_estimate.py
index ae75df9..b774239 100644
--- a/mne/source_estimate.py
+++ b/mne/source_estimate.py
@@ -23,7 +23,7 @@ from .source_space import (_ensure_src, _get_morph_src_reordering,
                            _ensure_src_subject, SourceSpaces)
 from .utils import (get_subjects_dir, _check_subject, logger, verbose,
                     _time_mask, warn as warn_, copy_function_doc_to_method_doc)
-from .viz import plot_source_estimates
+from .viz import plot_source_estimates, plot_vector_source_estimates
 from .io.base import ToDataFrameMixin, TimeMixin
 
 from .externals.six import string_types
@@ -32,48 +32,53 @@ from .externals.h5io import read_hdf5, write_hdf5
 
 
 def _read_stc(filename):
-    """ Aux Function
-    """
-    fid = open(filename, 'rb')
+    """Aux Function."""
+    with open(filename, 'rb') as fid:
+        buf = fid.read()
 
     stc = dict()
-
-    fid.seek(0, 2)  # go to end of file
-    file_length = fid.tell()
-    fid.seek(0, 0)  # go to beginning of file
+    offset = 0
+    num_bytes = 4
 
     # read tmin in ms
-    stc['tmin'] = float(np.fromfile(fid, dtype=">f4", count=1))
+    stc['tmin'] = float(np.frombuffer(buf, dtype=">f4", count=1,
+                                      offset=offset))
     stc['tmin'] /= 1000.0
+    offset += num_bytes
 
     # read sampling rate in ms
-    stc['tstep'] = float(np.fromfile(fid, dtype=">f4", count=1))
+    stc['tstep'] = float(np.frombuffer(buf, dtype=">f4", count=1,
+                                       offset=offset))
     stc['tstep'] /= 1000.0
+    offset += num_bytes
 
     # read number of vertices/sources
-    vertices_n = int(np.fromfile(fid, dtype=">u4", count=1))
+    vertices_n = int(np.frombuffer(buf, dtype=">u4", count=1, offset=offset))
+    offset += num_bytes
 
     # read the source vector
-    stc['vertices'] = np.fromfile(fid, dtype=">u4", count=vertices_n)
+    stc['vertices'] = np.frombuffer(buf, dtype=">u4", count=vertices_n,
+                                    offset=offset)
+    offset += num_bytes * vertices_n
 
     # read the number of timepts
-    data_n = int(np.fromfile(fid, dtype=">u4", count=1))
+    data_n = int(np.frombuffer(buf, dtype=">u4", count=1, offset=offset))
+    offset += num_bytes
 
     if (vertices_n and  # vertices_n can be 0 (empty stc)
-            ((file_length / 4 - 4 - vertices_n) % (data_n * vertices_n)) != 0):
+            ((len(buf) // 4 - 4 - vertices_n) % (data_n * vertices_n)) != 0):
         raise ValueError('incorrect stc file size')
 
     # read the data matrix
-    stc['data'] = np.fromfile(fid, dtype=">f4", count=vertices_n * data_n)
+    stc['data'] = np.frombuffer(buf, dtype=">f4", count=vertices_n * data_n,
+                                offset=offset)
     stc['data'] = stc['data'].reshape([data_n, vertices_n]).T
 
-    # close the file
-    fid.close()
     return stc
 
 
 def _write_stc(filename, tmin, tstep, vertices, data):
-    """Write an STC file
+    """Write an STC file.
 
     Parameters
     ----------
@@ -111,8 +116,7 @@ def _write_stc(filename, tmin, tstep, vertices, data):
 
 
 def _read_3(fid):
-    """ Read 3 byte integer from file
-    """
+    """Read 3 byte integer from file."""
     data = np.fromfile(fid, dtype=np.uint8, count=3).astype(np.int32)
 
     out = np.left_shift(data[0], 16) + np.left_shift(data[1], 8) + data[2]
@@ -121,7 +125,7 @@ def _read_3(fid):
 
 
 def _read_w(filename):
-    """Read a w file and return as dict
+    """Read a w file.
 
     w files contain activations or source reconstructions for a single time
     point.
@@ -138,7 +142,6 @@ def _read_w(filename):
            vertices       vertex indices (0 based)
            data           The data matrix (nvert long)
     """
-
     with open(filename, 'rb', buffering=0) as fid:  # buffering=0 for np bug
         # skip first 2 bytes
         fid.read(2)
@@ -162,20 +165,16 @@ def _read_w(filename):
 
 
 def _write_3(fid, val):
-    """ Write 3 byte integer to file
-    """
-
+    """Write 3 byte integer to file."""
     f_bytes = np.zeros((3), dtype=np.uint8)
-
     f_bytes[0] = (val >> 16) & 255
     f_bytes[1] = (val >> 8) & 255
     f_bytes[2] = val & 255
-
     fid.write(f_bytes.tostring())
 
 
 def _write_w(filename, vertices, data):
-    """Read a w file
+    """Write a w file.
 
     w files contain activations or source reconstructions for a single time
     point.
@@ -189,7 +188,6 @@ def _write_w(filename, vertices, data):
     data: 1D array
         The data array (nvert).
     """
-
     assert(len(vertices) == len(data))
 
     fid = open(filename, 'wb')
@@ -212,7 +210,7 @@ def _write_w(filename, vertices, data):
 
 
 def read_source_estimate(fname, subject=None):
-    """Read a soure estimate object
+    """Read a soure estimate object.
 
     Parameters
     ----------
@@ -227,7 +225,7 @@ def read_source_estimate(fname, subject=None):
 
     Returns
     -------
-    stc : SourceEstimate | VolSourceEstimate
+    stc : SourceEstimate | VectorSourceEstimate | VolSourceEstimate
         The soure estimate object loaded from file.
 
     Notes
@@ -239,6 +237,7 @@ def read_source_estimate(fname, subject=None):
        '*-rh.stc') or only specify the asterisk part in these patterns. In any
        case, the function expects files for both hemisphere with names
        following this pattern.
+     - for vector surface source estimates, only HDF5 files are supported.
      - for single time point .w files, ``fname`` should follow the same
        pattern as for surface estimates, except that files are named
        '*-lh.w' and '*-rh.w'.
@@ -269,9 +268,9 @@ def read_source_estimate(fname, subject=None):
                        "hemisphere tag ('...-lh.w' or '...-rh.w')"
                        % fname)
                 raise IOError(err)
-        elif fname.endswith('-stc.h5'):
+        elif fname.endswith('.h5'):
             ftype = 'h5'
-            fname = fname[:-7]
+            fname = fname[:-3]
         else:
             raise RuntimeError('Unknown extension for file %s' % fname_arg)
 
@@ -280,13 +279,15 @@ def read_source_estimate(fname, subject=None):
                      for f in [fname + '-rh.stc', fname + '-lh.stc']]
         w_exist = [op.exists(f)
                    for f in [fname + '-rh.w', fname + '-lh.w']]
-        h5_exist = op.exists(fname + '-stc.h5')
         if all(stc_exist) and (ftype is not 'w'):
             ftype = 'surface'
         elif all(w_exist):
             ftype = 'w'
-        elif h5_exist:
+        elif op.exists(fname + '.h5'):
             ftype = 'h5'
+        elif op.exists(fname + '-stc.h5'):
+            ftype = 'h5'
+            fname += '-stc'
         elif any(stc_exist) or any(w_exist):
             raise IOError("Hemisphere missing for %r" % fname_arg)
         else:
@@ -322,7 +323,7 @@ def read_source_estimate(fname, subject=None):
         kwargs['tmin'] = 0.0
         kwargs['tstep'] = 1.0
     elif ftype == 'h5':
-        kwargs = read_hdf5(fname + '-stc.h5', title='mnepython')
+        kwargs = read_hdf5(fname + '.h5', title='mnepython')
 
     if ftype != 'volume':
         # Make sure the vertices are ordered
@@ -343,20 +344,40 @@ def read_source_estimate(fname, subject=None):
 
     if ftype == 'volume':
         stc = VolSourceEstimate(**kwargs)
+    elif ftype == 'h5' and kwargs['data'].ndim == 3:
+        stc = VectorSourceEstimate(**kwargs)
     else:
         stc = SourceEstimate(**kwargs)
 
     return stc
 
 
-def _make_stc(data, vertices, tmin=None, tstep=None, subject=None):
-    """Helper function to generate a surface, volume or mixed source estimate
-    """
-
+def _make_stc(data, vertices, tmin=None, tstep=None, subject=None,
+              vector=False, source_nn=None):
+    """Generate a surface, vector-surface, volume or mixed source estimate."""
     if isinstance(vertices, list) and len(vertices) == 2:
         # make a surface source estimate
-        stc = SourceEstimate(data, vertices=vertices, tmin=tmin, tstep=tstep,
-                             subject=subject)
+        n_vertices = len(vertices[0]) + len(vertices[1])
+        if vector:
+            if source_nn is None:
+                raise RuntimeError('No source vectors supplied.')
+
+            # Rotate data to absolute XYZ coordinates
+            data_rot = np.zeros((n_vertices, 3, data.shape[1]))
+            if data.shape[0] == 3 * n_vertices:
+                source_nn = source_nn.reshape(n_vertices, 3, 3)
+                data = data.reshape(n_vertices, 3, -1)
+            else:
+                raise RuntimeError('Shape of data array does not match the '
+                                   'number of vertices.')
+            for i, d, n in zip(range(data.shape[0]), data, source_nn):
+                data_rot[i] = np.dot(n.T, d)
+            data = data_rot
+            stc = VectorSourceEstimate(data, vertices=vertices, tmin=tmin,
+                                       tstep=tstep, subject=subject)
+        else:
+            stc = SourceEstimate(data, vertices=vertices, tmin=tmin,
+                                 tstep=tstep, subject=subject)
     elif isinstance(vertices, np.ndarray) or isinstance(vertices, list)\
             and len(vertices) == 1:
         stc = VolSourceEstimate(data, vertices=vertices, tmin=tmin,
@@ -372,22 +393,24 @@ def _make_stc(data, vertices, tmin=None, tstep=None, subject=None):
 
 
 def _verify_source_estimate_compat(a, b):
-    """Make sure two SourceEstimates are compatible for arith. operations"""
+    """Make sure two SourceEstimates are compatible for arith. operations."""
     compat = False
+    if type(a) != type(b):
+        raise ValueError('Cannot combine %s and %s.' % (type(a), type(b)))
     if len(a.vertices) == len(b.vertices):
         if all(np.array_equal(av, vv)
                for av, vv in zip(a.vertices, b.vertices)):
             compat = True
     if not compat:
-        raise ValueError('Cannot combine SourceEstimates that do not have the '
-                         'same vertices. Consider using stc.expand().')
+        raise ValueError('Cannot combine source estimates that do not have '
+                         'the same vertices. Consider using stc.expand().')
     if a.subject != b.subject:
         raise ValueError('source estimates do not have the same subject '
                          'names, %r and %r' % (a.subject, b.subject))
 
 
 class _BaseSourceEstimate(ToDataFrameMixin, TimeMixin):
-    """Abstract base class for source estimates
+    """Abstract base class for source estimates.
 
     Parameters
     ----------
@@ -406,7 +429,8 @@ class _BaseSourceEstimate(ToDataFrameMixin, TimeMixin):
         The subject name. While not necessary, it is safer to set the
         subject parameter to avoid analysis errors.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Attributes
     ----------
@@ -422,9 +446,10 @@ class _BaseSourceEstimate(ToDataFrameMixin, TimeMixin):
     shape : tuple
         The shape of the data. A tuple of int (n_dipoles, n_times).
     """
+
     @verbose
     def __init__(self, data, vertices=None, tmin=None, tstep=None,
-                 subject=None, verbose=None):
+                 subject=None, verbose=None):  # noqa: D102
         kernel, sens_data = None, None
         if isinstance(data, tuple):
             if len(data) != 2:
@@ -436,10 +461,7 @@ class _BaseSourceEstimate(ToDataFrameMixin, TimeMixin):
                                  'dimensions')
 
         if isinstance(vertices, list):
-            if not all(isinstance(v, np.ndarray) for v in vertices):
-                raise ValueError('Vertices, if a list, must contain numpy '
-                                 'arrays')
-
+            vertices = [np.asarray(v) for v in vertices]
             if any(np.any(np.diff(v.astype(int)) <= 0) for v in vertices):
                 raise ValueError('Vertices must be ordered in increasing '
                                  'order.')
@@ -459,25 +481,24 @@ class _BaseSourceEstimate(ToDataFrameMixin, TimeMixin):
                              'must match' % (n_src, data.shape[0]))
 
         self._data = data
-        self.tmin = tmin
-        self.tstep = tstep
+        self._tmin = tmin
+        self._tstep = tstep
         self.vertices = vertices
         self.verbose = verbose
         self._kernel = kernel
         self._sens_data = sens_data
         self._kernel_removed = False
-        self.times = None
+        self._times = None
         self._update_times()
         self.subject = _check_subject(None, subject, False)
 
     @property
     def sfreq(self):
-        """Sample rate of the data"""
+        """Sample rate of the data."""
         return 1. / self.tstep
 
     def _remove_kernel_sens_data_(self):
-        """Remove kernel and sensor space data and compute self._data
-        """
+        """Remove kernel and sensor space data and compute self._data."""
         if self._kernel is not None or self._sens_data is not None:
             self._kernel_removed = True
             self._data = np.dot(self._kernel, self._sens_data)
@@ -485,7 +506,7 @@ class _BaseSourceEstimate(ToDataFrameMixin, TimeMixin):
             self._sens_data = None
 
     def crop(self, tmin=None, tmax=None):
-        """Restrict SourceEstimate to a time interval
+        """Restrict SourceEstimate to a time interval.
 
         Parameters
         ----------
@@ -497,17 +518,16 @@ class _BaseSourceEstimate(ToDataFrameMixin, TimeMixin):
         mask = _time_mask(self.times, tmin, tmax, sfreq=self.sfreq)
         self.tmin = self.times[np.where(mask)[0][0]]
         if self._kernel is not None and self._sens_data is not None:
-            self._sens_data = self._sens_data[:, mask]
+            self._sens_data = self._sens_data[..., mask]
         else:
-            self._data = self._data[:, mask]
+            self.data = self.data[..., mask]
 
-        self._update_times()
         return self  # return self for chaining methods
 
     @verbose
     def resample(self, sfreq, npad='auto', window='boxcar', n_jobs=1,
                  verbose=None):
-        """Resample data
+        """Resample data.
 
         Parameters
         ----------
@@ -522,8 +542,9 @@ class _BaseSourceEstimate(ToDataFrameMixin, TimeMixin):
         n_jobs : int
             Number of jobs to run in parallel.
         verbose : bool, str, int, or None
-            If not None, override default verbose level (see mne.verbose).
-            Defaults to self.verbose.
+            If not None, override default verbose level (see
+            :func:`mne.verbose` and :ref:`Logging documentation <tut_logging>`
+            for more). Defaults to self.verbose.
 
         Notes
         -----
@@ -537,44 +558,102 @@ class _BaseSourceEstimate(ToDataFrameMixin, TimeMixin):
         self._remove_kernel_sens_data_()
 
         o_sfreq = 1.0 / self.tstep
-        self._data = resample(self._data, sfreq, o_sfreq, npad, n_jobs=n_jobs)
+        self.data = resample(self.data, sfreq, o_sfreq, npad, n_jobs=n_jobs)
 
         # adjust indirectly affected variables
         self.tstep = 1.0 / sfreq
-        self._update_times()
+        return self
 
     @property
     def data(self):
-        """Numpy array of source estimate data"""
+        """Numpy array of source estimate data."""
         if self._data is None:
             # compute the solution the first time the data is accessed and
             # remove the kernel and sensor data
             self._remove_kernel_sens_data_()
         return self._data
 
+    @data.setter
+    def data(self, value):
+        value = np.asarray(value)
+        if self._data is not None and value.ndim != self._data.ndim:
+            raise ValueError('Data array should have %d dimensions.' %
+                             self._data.ndim)
+
+        # vertices can be a single number, so cast to ndarray
+        if isinstance(self.vertices, list):
+            n_verts = sum([len(v) for v in self.vertices])
+        elif isinstance(self.vertices, np.ndarray):
+            n_verts = len(self.vertices)
+        else:
+            raise ValueError('Vertices must be a list or numpy array')
+
+        if value.shape[0] != n_verts:
+            raise ValueError('The first dimension of the data array must '
+                             'match the number of vertices (%d != %d)' %
+                             (value.shape[0], n_verts))
+
+        self._data = value
+        self._update_times()
+
     @property
     def shape(self):
-        """Shape of the data"""
+        """Shape of the data."""
         if self._data is not None:
             return self._data.shape
         return (self._kernel.shape[0], self._sens_data.shape[1])
 
+    @property
+    def tmin(self):
+        """The first timestamp."""
+        return self._tmin
+
+    @tmin.setter
+    def tmin(self, value):
+        self._tmin = float(value)
+        self._update_times()
+
+    @property
+    def tstep(self):
+        """The change in time between two consecutive samples (1 / sfreq)."""
+        return self._tstep
+
+    @tstep.setter
+    def tstep(self, value):
+        if value <= 0:
+            raise ValueError('.tstep must be greater than 0.')
+        self._tstep = float(value)
+        self._update_times()
+
+    @property
+    def times(self):
+        """A timestamp for each sample."""
+        return self._times
+
+    @times.setter
+    def times(self, value):
+        raise ValueError('You cannot write to the .times attribute directly. '
+                         'This property automatically updates whenever '
+                         '.tmin, .tstep or .data changes.')
+
     def _update_times(self):
-        """Update the times attribute after changing tmin, tmax, or tstep"""
-        self.times = self.tmin + (self.tstep * np.arange(self.shape[1]))
+        """Update the times attribute after changing tmin, tmax, or tstep."""
+        self._times = self.tmin + (self.tstep * np.arange(self.shape[-1]))
+        self._times.flags.writeable = False
 
     def __add__(self, a):
-        stc = copy.deepcopy(self)
+        """Add source estimates."""
+        stc = self.copy()
         stc += a
         return stc
 
-    def __iadd__(self, a):
+    def __iadd__(self, a):  # noqa: D105
         self._remove_kernel_sens_data_()
         if isinstance(a, _BaseSourceEstimate):
             _verify_source_estimate_compat(self, a)
-            self._data += a.data
+            self.data += a.data
         else:
-            self._data += a
+            self.data += a
         return self
 
     def mean(self):
@@ -582,99 +661,117 @@ class _BaseSourceEstimate(ToDataFrameMixin, TimeMixin):
 
         Returns
         -------
-        stc : instance of SourceEstimate
+        stc : SourceEstimate | VectorSourceEstimate
             The modified stc (method operates inplace).
         """
         data = self.data
-        tmax = self.tmin + self.tstep * data.shape[1]
+        tmax = self.tmin + self.tstep * data.shape[-1]
         tmin = (self.tmin + tmax) / 2.
         tstep = tmax - self.tmin
-        mean_stc = SourceEstimate(self.data.mean(axis=1)[:, np.newaxis],
+        mean_stc = self.__class__(self.data.mean(axis=-1, keepdims=True),
                                   vertices=self.vertices, tmin=tmin,
                                   tstep=tstep, subject=self.subject)
         return mean_stc
 
     def __sub__(self, a):
-        stc = copy.deepcopy(self)
+        """Subtract source estimates."""
+        stc = self.copy()
         stc -= a
         return stc
 
-    def __isub__(self, a):
+    def __isub__(self, a):  # noqa: D105
         self._remove_kernel_sens_data_()
         if isinstance(a, _BaseSourceEstimate):
             _verify_source_estimate_compat(self, a)
-            self._data -= a.data
+            self.data -= a.data
         else:
-            self._data -= a
+            self.data -= a
         return self
 
-    def __truediv__(self, a):
+    def __truediv__(self, a):  # noqa: D105
         return self.__div__(a)
 
-    def __div__(self, a):
-        stc = copy.deepcopy(self)
+    def __div__(self, a):  # noqa: D105
+        """Divide source estimates."""
+        stc = self.copy()
         stc /= a
         return stc
 
-    def __itruediv__(self, a):
+    def __itruediv__(self, a):  # noqa: D105
         return self.__idiv__(a)
 
-    def __idiv__(self, a):
+    def __idiv__(self, a):  # noqa: D105
         self._remove_kernel_sens_data_()
         if isinstance(a, _BaseSourceEstimate):
             _verify_source_estimate_compat(self, a)
-            self._data /= a.data
+            self.data /= a.data
         else:
-            self._data /= a
+            self.data /= a
         return self
 
     def __mul__(self, a):
-        stc = copy.deepcopy(self)
+        """Multiply source estimates."""
+        stc = self.copy()
         stc *= a
         return stc
 
-    def __imul__(self, a):
+    def __imul__(self, a):  # noqa: D105
         self._remove_kernel_sens_data_()
         if isinstance(a, _BaseSourceEstimate):
             _verify_source_estimate_compat(self, a)
-            self._data *= a.data
+            self.data *= a.data
         else:
-            self._data *= a
+            self.data *= a
         return self
 
-    def __pow__(self, a):
-        stc = copy.deepcopy(self)
+    def __pow__(self, a):  # noqa: D105
+        stc = self.copy()
         stc **= a
         return stc
 
-    def __ipow__(self, a):
+    def __ipow__(self, a):  # noqa: D105
         self._remove_kernel_sens_data_()
-        self._data **= a
+        self.data **= a
         return self
 
-    def __radd__(self, a):
+    def __radd__(self, a):  # noqa: D105
         return self + a
 
-    def __rsub__(self, a):
+    def __rsub__(self, a):  # noqa: D105
         return self - a
 
-    def __rmul__(self, a):
+    def __rmul__(self, a):  # noqa: D105
         return self * a
 
-    def __rdiv__(self, a):
+    def __rdiv__(self, a):  # noqa: D105
         return self / a
 
-    def __neg__(self):
-        stc = copy.deepcopy(self)
+    def __neg__(self):  # noqa: D105
+        """Negate the source estimate."""
+        stc = self.copy()
         stc._remove_kernel_sens_data_()
-        stc._data *= -1
+        stc.data *= -1
         return stc
 
-    def __pos__(self):
+    def __pos__(self):  # noqa: D105
         return self
 
+    def __abs__(self):
+        """Compute the absolute value of the data.
+
+        Returns
+        -------
+        stc : instance of _BaseSourceEstimate
+            A version of the source estimate, where the data attribute is set
+            to abs(self.data).
+        """
+        stc = self.copy()
+        stc._remove_kernel_sens_data_()
+        stc._data = abs(stc._data)
+        return stc
+
     def sqrt(self):
-        """Take the square root
+        """Take the square root.
 
         Returns
         -------
@@ -684,11 +781,11 @@ class _BaseSourceEstimate(ToDataFrameMixin, TimeMixin):
         return self ** (0.5)
 
     def copy(self):
-        """Return copy of SourceEstimate instance"""
+        """Return copy of source estimate instance."""
         return copy.deepcopy(self)
 
     def bin(self, width, tstart=None, tstop=None, func=np.mean):
-        """Returns a SourceEstimate object with data summarized over time bins
+        """Return a source estimate object with data summarized over time bins.
 
         Time bins of ``width`` seconds. This method is intended for
         visualization only. No filter is applied to the data before binning,
@@ -711,8 +808,8 @@ class _BaseSourceEstimate(ToDataFrameMixin, TimeMixin):
 
         Returns
         -------
-        stc : instance of SourceEstimate
-            The binned SourceEstimate.
+        stc : SourceEstimate | VectorSourceEstimate
+            The binned source estimate.
         """
         if tstart is None:
             tstart = self.tmin
@@ -720,22 +817,23 @@ class _BaseSourceEstimate(ToDataFrameMixin, TimeMixin):
             tstop = self.times[-1]
 
         times = np.arange(tstart, tstop + self.tstep, width)
-        nv, _ = self.shape
         nt = len(times) - 1
-        data = np.empty((nv, nt), dtype=self.data.dtype)
+        data = np.empty(self.shape[:-1] + (nt,), dtype=self.data.dtype)
         for i in range(nt):
             idx = (self.times >= times[i]) & (self.times < times[i + 1])
-            data[:, i] = func(self.data[:, idx], axis=1)
+            data[..., i] = func(self.data[..., idx], axis=-1)
 
         tmin = times[0] + width / 2.
-        stc = _make_stc(data, vertices=self.vertices,
-                        tmin=tmin, tstep=width, subject=self.subject)
+        stc = self.copy()
+        stc._data = data
+        stc.tmin = tmin
+        stc.tstep = width
         return stc
 
     def transform_data(self, func, idx=None, tmin_idx=None, tmax_idx=None):
-        """Get data after a linear (time) transform has been applied
+        """Get data after a linear (time) transform has been applied.
 
-        The transorm is applied to each source time course independently.
+        The transform is applied to each source time course independently.
 
 
         Parameters
@@ -770,7 +868,6 @@ class _BaseSourceEstimate(ToDataFrameMixin, TimeMixin):
         Inverse methods, e.g., "apply_inverse_epochs", or "lcmv_epochs" do
         this automatically (if possible).
         """
-
         if idx is None:
             # use all time courses by default
             idx = slice(None, None)
@@ -781,7 +878,7 @@ class _BaseSourceEstimate(ToDataFrameMixin, TimeMixin):
                       'attribute before calling this method.')
 
             # transform source space data directly
-            data_t = func(self.data[idx, tmin_idx:tmax_idx])
+            data_t = func(self.data[idx, ..., tmin_idx:tmax_idx])
 
             if isinstance(data_t, tuple):
                 # use only first return value
@@ -810,7 +907,7 @@ class _BaseSourceEstimate(ToDataFrameMixin, TimeMixin):
         return data_t
 
     def transform(self, func, idx=None, tmin=None, tmax=None, copy=False):
-        """Apply linear transform
+        """Apply linear transform.
 
         The transform is applied to each source time course independently.
 
@@ -843,7 +940,7 @@ class _BaseSourceEstimate(ToDataFrameMixin, TimeMixin):
 
         Returns
         -------
-        stcs : instance of SourceEstimate | list
+        stcs : SourceEstimate | VectorSourceEstimate | list
             The transformed stc or, in the case of transforms which yield
             N-dimensional output (where N > 2), a list of stcs. For a list,
             copy must be True.
@@ -856,7 +953,6 @@ class _BaseSourceEstimate(ToDataFrameMixin, TimeMixin):
         Inverse methods, e.g., "apply_inverse_epochs", or "lcmv_epochs" do
         this automatically (if possible).
         """
-
         # min and max data indices to include
         times = 1000. * self.times
         t_idx = np.where(_time_mask(times, tmin, tmax, sfreq=self.sfreq))[0]
@@ -868,7 +964,8 @@ class _BaseSourceEstimate(ToDataFrameMixin, TimeMixin):
         if tmax is None:
             tmax_idx = None
         else:
-            tmax_idx = t_idx[-1]
+            # +1, because upper boundary needs to include the last sample
+            tmax_idx = t_idx[-1] + 1
 
         data_t = self.transform_data(func, idx=idx, tmin_idx=tmin_idx,
                                      tmax_idx=tmax_idx)
@@ -885,13 +982,8 @@ class _BaseSourceEstimate(ToDataFrameMixin, TimeMixin):
         verts = [verts_lh, verts_rh]
 
         tmin_idx = 0 if tmin_idx is None else tmin_idx
-        tmax_idx = -1 if tmax_idx is None else tmax_idx
-
         tmin = self.times[tmin_idx]
 
-        times = np.arange(self.times[tmin_idx],
-                          self.times[tmax_idx] + self.tstep / 2, self.tstep)
-
         if data_t.ndim > 2:
             # return list of stcs if transformed data has dimensionality > 2
             if copy:
@@ -904,15 +996,16 @@ class _BaseSourceEstimate(ToDataFrameMixin, TimeMixin):
         else:
             # return new or overwritten stc
             stcs = self if not copy else self.copy()
-            stcs._data, stcs.vertices = data_t, verts
-            stcs.tmin, stcs.times = tmin, times
+            stcs.vertices = verts
+            stcs.data = data_t
+            stcs.tmin = tmin
 
         return stcs
 
 
 def _center_of_mass(vertices, values, hemi, surf, subject, subjects_dir,
                     restrict_vertices):
-    """Helper to find the center of mass on a surface"""
+    """Find the center of mass on a surface."""
     if (values == 0).all() or (values < 0).any():
         raise ValueError('All values must be non-negative and at least one '
                          'must be non-zero, cannot compute COM')
@@ -936,16 +1029,13 @@ def _center_of_mass(vertices, values, hemi, surf, subject, subjects_dir,
     return vertex
 
 
-class SourceEstimate(_BaseSourceEstimate):
-    """Container for surface source estimates
+class _BaseSurfaceSourceEstimate(_BaseSourceEstimate):
+    """Abstract base class for surface source estimates.
 
     Parameters
     ----------
-    data : array of shape (n_dipoles, n_times) | 2-tuple (kernel, sens_data)
-        The data in source space. The data can either be a single array or
-        a tuple with two arrays: "kernel" shape (n_vertices, n_sensors) and
-        "sens_data" shape (n_sensors, n_times). In this case, the source
-        space data corresponds to "numpy.dot(kernel, sens_data)".
+    data : array
+        The data in source space.
     vertices : list of two arrays
         Vertex numbers corresponding to the data.
     tmin : scalar
@@ -956,7 +1046,8 @@ class SourceEstimate(_BaseSourceEstimate):
         The subject name. While not necessary, it is safer to set the
         subject parameter to avoid analysis errors.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Attributes
     ----------
@@ -966,14 +1057,15 @@ class SourceEstimate(_BaseSourceEstimate):
         The time vector.
     vertices : list of two arrays of shape (n_dipoles,)
         The indices of the dipoles in the left and right source space.
-    data : array of shape (n_dipoles, n_times)
+    data : array
         The data in source space.
     shape : tuple
         The shape of the data. A tuple of int (n_dipoles, n_times).
     """
+
     @verbose
     def __init__(self, data, vertices=None, tmin=None, tstep=None,
-                 subject=None, verbose=None):
+                 subject=None, verbose=None):  # noqa: D102
 
         if not (isinstance(vertices, list) and len(vertices) == 2):
             raise ValueError('Vertices, if a list, must contain two '
@@ -983,54 +1075,7 @@ class SourceEstimate(_BaseSourceEstimate):
                                      tstep=tstep, subject=subject,
                                      verbose=verbose)
 
-    @verbose
-    def save(self, fname, ftype='stc', verbose=None):
-        """Save the source estimates to a file
-
-        Parameters
-        ----------
-        fname : string
-            The stem of the file name. The file names used for surface source
-            spaces are obtained by adding "-lh.stc" and "-rh.stc" (or "-lh.w"
-            and "-rh.w") to the stem provided, for the left and the right
-            hemisphere, respectively.
-        ftype : string
-            File format to use. Allowed values are "stc" (default), "w",
-            and "h5". The "w" format only supports a single time point.
-        verbose : bool, str, int, or None
-            If not None, override default verbose level (see mne.verbose).
-            Defaults to self.verbose.
-        """
-        if ftype not in ('stc', 'w', 'h5'):
-            raise ValueError('ftype must be "stc", "w", or "h5", not "%s"'
-                             % ftype)
-
-        lh_data = self.data[:len(self.lh_vertno)]
-        rh_data = self.data[-len(self.rh_vertno):]
-
-        if ftype == 'stc':
-            logger.info('Writing STC to disk...')
-            _write_stc(fname + '-lh.stc', tmin=self.tmin, tstep=self.tstep,
-                       vertices=self.lh_vertno, data=lh_data)
-            _write_stc(fname + '-rh.stc', tmin=self.tmin, tstep=self.tstep,
-                       vertices=self.rh_vertno, data=rh_data)
-        elif ftype == 'w':
-            if self.shape[1] != 1:
-                raise ValueError('w files can only contain a single time '
-                                 'point')
-            logger.info('Writing STC to disk (w format)...')
-            _write_w(fname + '-lh.w', vertices=self.lh_vertno,
-                     data=lh_data[:, 0])
-            _write_w(fname + '-rh.w', vertices=self.rh_vertno,
-                     data=rh_data[:, 0])
-        elif ftype == 'h5':
-            write_hdf5(fname + '-stc.h5',
-                       dict(vertices=self.vertices, data=self.data,
-                            tmin=self.tmin, tstep=self.tstep,
-                            subject=self.subject), title='mnepython')
-        logger.info('[done]')
-
-    def __repr__(self):
+    def __repr__(self):  # noqa: D105
         if isinstance(self.vertices, list):
             nv = sum([len(v) for v in self.vertices])
         else:
@@ -1041,27 +1086,30 @@ class SourceEstimate(_BaseSourceEstimate):
         s += ", tmin : %s (ms)" % (1e3 * self.tmin)
         s += ", tmax : %s (ms)" % (1e3 * self.times[-1])
         s += ", tstep : %s (ms)" % (1e3 * self.tstep)
-        s += ", data size : %s x %s" % self.shape
-        return "<SourceEstimate  |  %s>" % s
+        s += ", data shape : %s" % (self.shape,)
+        return "<%s  |  %s>" % (type(self).__name__, s)
 
     @property
     def lh_data(self):
+        """Left hemisphere data."""
         return self.data[:len(self.lh_vertno)]
 
     @property
     def rh_data(self):
+        """Right hemisphere data."""
         return self.data[len(self.lh_vertno):]
 
     @property
     def lh_vertno(self):
+        """Left hemisphere vertno."""
         return self.vertices[0]
 
     @property
     def rh_vertno(self):
+        """Right hemisphere vertno."""
         return self.vertices[1]
 
     def _hemilabel_stc(self, label):
-
         if label.hemi == 'lh':
             stc_vertices = self.vertices[0]
         else:
@@ -1082,7 +1130,7 @@ class SourceEstimate(_BaseSourceEstimate):
         return vertices, values
 
     def in_label(self, label):
-        """Returns a SourceEstimate object restricted to a label
+        """Get a source estimate object restricted to a label.
 
         SourceEstimate contains the time course of
         activation of all sources inside the label.
@@ -1093,6 +1141,11 @@ class SourceEstimate(_BaseSourceEstimate):
             The label (as created for example by mne.read_label). If the label
             does not match any sources in the SourceEstimate, a ValueError is
             raised.
+
+        Returns
+        -------
+        stc : SourceEstimate | VectorSourceEstimate
+            The source estimate restricted to the given label.
         """
         # make sure label and stc are compatible
         if label.subject is not None and self.subject is not None \
@@ -1118,13 +1171,12 @@ class SourceEstimate(_BaseSourceEstimate):
         if sum([len(v) for v in vertices]) == 0:
             raise ValueError('No vertices match the label in the stc file')
 
-        label_stc = SourceEstimate(values, vertices=vertices,
-                                   tmin=self.tmin, tstep=self.tstep,
-                                   subject=self.subject)
+        label_stc = self.__class__(values, vertices=vertices, tmin=self.tmin,
+                                   tstep=self.tstep, subject=self.subject)
         return label_stc
 
     def expand(self, vertices):
-        """Expand SourceEstimate to include more vertices
+        """Expand SourceEstimate to include more vertices.
 
         This will add rows to stc.data (zero-filled) and modify stc.vertices
         to include all vertices in stc.vertices and the input vertices.
@@ -1136,7 +1188,7 @@ class SourceEstimate(_BaseSourceEstimate):
 
         Returns
         -------
-        stc : instance of SourceEstimate
+        stc : SourceEstimate | VectorSourceEstimate
             The modified stc (note: method operates inplace).
         """
         if not isinstance(vertices, list):
@@ -1159,185 +1211,14 @@ class SourceEstimate(_BaseSourceEstimate):
             self.vertices[vi] = np.insert(v_old, inds, v_new)
         inds = [ii + offset for ii, offset in zip(inserters, offsets[:-1])]
         inds = np.concatenate(inds)
-        new_data = np.zeros((len(inds), self._data.shape[1]))
-        self._data = np.insert(self._data, inds, new_data, axis=0)
+        new_data = np.zeros((len(inds),) + self.data.shape[1:])
+        self.data = np.insert(self.data, inds, new_data, axis=0)
         return self
 
     @verbose
-    def extract_label_time_course(self, labels, src, mode='mean_flip',
-                                  allow_empty=False, verbose=None):
-        """Extract label time courses for lists of labels
-
-        This function will extract one time course for each label. The way the
-        time courses are extracted depends on the mode parameter.
-
-        Valid values for mode are:
-
-            - 'mean': Average within each label.
-            - 'mean_flip': Average within each label with sign flip depending
-              on source orientation.
-            - 'pca_flip': Apply an SVD to the time courses within each label
-              and use the scaled and sign-flipped first right-singular vector
-              as the label time course. The scaling is performed such that the
-              power of the label time course is the same as the average
-              per-vertex time course power within the label. The sign of the
-              resulting time course is adjusted by multiplying it with
-              "sign(dot(u, flip))" where u is the first left-singular vector,
-              and flip is a sing-flip vector based on the vertex normals. This
-              procedure assures that the phase does not randomly change by 180
-              degrees from one stc to the next.
-            - 'max': Max value within each label.
-
-
-        Parameters
-        ----------
-        labels : Label | list of Label
-            The labels for which to extract the time courses.
-        src : list
-            Source spaces for left and right hemisphere.
-        mode : str
-            Extraction mode, see explanation above.
-        allow_empty : bool
-            Instead of emitting an error, return all-zero time course for
-            labels that do not have any vertices in the source estimate.
-        verbose : bool, str, int, or None
-            If not None, override default verbose level (see mne.verbose).
-
-        Returns
-        -------
-        label_tc : array, shape=(len(labels), n_times)
-            Extracted time course for each label.
-
-        See Also
-        --------
-        extract_label_time_course : extract time courses for multiple STCs
-        """
-        label_tc = extract_label_time_course(self, labels, src, mode=mode,
-                                             return_generator=False,
-                                             allow_empty=allow_empty,
-                                             verbose=verbose)
-
-        return label_tc
-
-    def center_of_mass(self, subject=None, hemi=None, restrict_vertices=False,
-                       subjects_dir=None, surf='sphere'):
-        """Compute the center of mass of activity
-
-        This function computes the spatial center of mass on the surface
-        as well as the temporal center of mass as in [1]_.
-
-        .. note:: All activity must occur in a single hemisphere, otherwise
-                  an error is raised. The "mass" of each point in space for
-                  computing the spatial center of mass is computed by summing
-                  across time, and vice-versa for each point in time in
-                  computing the temporal center of mass. This is useful for
-                  quantifying spatio-temporal cluster locations, especially
-                  when combined with :func:`mne.source_space.vertex_to_mni`.
-
-        Parameters
-        ----------
-        subject : string | None
-            The subject the stc is defined for.
-        hemi : int, or None
-            Calculate the center of mass for the left (0) or right (1)
-            hemisphere. If None, one of the hemispheres must be all zeroes,
-            and the center of mass will be calculated for the other
-            hemisphere (useful for getting COM for clusters).
-        restrict_vertices : bool | array of int | instance of SourceSpaces
-            If True, returned vertex will be one from stc. Otherwise, it could
-            be any vertex from surf. If an array of int, the returned vertex
-            will come from that array. If instance of SourceSpaces (as of
-            0.13), the returned vertex will be from the given source space.
-            For most accuruate estimates, do not restrict vertices.
-        subjects_dir : str, or None
-            Path to the SUBJECTS_DIR. If None, the path is obtained by using
-            the environment variable SUBJECTS_DIR.
-        surf : str
-            The surface to use for Euclidean distance center of mass
-            finding. The default here is "sphere", which finds the center
-            of mass on the spherical surface to help avoid potential issues
-            with cortical folding.
-
-        See Also
-        --------
-        Label.center_of_mass
-        vertex_to_mni
-
-        Returns
-        -------
-        vertex : int
-            Vertex of the spatial center of mass for the inferred hemisphere,
-            with each vertex weighted by the sum of the stc across time. For a
-            boolean stc, then, this would be weighted purely by the duration
-            each vertex was active.
-        hemi : int
-            Hemisphere the vertex was taken from.
-        t : float
-            Time of the temporal center of mass (weighted by the sum across
-            source vertices).
-
-        References
-        ----------
-        .. [1] Larson and Lee, "The cortical dynamics underlying effective
-               switching of auditory spatial attention", NeuroImage 2012.
-        """
-        if not isinstance(surf, string_types):
-            raise TypeError('surf must be a string, got %s' % (type(surf),))
-        subject = _check_subject(self.subject, subject)
-        if np.any(self.data < 0):
-            raise ValueError('Cannot compute COM with negative values')
-        values = np.sum(self.data, axis=1)  # sum across time
-        vert_inds = [np.arange(len(self.vertices[0])),
-                     np.arange(len(self.vertices[1])) + len(self.vertices[0])]
-        if hemi is None:
-            hemi = np.where(np.array([np.sum(values[vi])
-                            for vi in vert_inds]))[0]
-            if not len(hemi) == 1:
-                raise ValueError('Could not infer hemisphere')
-            hemi = hemi[0]
-        if hemi not in [0, 1]:
-            raise ValueError('hemi must be 0 or 1')
-        vertices = self.vertices[hemi]
-        values = values[vert_inds[hemi]]  # left or right
-        del vert_inds
-        vertex = _center_of_mass(
-            vertices, values, hemi=['lh', 'rh'][hemi], surf=surf,
-            subject=subject, subjects_dir=subjects_dir,
-            restrict_vertices=restrict_vertices)
-        # do time center of mass by using the values across space
-        masses = np.sum(self.data, axis=0).astype(float)
-        t_ind = np.sum(masses * np.arange(self.shape[1])) / np.sum(masses)
-        t = self.tmin + self.tstep * t_ind
-        return vertex, hemi, t
-
-    @copy_function_doc_to_method_doc(plot_source_estimates)
-    def plot(self, subject=None, surface='inflated', hemi='lh',
-             colormap='auto', time_label='auto',
-             smoothing_steps=10, transparent=None, alpha=1.0,
-             time_viewer=False, config_opts=None, subjects_dir=None,
-             figure=None, views='lat', colorbar=True, clim='auto',
-             cortex="classic", size=800, background="black",
-             foreground="white", initial_time=None, time_unit=None):
-        brain = plot_source_estimates(self, subject, surface=surface,
-                                      hemi=hemi, colormap=colormap,
-                                      time_label=time_label,
-                                      smoothing_steps=smoothing_steps,
-                                      transparent=transparent, alpha=alpha,
-                                      time_viewer=time_viewer,
-                                      config_opts=config_opts,
-                                      subjects_dir=subjects_dir, figure=figure,
-                                      views=views, colorbar=colorbar,
-                                      clim=clim, cortex=cortex, size=size,
-                                      background=background,
-                                      foreground=foreground,
-                                      initial_time=initial_time,
-                                      time_unit=time_unit)
-        return brain
-
-    @verbose
     def to_original_src(self, src_orig, subject_orig=None,
                         subjects_dir=None, verbose=None):
-        """Return a SourceEstimate from morphed source to the original subject
+        """Get a source estimate from morphed source to the original subject.
 
         Parameters
         ----------
@@ -1350,7 +1231,14 @@ class SourceEstimate(_BaseSourceEstimate):
         subjects_dir : string, or None
             Path to SUBJECTS_DIR if it is not set in the environment.
         verbose : bool, str, int, or None
-            If not None, override default verbose level (see mne.verbose).
+            If not None, override default verbose level (see
+            :func:`mne.verbose` and :ref:`Logging documentation <tut_logging>`
+            for more).
+
+        Returns
+        -------
+        stc : SourceEstimate | VectorSourceEstimate
+            The transformed source estimate.
 
         See Also
         --------
@@ -1366,14 +1254,14 @@ class SourceEstimate(_BaseSourceEstimate):
         subject_orig = _ensure_src_subject(src_orig, subject_orig)
         data_idx, vertices = _get_morph_src_reordering(
             self.vertices, src_orig, subject_orig, self.subject, subjects_dir)
-        return SourceEstimate(self._data[data_idx], vertices,
+        return self.__class__(self._data[data_idx], vertices,
                               self.tmin, self.tstep, subject_orig)
 
     @verbose
     def morph(self, subject_to, grade=5, smooth=None, subjects_dir=None,
               buffer_size=64, n_jobs=1, subject_from=None, sparse=False,
               verbose=None):
-        """Morph a source estimate from one subject to another
+        """Morph a source estimate from one subject to another.
 
         Parameters
         ----------
@@ -1389,7 +1277,7 @@ class SourceEstimate(_BaseSourceEstimate):
             computing vertex locations. Note that if subject='fsaverage'
             and 'grade=5', this set of vertices will automatically be used
             (instead of computed) for speed, since this is a common morph.
-            NOTE : If sparse=True, grade has to be set to None.
+            .. note :: If sparse=True, grade has to be set to None.
         smooth : int or None
             Number of iterations for the smoothing of the surface data.
             If None, smooth is automatically defined to fill the surface
@@ -1409,11 +1297,13 @@ class SourceEstimate(_BaseSourceEstimate):
             parameters used are subject_to and subject_from,
             and grade has to be None.
         verbose : bool, str, int, or None
-            If not None, override default verbose level (see mne.verbose).
+            If not None, override default verbose level (see
+            :func:`mne.verbose` and :ref:`Logging documentation <tut_logging>`
+            for more).
 
         Returns
         -------
-        stc_to : SourceEstimate
+        stc_to : SourceEstimate | VectorSourceEstimate
             Source estimate for the destination subject.
         """
         subject_from = _check_subject(self.subject, subject_from)
@@ -1425,34 +1315,213 @@ class SourceEstimate(_BaseSourceEstimate):
             return morph_data(subject_from, subject_to, self, grade, smooth,
                               subjects_dir, buffer_size, n_jobs, verbose)
 
-    def morph_precomputed(self, subject_to, vertices_to, morph_mat,
-                          subject_from=None):
-        """Morph source estimate between subjects using a precomputed matrix
+    def morph_precomputed(self, subject_to, vertices_to, morph_mat,
+                          subject_from=None):
+        """Morph source estimate between subjects using a precomputed matrix.
+
+        Parameters
+        ----------
+        subject_to : string
+            Name of the subject on which to morph as named in the SUBJECTS_DIR.
+        vertices_to : list of array of int
+            The vertices on the destination subject's brain.
+        morph_mat : sparse matrix
+            The morphing matrix, usually from compute_morph_matrix.
+        subject_from : string | None
+            Name of the original subject as named in the SUBJECTS_DIR.
+            If None, self.subject will be used.
+
+        Returns
+        -------
+        stc_to : SourceEstimate | VectorSourceEstimate
+            Source estimate for the destination subject.
+        """
+        subject_from = _check_subject(self.subject, subject_from)
+        return morph_data_precomputed(subject_from, subject_to, self,
+                                      vertices_to, morph_mat)
+
+
+class SourceEstimate(_BaseSurfaceSourceEstimate):
+    """Container for surface source estimates.
+
+    Parameters
+    ----------
+    data : array of shape (n_dipoles, n_times) | 2-tuple (kernel, sens_data)
+        The data in source space. The data can either be a single array or
+        a tuple with two arrays: "kernel" shape (n_vertices, n_sensors) and
+        "sens_data" shape (n_sensors, n_times). In this case, the source
+        space data corresponds to "numpy.dot(kernel, sens_data)".
+    vertices : list of two arrays
+        Vertex numbers corresponding to the data.
+    tmin : scalar
+        Time point of the first sample in data.
+    tstep : scalar
+        Time step between successive samples in data.
+    subject : str | None
+        The subject name. While not necessary, it is safer to set the
+        subject parameter to avoid analysis errors.
+    verbose : bool, str, int, or None
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
+
+    Attributes
+    ----------
+    subject : str | None
+        The subject name.
+    times : array of shape (n_times,)
+        The time vector.
+    vertices : list of two arrays of shape (n_dipoles,)
+        The indices of the dipoles in the left and right source space.
+    data : array of shape (n_dipoles, n_times)
+        The data in source space.
+    shape : tuple
+        The shape of the data. A tuple of int (n_dipoles, n_times).
+
+    See Also
+    --------
+    VectorSourceEstimate : A container for vector source estimates.
+    VolSourceEstimate : A container for volume source estimates.
+    MixedSourceEstimate : A container for mixed surface + volume source
+                          estimates.
+    """
+
+    @verbose
+    def save(self, fname, ftype='stc', verbose=None):
+        """Save the source estimates to a file.
+
+        Parameters
+        ----------
+        fname : string
+            The stem of the file name. The file names used for surface source
+            spaces are obtained by adding "-lh.stc" and "-rh.stc" (or "-lh.w"
+            and "-rh.w") to the stem provided, for the left and the right
+            hemisphere, respectively.
+        ftype : string
+            File format to use. Allowed values are "stc" (default), "w",
+            and "h5". The "w" format only supports a single time point.
+        verbose : bool, str, int, or None
+            If not None, override default verbose level (see
+            :func:`mne.verbose` and :ref:`Logging documentation <tut_logging>`
+            for more). Defaults to self.verbose.
+        """
+        if ftype not in ('stc', 'w', 'h5'):
+            raise ValueError('ftype must be "stc", "w", or "h5", not "%s"'
+                             % ftype)
+
+        lh_data = self.data[:len(self.lh_vertno)]
+        rh_data = self.data[-len(self.rh_vertno):]
+
+        if ftype == 'stc':
+            logger.info('Writing STC to disk...')
+            _write_stc(fname + '-lh.stc', tmin=self.tmin, tstep=self.tstep,
+                       vertices=self.lh_vertno, data=lh_data)
+            _write_stc(fname + '-rh.stc', tmin=self.tmin, tstep=self.tstep,
+                       vertices=self.rh_vertno, data=rh_data)
+
+        elif ftype == 'w':
+            if self.shape[1] != 1:
+                raise ValueError('w files can only contain a single time '
+                                 'point')
+            logger.info('Writing STC to disk (w format)...')
+            _write_w(fname + '-lh.w', vertices=self.lh_vertno,
+                     data=lh_data[:, 0])
+            _write_w(fname + '-rh.w', vertices=self.rh_vertno,
+                     data=rh_data[:, 0])
+
+        elif ftype == 'h5':
+            if not fname.endswith('.h5'):
+                fname += '-stc.h5'
+            write_hdf5(fname,
+                       dict(vertices=self.vertices, data=self.data,
+                            tmin=self.tmin, tstep=self.tstep,
+                            subject=self.subject), title='mnepython',
+                       overwrite=True)
+        logger.info('[done]')
+
+    @copy_function_doc_to_method_doc(plot_source_estimates)
+    def plot(self, subject=None, surface='inflated', hemi='lh',
+             colormap='auto', time_label='auto', smoothing_steps=10,
+             transparent=None, alpha=1.0, time_viewer=False, subjects_dir=None,
+             figure=None, views='lat', colorbar=True, clim='auto',
+             cortex="classic", size=800, background="black",
+             foreground="white", initial_time=None, time_unit='s',
+             backend='auto', spacing='oct6'):
+        brain = plot_source_estimates(self, subject, surface=surface,
+                                      hemi=hemi, colormap=colormap,
+                                      time_label=time_label,
+                                      smoothing_steps=smoothing_steps,
+                                      transparent=transparent, alpha=alpha,
+                                      time_viewer=time_viewer,
+                                      subjects_dir=subjects_dir, figure=figure,
+                                      views=views, colorbar=colorbar,
+                                      clim=clim, cortex=cortex, size=size,
+                                      background=background,
+                                      foreground=foreground,
+                                      initial_time=initial_time,
+                                      time_unit=time_unit, backend=backend,
+                                      spacing=spacing)
+        return brain
+
+    @verbose
+    def extract_label_time_course(self, labels, src, mode='mean_flip',
+                                  allow_empty=False, verbose=None):
+        """Extract label time courses for lists of labels.
+
+        This function will extract one time course for each label. The way the
+        time courses are extracted depends on the mode parameter.
+
+        Valid values for mode are:
+
+            - 'mean': Average within each label.
+            - 'mean_flip': Average within each label with sign flip depending
+              on source orientation.
+            - 'pca_flip': Apply an SVD to the time courses within each label
+              and use the scaled and sign-flipped first right-singular vector
+              as the label time course. The scaling is performed such that the
+              power of the label time course is the same as the average
+              per-vertex time course power within the label. The sign of the
+              resulting time course is adjusted by multiplying it with
+              "sign(dot(u, flip))" where u is the first left-singular vector,
+              and flip is a sing-flip vector based on the vertex normals. This
+              procedure assures that the phase does not randomly change by 180
+              degrees from one stc to the next.
+            - 'max': Max value within each label.
+
 
         Parameters
         ----------
-        subject_to : string
-            Name of the subject on which to morph as named in the SUBJECTS_DIR.
-        vertices_to : list of array of int
-            The vertices on the destination subject's brain.
-        morph_mat : sparse matrix
-            The morphing matrix, usually from compute_morph_matrix.
-        subject_from : string | None
-            Name of the original subject as named in the SUBJECTS_DIR.
-            If None, self.subject will be used.
+        labels : Label | BiHemiLabel | list of Label or BiHemiLabel
+            The labels for which to extract the time courses.
+        src : list
+            Source spaces for left and right hemisphere.
+        mode : str
+            Extraction mode, see explanation above.
+        allow_empty : bool
+            Instead of emitting an error, return all-zero time course for
+            labels that do not have any vertices in the source estimate.
+        verbose : bool, str, int, or None
+            If not None, override default verbose level (see
+            :func:`mne.verbose` and :ref:`Logging documentation <tut_logging>`
+            for more).
 
         Returns
         -------
-        stc_to : SourceEstimate
-            Source estimate for the destination subject.
+        label_tc : array, shape=(len(labels), n_times)
+            Extracted time course for each label.
+
+        See Also
+        --------
+        extract_label_time_course : extract time courses for multiple STCs
         """
-        subject_from = _check_subject(self.subject, subject_from)
-        return morph_data_precomputed(subject_from, subject_to, self,
-                                      vertices_to, morph_mat)
+        label_tc = extract_label_time_course(
+            self, labels, src, mode=mode, return_generator=False,
+            allow_empty=allow_empty, verbose=verbose)
+
+        return label_tc
 
     def get_peak(self, hemi=None, tmin=None, tmax=None, mode='abs',
                  vert_as_index=False, time_as_index=False):
-        """Get location and latency of peak amplitude
+        """Get location and latency of peak amplitude.
 
         Parameters
         ----------
@@ -1492,9 +1561,100 @@ class SourceEstimate(_BaseSourceEstimate):
         return (vert_idx if vert_as_index else vertno[vert_idx],
                 time_idx if time_as_index else self.times[time_idx])
 
+    def center_of_mass(self, subject=None, hemi=None, restrict_vertices=False,
+                       subjects_dir=None, surf='sphere'):
+        """Compute the center of mass of activity.
+
+        This function computes the spatial center of mass on the surface
+        as well as the temporal center of mass as in [1]_.
+
+        .. note:: All activity must occur in a single hemisphere, otherwise
+                  an error is raised. The "mass" of each point in space for
+                  computing the spatial center of mass is computed by summing
+                  across time, and vice-versa for each point in time in
+                  computing the temporal center of mass. This is useful for
+                  quantifying spatio-temporal cluster locations, especially
+                  when combined with :func:`mne.vertex_to_mni`.
+
+        Parameters
+        ----------
+        subject : string | None
+            The subject the stc is defined for.
+        hemi : int, or None
+            Calculate the center of mass for the left (0) or right (1)
+            hemisphere. If None, one of the hemispheres must be all zeroes,
+            and the center of mass will be calculated for the other
+            hemisphere (useful for getting COM for clusters).
+        restrict_vertices : bool | array of int | instance of SourceSpaces
+            If True, returned vertex will be one from stc. Otherwise, it could
+            be any vertex from surf. If an array of int, the returned vertex
+            will come from that array. If instance of SourceSpaces (as of
+            0.13), the returned vertex will be from the given source space.
+            For most accuruate estimates, do not restrict vertices.
+        subjects_dir : str, or None
+            Path to the SUBJECTS_DIR. If None, the path is obtained by using
+            the environment variable SUBJECTS_DIR.
+        surf : str
+            The surface to use for Euclidean distance center of mass
+            finding. The default here is "sphere", which finds the center
+            of mass on the spherical surface to help avoid potential issues
+            with cortical folding.
+
+        See Also
+        --------
+        mne.Label.center_of_mass
+        mne.vertex_to_mni
+
+        Returns
+        -------
+        vertex : int
+            Vertex of the spatial center of mass for the inferred hemisphere,
+            with each vertex weighted by the sum of the stc across time. For a
+            boolean stc, then, this would be weighted purely by the duration
+            each vertex was active.
+        hemi : int
+            Hemisphere the vertex was taken from.
+        t : float
+            Time of the temporal center of mass (weighted by the sum across
+            source vertices).
+
+        References
+        ----------
+        .. [1] Larson and Lee, "The cortical dynamics underlying effective
+               switching of auditory spatial attention", NeuroImage 2012.
+        """
+        if not isinstance(surf, string_types):
+            raise TypeError('surf must be a string, got %s' % (type(surf),))
+        subject = _check_subject(self.subject, subject)
+        if np.any(self.data < 0):
+            raise ValueError('Cannot compute COM with negative values')
+        values = np.sum(self.data, axis=1)  # sum across time
+        vert_inds = [np.arange(len(self.vertices[0])),
+                     np.arange(len(self.vertices[1])) + len(self.vertices[0])]
+        if hemi is None:
+            hemi = np.where(np.array([np.sum(values[vi])
+                            for vi in vert_inds]))[0]
+            if not len(hemi) == 1:
+                raise ValueError('Could not infer hemisphere')
+            hemi = hemi[0]
+        if hemi not in [0, 1]:
+            raise ValueError('hemi must be 0 or 1')
+        vertices = self.vertices[hemi]
+        values = values[vert_inds[hemi]]  # left or right
+        del vert_inds
+        vertex = _center_of_mass(
+            vertices, values, hemi=['lh', 'rh'][hemi], surf=surf,
+            subject=subject, subjects_dir=subjects_dir,
+            restrict_vertices=restrict_vertices)
+        # do time center of mass by using the values across space
+        masses = np.sum(self.data, axis=0).astype(float)
+        t_ind = np.sum(masses * np.arange(self.shape[1])) / np.sum(masses)
+        t = self.tmin + self.tstep * t_ind
+        return vertex, hemi, t
+
 
 class VolSourceEstimate(_BaseSourceEstimate):
-    """Container for volume source estimates
+    """Container for volume source estimates.
 
     Parameters
     ----------
@@ -1513,7 +1673,8 @@ class VolSourceEstimate(_BaseSourceEstimate):
         The subject name. While not necessary, it is safer to set the
         subject parameter to avoid analysis errors.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Attributes
     ----------
@@ -1531,11 +1692,18 @@ class VolSourceEstimate(_BaseSourceEstimate):
     Notes
     -----
     .. versionadded:: 0.9.0
+
+    See Also
+    --------
+    SourceEstimate : A container for surface source estimates.
+    VectorSourceEstimate : A container for vector source estimates.
+    MixedSourceEstimate : A container for mixed surface + volume source
+                          estimates.
     """
+
     @verbose
     def __init__(self, data, vertices=None, tmin=None, tstep=None,
-                 subject=None, verbose=None):
-
+                 subject=None, verbose=None):  # noqa: D102
         if not (isinstance(vertices, np.ndarray) or
                 isinstance(vertices, list) and len(vertices) == 1):
             raise ValueError('Vertices must be a numpy array or a list with '
@@ -1547,7 +1715,7 @@ class VolSourceEstimate(_BaseSourceEstimate):
 
     @verbose
     def save(self, fname, ftype='stc', verbose=None):
-        """Save the source estimates to a file
+        """Save the source estimates to a file.
 
         Parameters
         ----------
@@ -1558,8 +1726,9 @@ class VolSourceEstimate(_BaseSourceEstimate):
             File format to use. Allowed values are "stc" (default) and "w".
             The "w" format only supports a single time point.
         verbose : bool, str, int, or None
-            If not None, override default verbose level (see mne.verbose).
-            Defaults to self.verbose.
+            If not None, override default verbose level (see
+            :func:`mne.verbose` and :ref:`Logging documentation <tut_logging>`
+            for more). Defaults to self.verbose.
         """
         if ftype not in ['stc', 'w']:
             raise ValueError('ftype must be "stc" or "w", not "%s"' % ftype)
@@ -1579,7 +1748,7 @@ class VolSourceEstimate(_BaseSourceEstimate):
         logger.info('[done]')
 
     def save_as_volume(self, fname, src, dest='mri', mri_resolution=False):
-        """Save a volume source estimate in a nifti file
+        """Save a volume source estimate in a NIfTI file.
 
         Parameters
         ----------
@@ -1609,7 +1778,7 @@ class VolSourceEstimate(_BaseSourceEstimate):
                            mri_resolution=mri_resolution)
 
     def as_volume(self, src, dest='mri', mri_resolution=False):
-        """Export volume source estimate as a nifti object
+        """Export volume source estimate as a nifti object.
 
         Parameters
         ----------
@@ -1636,7 +1805,7 @@ class VolSourceEstimate(_BaseSourceEstimate):
         return save_stc_as_volume(None, self, src, dest=dest,
                                   mri_resolution=mri_resolution)
 
-    def __repr__(self):
+    def __repr__(self):  # noqa: D105
         if isinstance(self.vertices, list):
             nv = sum([len(v) for v in self.vertices])
         else:
@@ -1652,7 +1821,7 @@ class VolSourceEstimate(_BaseSourceEstimate):
 
     def get_peak(self, tmin=None, tmax=None, mode='abs',
                  vert_as_index=False, time_as_index=False):
-        """Get location and latency of peak amplitude
+        """Get location and latency of peak amplitude.
 
         Parameters
         ----------
@@ -1679,7 +1848,6 @@ class VolSourceEstimate(_BaseSourceEstimate):
         latency : float
             The latency in seconds.
         """
-
         vert_idx, time_idx = _get_peak(self.data, self.times, tmin, tmax,
                                        mode)
 
@@ -1687,8 +1855,147 @@ class VolSourceEstimate(_BaseSourceEstimate):
                 time_idx if time_as_index else self.times[time_idx])
 
 
+class VectorSourceEstimate(_BaseSurfaceSourceEstimate):
+    """Container for vector surface source estimates.
+
+    For each vertex, the magnitude of the current is defined in the X, Y and Z
+    directions.
+
+    Parameters
+    ----------
+    data : array of shape (n_dipoles, 3, n_times)
+        The data in source space. Each dipole contains three vectors that
+        denote the dipole strength in X, Y and Z directions over time.
+    vertices : array | list of two arrays
+        Vertex numbers corresponding to the data.
+    tmin : float
+        Time point of the first sample in data.
+    tstep : float
+        Time step between successive samples in data.
+    subject : str | None
+        The subject name. While not necessary, it is safer to set the
+        subject parameter to avoid analysis errors.
+    verbose : bool, str, int, or None
+        If not None, override default verbose level (see mne.verbose).
+
+    Attributes
+    ----------
+    subject : str | None
+        The subject name.
+    times : array of shape (n_times,)
+        The time vector.
+    shape : tuple
+        The shape of the data. A tuple of int (n_dipoles, n_times).
+
+    Notes
+    -----
+    .. versionadded:: 0.15
+
+    See Also
+    --------
+    SourceEstimate : A container for surface source estimates.
+    VolSourceEstimate : A container for volume source estimates.
+    MixedSourceEstimate : A container for mixed surface + volume source
+                          estimates.
+    """
+
+    @verbose
+    def save(self, fname, ftype='h5', verbose=None):
+        """Save the full source estimate to an HDF5 file.
+
+        Parameters
+        ----------
+        fname : string
+            The file name to write the source estimate to, should end in
+            '-stc.h5'.
+        ftype : string
+            File format to use. Currently, the only allowed values is "h5".
+        verbose : bool, str, int, or None
+            If not None, override default verbose level (see mne.verbose).
+            Defaults to self.verbose.
+        """
+        if ftype != 'h5':
+            raise ValueError('VectorSourceEstimate objects can only be '
+                             'written as HDF5 files.')
+
+        if not fname.endswith('.h5'):
+            fname += '-stc.h5'
+
+        write_hdf5(fname,
+                   dict(vertices=self.vertices, data=self.data, tmin=self.tmin,
+                        tstep=self.tstep, subject=self.subject),
+                   title='mnepython', overwrite=True)
+
+    def magnitude(self):
+        """Compute magnitude of activity without directionality.
+
+        Returns
+        -------
+        stc : instance of SourceEstimate
+            The source estimate without directionality information.
+        """
+        data_mag = np.linalg.norm(self.data, axis=1)
+        return SourceEstimate(data_mag, self.vertices, self.tmin, self.tstep,
+                              self.subject, self.verbose)
+
+    def normal(self, src):
+        """Compute activity orthogonal to the cortex.
+
+        Parameters
+        ----------
+        src : instance of SourceSpaces
+            The source space for which this source estimate is specified.
+
+        Returns
+        -------
+        stc : instance of SourceEstimate
+            The source estimate only retaining the activity orthogonal to the
+            cortex.
+        """
+        normals = np.vstack([s['nn'][v] for s, v in zip(src, self.vertices)])
+        data_norm = np.einsum('ijk,ij->ik', self.data, normals)
+        return SourceEstimate(data_norm, self.vertices, self.tmin, self.tstep,
+                              self.subject, self.verbose)
+
+    @copy_function_doc_to_method_doc(plot_vector_source_estimates)
+    def plot(self, subject=None, hemi='lh', colormap='hot', time_label='auto',
+             smoothing_steps=10, transparent=None, brain_alpha=0.4,
+             overlay_alpha=None, vector_alpha=1.0, scale_factor=None,
+             time_viewer=False, subjects_dir=None, figure=None, views='lat',
+             colorbar=True, clim='auto', cortex='classic', size=800,
+             background='black', foreground='white', initial_time=None,
+             time_unit='s'):
+
+        return plot_vector_source_estimates(
+            self, subject=subject, hemi=hemi, colormap=colormap,
+            time_label=time_label, smoothing_steps=smoothing_steps,
+            transparent=transparent, brain_alpha=brain_alpha,
+            overlay_alpha=overlay_alpha, vector_alpha=vector_alpha,
+            scale_factor=scale_factor, time_viewer=time_viewer,
+            subjects_dir=subjects_dir, figure=figure, views=views,
+            colorbar=colorbar, clim=clim, cortex=cortex, size=size,
+            background=background, foreground=foreground,
+            initial_time=initial_time, time_unit=time_unit
+        )
+
+    def __abs__(self):
+        """Compute the absolute value of each component.
+
+        Returns
+        -------
+        stc_abs : VectorSourceEstimate
+            A vector source estimate where the data attribute is set to
+            abs(self.data).
+
+        See Also
+        --------
+        VectorSourceEstimate.magnitude
+        """
+        return super(VectorSourceEstimate, self).__abs__()
+
+
 class MixedSourceEstimate(_BaseSourceEstimate):
-    """Container for mixed surface and volume source estimates
+    """Container for mixed surface and volume source estimates.
 
     Parameters
     ----------
@@ -1707,7 +2014,8 @@ class MixedSourceEstimate(_BaseSourceEstimate):
         The subject name. While not necessary, it is safer to set the
         subject parameter to avoid analysis errors.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Attributes
     ----------
@@ -1725,11 +2033,17 @@ class MixedSourceEstimate(_BaseSourceEstimate):
     Notes
     -----
     .. versionadded:: 0.9.0
+
+    See Also
+    --------
+    SourceEstimate : A container for surface source estimates.
+    VectorSourceEstimate : A container for vector source estimates.
+    VolSourceEstimate : A container for volume source estimates.
     """
+
     @verbose
     def __init__(self, data, vertices=None, tmin=None, tstep=None,
-                 subject=None, verbose=None):
-
+                 subject=None, verbose=None):  # noqa: D102
         if not isinstance(vertices, list) or len(vertices) < 2:
             raise ValueError('Vertices must be a list of numpy arrays with '
                              'one array per source space.')
@@ -1744,7 +2058,7 @@ class MixedSourceEstimate(_BaseSourceEstimate):
                      transparent=None, alpha=1.0, time_viewer=False,
                      config_opts=None, subjects_dir=None, figure=None,
                      views='lat', colorbar=True, clim='auto'):
-        """Plot surface source estimates with PySurfer
+        """Plot surface source estimates with PySurfer.
 
         Note: PySurfer currently needs the SUBJECTS_DIR environment variable,
         which will automatically be set by this function. Plotting multiple
@@ -1801,7 +2115,6 @@ class MixedSourceEstimate(_BaseSourceEstimate):
         brain : Brain
             A instance of surfer.viz.Brain from PySurfer.
         """
-
         # extract surface source spaces
         surf = _ensure_src(src, kind='surf')
 
@@ -1829,12 +2142,12 @@ class MixedSourceEstimate(_BaseSourceEstimate):
 @verbose
 def _morph_buffer(data, idx_use, e, smooth, n_vertices, nearest, maps,
                   warn=True, verbose=None):
-    """Morph data from one subject's source space to another
+    """Morph data from one subject's source space to another.
 
     Parameters
     ----------
     data : array, or csr sparse matrix
-        A n_vertices x n_times (or other dimension) dataset to morph.
+        A n_vertices [x 3] x n_times (or other dimension) dataset to morph.
     idx_use : array of int
         Vertices from the original subject's data.
     e : sparse matrix
@@ -1851,13 +2164,25 @@ def _morph_buffer(data, idx_use, e, smooth, n_vertices, nearest, maps,
     warn : bool
         If True, warn if not all vertices were used.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
     data_morphed : array, or csr sparse matrix
         The morphed data (same type as input).
     """
+    # When operating on vector data, morph each dimension separately
+    if data.ndim == 3:
+        data_morphed = np.zeros((len(nearest), 3, data.shape[2]),
+                                dtype=data.dtype)
+        for dim in range(3):
+            data_morphed[:, dim, :] = _morph_buffer(
+                data=data[:, dim, :], idx_use=idx_use, e=e, smooth=smooth,
+                n_vertices=n_vertices, nearest=nearest, maps=maps, warn=warn,
+                verbose=verbose
+            )
+        return data_morphed
 
     n_iter = 99  # max nb of smoothing iterations (minus one)
     if smooth is not None:
@@ -1873,6 +2198,7 @@ def _morph_buffer(data, idx_use, e, smooth, n_vertices, nearest, maps,
             data = data.tocsr()
     else:
         use_sparse = False
+
     done = False
     # do the smoothing
     for k in range(n_iter + 1):
@@ -1922,12 +2248,13 @@ def _morph_buffer(data, idx_use, e, smooth, n_vertices, nearest, maps,
               % (len(data_sum) - len(idx_use), len(data_sum)))
 
     logger.info('    %d smooth iterations done.' % (k + 1))
+
     data_morphed = maps[nearest, :] * data
     return data_morphed
 
 
 def _morph_mult(data, e, use_sparse, idx_use_data, idx_use_out=None):
-    """Helper for morphing
+    """Help morphing.
 
     Equivalent to "data = (e[:, idx_use_data] * data)[idx_use_out]"
     but faster.
@@ -1961,8 +2288,7 @@ def _get_subject_sphere_tris(subject, subjects_dir):
 
 
 def _sparse_argmax_nnz_row(csr_mat):
-    """Return index of the maximum non-zero index in each row
-    """
+    """Return index of the maximum non-zero index in each row."""
     n_rows = csr_mat.shape[0]
     idx = np.empty(n_rows, dtype=np.int)
     for k in range(n_rows):
@@ -1972,11 +2298,11 @@ def _sparse_argmax_nnz_row(csr_mat):
 
 
 def _morph_sparse(stc, subject_from, subject_to, subjects_dir=None):
-    """Morph sparse source estimates to an other subject
+    """Morph sparse source estimates to an other subject.
 
     Parameters
     ----------
-    stc : SourceEstimate
+    stc : SourceEstimate | VectorSourceEstimate
         The sparse STC.
     subject_from : str
         The subject on which stc is defined.
@@ -1987,7 +2313,7 @@ def _morph_sparse(stc, subject_from, subject_to, subjects_dir=None):
 
     Returns
     -------
-    stc_morph : SourceEstimate
+    stc_morph : SourceEstimate | VectorSourceEstimate
         The morphed source estimates.
     """
     maps = read_morph_map(subject_to, subject_from, subjects_dir)
@@ -2001,8 +2327,8 @@ def _morph_sparse(stc, subject_from, subject_to, subjects_dir=None):
             vertno_k = _sparse_argmax_nnz_row(map_hemi[stc.vertices[k]])
             order = np.argsort(vertno_k)
             n_active_hemi = len(vertno_k)
-            data_hemi = stc_morph._data[cnt:cnt + n_active_hemi]
-            stc_morph._data[cnt:cnt + n_active_hemi] = data_hemi[order]
+            data_hemi = stc_morph.data[cnt:cnt + n_active_hemi]
+            stc_morph.data[cnt:cnt + n_active_hemi] = data_hemi[order]
             stc_morph.vertices[k] = vertno_k[order]
             cnt += n_active_hemi
         else:
@@ -2015,7 +2341,7 @@ def _morph_sparse(stc, subject_from, subject_to, subjects_dir=None):
 def morph_data(subject_from, subject_to, stc_from, grade=5, smooth=None,
                subjects_dir=None, buffer_size=64, n_jobs=1, warn=True,
                verbose=None):
-    """Morph a source estimate from one subject to another
+    """Morph a source estimate from one subject to another.
 
     Parameters
     ----------
@@ -2023,7 +2349,7 @@ def morph_data(subject_from, subject_to, stc_from, grade=5, smooth=None,
         Name of the original subject as named in the SUBJECTS_DIR
     subject_to : string
         Name of the subject on which to morph as named in the SUBJECTS_DIR
-    stc_from : SourceEstimate
+    stc_from : SourceEstimate | VectorSourceEstimate
         Source estimates for subject "from" to morph
     grade : int, list (of two arrays), or None
         Resolution of the icosahedral mesh (typically 5). If None, all
@@ -2049,16 +2375,17 @@ def morph_data(subject_from, subject_to, stc_from, grade=5, smooth=None,
     warn : bool
         If True, warn if not all vertices were used.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
-    stc_to : SourceEstimate
+    stc_to : SourceEstimate | VectorSourceEstimate
         Source estimate for the destination subject.
     """
-    if not isinstance(stc_from, SourceEstimate):
-        raise ValueError('Morphing is only possible with surface source '
-                         'estimates')
+    if not isinstance(stc_from, _BaseSurfaceSourceEstimate):
+        raise ValueError('Morphing is only possible with surface or vector '
+                         'source estimates')
 
     logger.info('Morphing data...')
     subjects_dir = get_subjects_dir(subjects_dir, raise_error=True)
@@ -2103,8 +2430,13 @@ def morph_data(subject_from, subject_to, stc_from, grade=5, smooth=None,
     else:
         data = np.r_[data_morphed[0], data_morphed[1]]
 
-    stc_to = SourceEstimate(data, vertices, stc_from.tmin, stc_from.tstep,
-                            subject=subject_to, verbose=stc_from.verbose)
+    if isinstance(stc_from, VectorSourceEstimate):
+        stc_to = VectorSourceEstimate(data, vertices, stc_from.tmin,
+                                      stc_from.tstep, subject=subject_to,
+                                      verbose=stc_from.verbose)
+    else:
+        stc_to = SourceEstimate(data, vertices, stc_from.tmin, stc_from.tstep,
+                                subject=subject_to, verbose=stc_from.verbose)
     logger.info('[done]')
 
     return stc_to
@@ -2113,57 +2445,93 @@ def morph_data(subject_from, subject_to, stc_from, grade=5, smooth=None,
 @verbose
 def compute_morph_matrix(subject_from, subject_to, vertices_from, vertices_to,
                          smooth=None, subjects_dir=None, warn=True,
-                         verbose=None):
-    """Get a matrix that morphs data from one subject to another
+                         xhemi=False, verbose=None):
+    """Get a matrix that morphs data from one subject to another.
 
     Parameters
     ----------
     subject_from : string
-        Name of the original subject as named in the SUBJECTS_DIR
+        Name of the original subject as named in the SUBJECTS_DIR.
     subject_to : string
-        Name of the subject on which to morph as named in the SUBJECTS_DIR
+        Name of the subject on which to morph as named in the SUBJECTS_DIR.
     vertices_from : list of arrays of int
-        Vertices for each hemisphere (LH, RH) for subject_from
+        Vertices for each hemisphere (LH, RH) for subject_from.
     vertices_to : list of arrays of int
-        Vertices for each hemisphere (LH, RH) for subject_to
+        Vertices for each hemisphere (LH, RH) for subject_to.
     smooth : int or None
         Number of iterations for the smoothing of the surface data.
         If None, smooth is automatically defined to fill the surface
         with non-zero values.
     subjects_dir : string
-        Path to SUBJECTS_DIR is not set in the environment
+        Path to SUBJECTS_DIR is not set in the environment.
     warn : bool
         If True, warn if not all vertices were used.
+    xhemi : bool
+        Morph across hemisphere. Currently only implemented for
+        ``subject_to == subject_from``. See notes below.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
     morph_matrix : sparse matrix
-        matrix that morphs data from subject_from to subject_to
+        matrix that morphs data from ``subject_from`` to ``subject_to``.
+
+    Notes
+    -----
+    This function can be used to morph data between hemispheres by setting
+    ``xhemi=True``. The full cross-hemisphere morph matrix maps left to right
+    and right to left. A matrix for cross-mapping only one hemisphere can be
+    constructed by specifying the appropriate vertices, for example, to map the
+    right hemisphere to the left:
+    ``vertices_from=[[], vert_rh], vertices_to=[vert_lh, []]``.
+
+    Cross-hemisphere mapping requires appropriate ``sphere.left_right``
+    morph-maps in the subject's directory. These morph maps are included
+    with the ``fsaverage_sym`` FreeSurfer subject, and can be created for other
+    subjects with the ``mris_left_right_register`` FreeSurfer command. The
+    ``fsaverage_sym`` subject is included with FreeSurfer > 5.1 and can be
+    obtained as described `here
+    <http://surfer.nmr.mgh.harvard.edu/fswiki/Xhemi>`_. For statistical
+    comparisons between hemispheres, use of the symmetric ``fsaverage_sym``
+    model is recommended to minimize bias [1]_.
+
+    References
+    ----------
+    .. [1] Greve D. N., Van der Haegen L., Cai Q., Stufflebeam S., Sabuncu M.
+           R., Fischl B., Brysbaert M.
+           A Surface-based Analysis of Language Lateralization and Cortical
+           Asymmetry. Journal of Cognitive Neuroscience 25(9), 1477-1492, 2013.
     """
     logger.info('Computing morph matrix...')
     subjects_dir = get_subjects_dir(subjects_dir, raise_error=True)
+
     tris = _get_subject_sphere_tris(subject_from, subjects_dir)
-    maps = read_morph_map(subject_from, subject_to, subjects_dir)
+    maps = read_morph_map(subject_from, subject_to, subjects_dir, xhemi)
 
-    morpher = [None] * 2
-    for hemi in [0, 1]:
-        e = mesh_edges(tris[hemi])
+    if xhemi:
+        hemi_indexes = [(0, 1), (1, 0)]
+    else:
+        hemi_indexes = [(0, 0), (1, 1)]
+
+    morpher = []
+    for hemi_from, hemi_to in hemi_indexes:
+        idx_use = vertices_from[hemi_from]
+        if len(idx_use) == 0:
+            continue
+        e = mesh_edges(tris[hemi_from])
         e.data[e.data == 2] = 1
         n_vertices = e.shape[0]
         e = e + sparse.eye(n_vertices, n_vertices)
-        idx_use = vertices_from[hemi]
-        if len(idx_use) == 0:
-            morpher[hemi] = []
-            continue
         m = sparse.eye(len(idx_use), len(idx_use), format='csr')
-        morpher[hemi] = _morph_buffer(m, idx_use, e, smooth, n_vertices,
-                                      vertices_to[hemi], maps[hemi], warn=warn)
-    # be careful about zero-length arrays
-    if isinstance(morpher[0], list):
-        morpher = morpher[1]
-    elif isinstance(morpher[1], list):
+        mm = _morph_buffer(m, idx_use, e, smooth, n_vertices,
+                           vertices_to[hemi_to], maps[hemi_from], warn=warn)
+        morpher.append(mm)
+
+    if len(morpher) == 0:
+        raise ValueError("Empty morph-matrix")
+    elif len(morpher) == 1:
         morpher = morpher[0]
     else:
         morpher = sparse_block_diag(morpher, format='csr')
@@ -2174,7 +2542,7 @@ def compute_morph_matrix(subject_from, subject_to, vertices_from, vertices_to,
 @verbose
 def grade_to_vertices(subject, grade, subjects_dir=None, n_jobs=1,
                       verbose=None):
-    """Convert a grade to source space vertices for a given subject
+    """Convert a grade to source space vertices for a given subject.
 
     Parameters
     ----------
@@ -2195,7 +2563,8 @@ def grade_to_vertices(subject, grade, subjects_dir=None, n_jobs=1,
     n_jobs : int
         Number of jobs to run in parallel
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -2248,7 +2617,7 @@ def grade_to_vertices(subject, grade, subjects_dir=None, n_jobs=1,
 
 def morph_data_precomputed(subject_from, subject_to, stc_from, vertices_to,
                            morph_mat):
-    """Morph source estimate between subjects using a precomputed matrix
+    """Morph source estimate between subjects using a precomputed matrix.
 
     Parameters
     ----------
@@ -2256,7 +2625,7 @@ def morph_data_precomputed(subject_from, subject_to, stc_from, vertices_to,
         Name of the original subject as named in the SUBJECTS_DIR.
     subject_to : string
         Name of the subject on which to morph as named in the SUBJECTS_DIR.
-    stc_from : SourceEstimate
+    stc_from : SourceEstimate | VectorSourceEstimate
         Source estimates for subject "from" to morph.
     vertices_to : list of array of int
         The vertices on the destination subject's brain.
@@ -2265,7 +2634,7 @@ def morph_data_precomputed(subject_from, subject_to, stc_from, vertices_to,
 
     Returns
     -------
-    stc_to : SourceEstimate
+    stc_to : SourceEstimate | VectorSourceEstimate
         Source estimate for the destination subject.
     """
     if not sparse.issparse(morph_mat):
@@ -2277,26 +2646,92 @@ def morph_data_precomputed(subject_from, subject_to, stc_from, vertices_to,
     if not sum(len(v) for v in vertices_to) == morph_mat.shape[0]:
         raise ValueError('number of vertices in vertices_to must match '
                          'morph_mat.shape[0]')
+
     if not stc_from.data.shape[0] == morph_mat.shape[1]:
         raise ValueError('stc_from.data.shape[0] must be the same as '
                          'morph_mat.shape[0]')
 
     if stc_from.subject is not None and stc_from.subject != subject_from:
         raise ValueError('stc_from.subject and subject_from must match')
-    data = morph_mat * stc_from.data
-    stc_to = SourceEstimate(data, vertices_to, stc_from.tmin, stc_from.tstep,
-                            verbose=stc_from.verbose, subject=subject_to)
+
+    if isinstance(stc_from, VectorSourceEstimate):
+        # Morph the locations of the dipoles, but not their orientation
+        n_verts, _, n_samples = stc_from.data.shape
+        data = morph_mat * stc_from.data.reshape(n_verts, 3 * n_samples)
+        data = data.reshape(morph_mat.shape[0], 3, n_samples)
+        stc_to = VectorSourceEstimate(data, vertices=vertices_to,
+                                      tmin=stc_from.tmin, tstep=stc_from.tstep,
+                                      verbose=stc_from.verbose,
+                                      subject=subject_to)
+    else:
+        data = morph_mat * stc_from.data
+        stc_to = SourceEstimate(data, vertices=vertices_to, tmin=stc_from.tmin,
+                                tstep=stc_from.tstep, verbose=stc_from.verbose,
+                                subject=subject_to)
     return stc_to
 
 
+def _get_vol_mask(src):
+    """Get the volume source space mask."""
+    assert len(src) == 1  # not a mixed source space
+    shape = src[0]['shape'][::-1]
+    mask = np.zeros(shape, bool)
+    mask.flat[src[0]['vertno']] = True
+    return mask
+
+
+def _spatio_temporal_src_connectivity_vol(src, n_times):
+    from sklearn.feature_extraction import grid_to_graph
+    mask = _get_vol_mask(src)
+    edges = grid_to_graph(*mask.shape, mask=mask)
+    connectivity = _get_connectivity_from_edges(edges, n_times)
+    return connectivity
+
+
+def _spatio_temporal_src_connectivity_ico(src, n_times):
+    if src[0]['use_tris'] is None:
+        # XXX It would be nice to support non oct source spaces too...
+        raise RuntimeError("The source space does not appear to be an ico "
+                           "surface. Connectivity cannot be extracted from"
+                           " non-ico source spaces.")
+    used_verts = [np.unique(s['use_tris']) for s in src]
+    lh_tris = np.searchsorted(used_verts[0], src[0]['use_tris'])
+    rh_tris = np.searchsorted(used_verts[1], src[1]['use_tris'])
+    tris = np.concatenate((lh_tris, rh_tris + np.max(lh_tris) + 1))
+    connectivity = spatio_temporal_tris_connectivity(tris, n_times)
+
+    # deal with source space only using a subset of vertices
+    masks = [np.in1d(u, s['vertno']) for s, u in zip(src, used_verts)]
+    if sum(u.size for u in used_verts) != connectivity.shape[0] / n_times:
+        raise ValueError('Used vertices do not match connectivity shape')
+    if [np.sum(m) for m in masks] != [len(s['vertno']) for s in src]:
+        raise ValueError('Vertex mask does not match number of vertices')
+    masks = np.concatenate(masks)
+    missing = 100 * float(len(masks) - np.sum(masks)) / len(masks)
+    if missing:
+        warn_('%0.1f%% of original source space vertices have been'
+              ' omitted, tri-based connectivity will have holes.\n'
+              'Consider using distance-based connectivity or '
+              'morphing data to all source space vertices.' % missing)
+        masks = np.tile(masks, n_times)
+        masks = np.where(masks)[0]
+        connectivity = connectivity.tocsr()
+        connectivity = connectivity[masks]
+        connectivity = connectivity[:, masks]
+        # return to original format
+        connectivity = connectivity.tocoo()
+    return connectivity
+
+
 @verbose
 def spatio_temporal_src_connectivity(src, n_times, dist=None, verbose=None):
-    """Compute connectivity for a source space activation over time
+    """Compute connectivity for a source space activation over time.
 
     Parameters
     ----------
     src : instance of SourceSpaces
-        The source space.
+        The source space. It can be a surface source space or a
+        volume source space.
     n_times : int
         Number of time instants.
     dist : float, or None
@@ -2304,7 +2739,8 @@ def spatio_temporal_src_connectivity(src, n_times, dist=None, verbose=None):
         source space to consider neighbors. If None, immediate neighbors
         are extracted from an ico surface.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -2315,53 +2751,33 @@ def spatio_temporal_src_connectivity(src, n_times, dist=None, verbose=None):
         vertices are time 1, the nodes from 2 to 2N are the vertices
         during time 2, etc.
     """
-    if dist is None:
-        if src[0]['use_tris'] is None:
-            raise RuntimeError("The source space does not appear to be an ico "
-                               "surface. Connectivity cannot be extracted from"
-                               " non-ico source spaces.")
-        used_verts = [np.unique(s['use_tris']) for s in src]
-        lh_tris = np.searchsorted(used_verts[0], src[0]['use_tris'])
-        rh_tris = np.searchsorted(used_verts[1], src[1]['use_tris'])
-        tris = np.concatenate((lh_tris, rh_tris + np.max(lh_tris) + 1))
-        connectivity = spatio_temporal_tris_connectivity(tris, n_times)
-
-        # deal with source space only using a subset of vertices
-        masks = [np.in1d(u, s['vertno']) for s, u in zip(src, used_verts)]
-        if sum(u.size for u in used_verts) != connectivity.shape[0] / n_times:
-            raise ValueError('Used vertices do not match connectivity shape')
-        if [np.sum(m) for m in masks] != [len(s['vertno']) for s in src]:
-            raise ValueError('Vertex mask does not match number of vertices')
-        masks = np.concatenate(masks)
-        missing = 100 * float(len(masks) - np.sum(masks)) / len(masks)
-        if missing:
-            warn_('%0.1f%% of original source space vertices have been'
-                  ' omitted, tri-based connectivity will have holes.\n'
-                  'Consider using distance-based connectivity or '
-                  'morphing data to all source space vertices.' % missing)
-            masks = np.tile(masks, n_times)
-            masks = np.where(masks)[0]
-            connectivity = connectivity.tocsr()
-            connectivity = connectivity[masks]
-            connectivity = connectivity[:, masks]
-            # return to original format
-            connectivity = connectivity.tocoo()
-
-        return connectivity
-    else:  # use distances computed and saved in the source space file
-        return spatio_temporal_dist_connectivity(src, n_times, dist)
+    # XXX we should compute connectivity for each source space and then
+    # use scipy.sparse.block_diag to concatenate them
+    if src[0]['type'] == 'vol':
+        if dist is not None:
+            raise ValueError('dist must be None for a volume '
+                             'source space. Got %s.' % dist)
+
+        connectivity = _spatio_temporal_src_connectivity_vol(src, n_times)
+    elif dist is not None:
+        # use distances computed and saved in the source space file
+        connectivity = spatio_temporal_dist_connectivity(src, n_times, dist)
+    else:
+        connectivity = _spatio_temporal_src_connectivity_ico(src, n_times)
+    return connectivity
 
 
 @verbose
 def grade_to_tris(grade, verbose=None):
-    """Get tris defined for a certain grade
+    """Get tris defined for a certain grade.
 
     Parameters
     ----------
     grade : int
         Grade of an icosahedral mesh.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -2377,7 +2793,7 @@ def grade_to_tris(grade, verbose=None):
 @verbose
 def spatio_temporal_tris_connectivity(tris, n_times, remap_vertices=False,
                                       verbose=None):
-    """Compute connectivity from triangles and time instants
+    """Compute connectivity from triangles and time instants.
 
     Parameters
     ----------
@@ -2389,7 +2805,8 @@ def spatio_temporal_tris_connectivity(tris, n_times, remap_vertices=False,
         Reassign vertex indices based on unique values. Useful
         to process a subset of triangles. Defaults to False.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -2410,7 +2827,7 @@ def spatio_temporal_tris_connectivity(tris, n_times, remap_vertices=False,
 
 @verbose
 def spatio_temporal_dist_connectivity(src, n_times, dist, verbose=None):
-    """Compute connectivity from distances in a source space and time instants
+    """Compute connectivity from distances in a source space and time instants.
 
     Parameters
     ----------
@@ -2424,7 +2841,8 @@ def spatio_temporal_dist_connectivity(src, n_times, dist, verbose=None):
         Maximal geodesic distance (in m) between vertices in the
         source space to consider neighbors.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -2450,18 +2868,20 @@ def spatio_temporal_dist_connectivity(src, n_times, dist, verbose=None):
 
 @verbose
 def spatial_src_connectivity(src, dist=None, verbose=None):
-    """Compute connectivity for a source space activation
+    """Compute connectivity for a source space activation.
 
     Parameters
     ----------
     src : instance of SourceSpaces
-        The source space.
+        The source space. It can be a surface source space or a
+        volume source space.
     dist : float, or None
         Maximal geodesic distance (in m) between vertices in the
         source space to consider neighbors. If None, immediate neighbors
         are extracted from an ico surface.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -2473,7 +2893,7 @@ def spatial_src_connectivity(src, dist=None, verbose=None):
 
 @verbose
 def spatial_tris_connectivity(tris, remap_vertices=False, verbose=None):
-    """Compute connectivity from triangles
+    """Compute connectivity from triangles.
 
     Parameters
     ----------
@@ -2483,7 +2903,8 @@ def spatial_tris_connectivity(tris, remap_vertices=False, verbose=None):
         Reassign vertex indices based on unique values. Useful
         to process a subset of triangles. Defaults to False.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -2494,7 +2915,7 @@ def spatial_tris_connectivity(tris, remap_vertices=False, verbose=None):
 
 
 def spatial_dist_connectivity(src, dist, verbose=None):
-    """Compute connectivity from distances in a source space
+    """Compute connectivity from distances in a source space.
 
     Parameters
     ----------
@@ -2506,7 +2927,8 @@ def spatial_dist_connectivity(src, dist, verbose=None):
         Maximal geodesic distance (in m) between vertices in the
         source space to consider neighbors.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -2517,7 +2939,7 @@ def spatial_dist_connectivity(src, dist, verbose=None):
 
 
 def spatial_inter_hemi_connectivity(src, dist, verbose=None):
-    """Get vertices on each hemisphere that are close to the other hemisphere
+    """Get vertices on each hemisphere that are close to the other hemisphere.
 
     Parameters
     ----------
@@ -2527,7 +2949,8 @@ def spatial_inter_hemi_connectivity(src, dist, verbose=None):
         Maximal Euclidean distance (in m) between vertices in one hemisphere
         compared to the other to consider neighbors.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -2550,7 +2973,7 @@ def spatial_inter_hemi_connectivity(src, dist, verbose=None):
 
 @verbose
 def _get_connectivity_from_edges(edges, n_times, verbose=None):
-    """Given edges sparse matrix, create connectivity matrix"""
+    """Given edges sparse matrix, create connectivity matrix."""
     n_vertices = edges.shape[0]
     logger.info("-- number of connected vertices : %d" % n_vertices)
     nnz = edges.col.size
@@ -2582,7 +3005,7 @@ def _get_ico_tris(grade, verbose=None, return_surf=False):
 
 
 def save_stc_as_volume(fname, stc, src, dest='mri', mri_resolution=False):
-    """Save a volume source estimate in a nifti file
+    """Save a volume source estimate in a NIfTI file.
 
     Parameters
     ----------
@@ -2660,14 +3083,12 @@ def save_stc_as_volume(fname, stc, src, dest='mri', mri_resolution=False):
 
 
 def _get_label_flip(labels, label_vertidx, src):
-    """Helper function to get sign-flip for labels"""
+    """Get sign-flip for labels."""
     # do the import here to avoid circular dependency
     from .label import label_sign_flip
     # get the sign-flip vector for every label
     label_flip = list()
     for label, vertidx in zip(labels, label_vertidx):
-        if label.hemi == 'both':
-            raise ValueError('BiHemiLabel not supported when using sign-flip')
         if vertidx is not None:
             flip = label_sign_flip(label, src)[:, None]
         else:
@@ -2680,9 +3101,23 @@ def _get_label_flip(labels, label_vertidx, src):
 @verbose
 def _gen_extract_label_time_course(stcs, labels, src, mode='mean',
                                    allow_empty=False, verbose=None):
-    """Generator for extract_label_time_course"""
-
-    n_labels = len(labels)
+    """Generate extract_label_time_course."""
+    # if src is a mixed src space, the first 2 src spaces are surf type and
+    # the other ones are vol type. For mixed source space n_labels will be the
+    # given by the number of ROIs of the cortical parcellation plus the number
+    # of vol src space
+
+    if len(src) > 2:
+        if src[0]['type'] != 'surf' or src[1]['type'] != 'surf':
+            raise ValueError('The first 2 source spaces have to be surf type')
+        if any(np.any(s['type'] != 'vol') for s in src[2:]):
+            raise ValueError('source spaces have to be of vol type')
+
+        n_aparc = len(labels)
+        n_aseg = len(src[2:])
+        n_labels = n_aparc + n_aseg
+    else:
+        n_labels = len(labels)
 
     # get vertices from source space, they have to be the same as in the stcs
     vertno = [s['vertno'] for s in src]
@@ -2726,10 +3161,10 @@ def _gen_extract_label_time_course(stcs, labels, src, mode='mean',
         pass  # we have this here to catch invalid values for mode
     elif mode == 'mean_flip':
         # get the sign-flip vector for every label
-        label_flip = _get_label_flip(labels, label_vertidx, src)
+        label_flip = _get_label_flip(labels, label_vertidx, src[:2])
     elif mode == 'pca_flip':
         # get the sign-flip vector for every label
-        label_flip = _get_label_flip(labels, label_vertidx, src)
+        label_flip = _get_label_flip(labels, label_vertidx, src[:2])
     elif mode == 'max':
         pass  # we calculate the maximum value later
     else:
@@ -2738,11 +3173,20 @@ def _gen_extract_label_time_course(stcs, labels, src, mode='mean',
     # loop through source estimates and extract time series
     for stc in stcs:
         # make sure the stc is compatible with the source space
-        if len(stc.vertices[0]) != nvert[0] or \
-                len(stc.vertices[1]) != nvert[1]:
-            raise ValueError('stc not compatible with source space')
+        for i in range(len(src)):
+            if len(stc.vertices[i]) != nvert[i]:
+                raise ValueError('stc not compatible with source space. '
+                                 'stc has %s time series but there are %s '
+                                 'vertices in source space'
+                                 % (len(stc.vertices[i]), nvert[i]))
+
         if any(np.any(svn != vn) for svn, vn in zip(stc.vertices, vertno)):
             raise ValueError('stc not compatible with source space')
+        if sum(nvert) != stc.shape[0]:
+            raise ValueError('stc not compatible with source space. '
+                             'stc has %s vertices but the source space '
+                             'has %s vertices'
+                             % (stc.shape[0], sum(nvert)))
 
         logger.info('Extracting time courses for %d labels (mode: %s)'
                     % (n_labels, mode))
@@ -2779,6 +3223,18 @@ def _gen_extract_label_time_course(stcs, labels, src, mode='mean',
         else:
             raise ValueError('%s is an invalid mode' % mode)
 
+        # extract label time series for the vol src space
+        if len(src) > 2:
+            v1 = nvert[0] + nvert[1]
+            for i, nv in enumerate(nvert[2:]):
+
+                v2 = v1 + nv
+                v = range(v1, v2)
+                if nv != 0:
+                    label_tc[n_aparc + i] = np.mean(stc.data[v, :], axis=0)
+
+                v1 = v2
+
         # this is a generator!
         yield label_tc
 
@@ -2787,7 +3243,7 @@ def _gen_extract_label_time_course(stcs, labels, src, mode='mean',
 def extract_label_time_course(stcs, labels, src, mode='mean_flip',
                               allow_empty=False, return_generator=False,
                               verbose=None):
-    """Extract label time course for lists of labels and source estimates
+    """Extract label time course for lists of labels and source estimates.
 
     This function will extract one time course for each label and source
     estimate. The way the time courses are extracted depends on the mode
@@ -2815,7 +3271,7 @@ def extract_label_time_course(stcs, labels, src, mode='mean_flip',
     ----------
     stcs : SourceEstimate | list (or generator) of SourceEstimate
         The source estimates from which to extract the time course.
-    labels : Label | list of Label
+    labels : Label | BiHemiLabel | list of Label or BiHemiLabel
         The labels for which to extract the time course.
     src : list
         Source spaces for left and right hemisphere.
@@ -2827,13 +3283,14 @@ def extract_label_time_course(stcs, labels, src, mode='mean_flip',
     return_generator : bool
         If True, a generator instead of a list is returned.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
     label_tc : array | list (or generator) of array, shape=(len(labels), n_times)
         Extracted time course for each label and source estimate.
-    """  # noqa
+    """  # noqa: E501
     # convert inputs to lists
     if isinstance(stcs, SourceEstimate):
         stcs = [stcs]
diff --git a/mne/source_space.py b/mne/source_space.py
index 440f536..98d407f 100644
--- a/mne/source_space.py
+++ b/mne/source_space.py
@@ -13,6 +13,7 @@ import numpy as np
 from scipy import sparse, linalg
 
 from .io.constants import FIFF
+from .io.meas_info import create_info
 from .io.tree import dir_tree_find
 from .io.tag import find_tag, read_tag
 from .io.open import fiff_open
@@ -23,13 +24,12 @@ from .io.write import (start_block, end_block, write_int,
 from .bem import read_bem_surfaces
 from .surface import (read_surface, _create_surf_spacing, _get_ico_surface,
                       _tessellate_sphere_surf, _get_surf_neighbors,
-                      _read_surface_geom, _normalize_vectors,
-                      _complete_surface_info, _compute_nearest,
-                      fast_cross_3d, _fast_cross_nd_sum, mesh_dist,
-                      _triangle_neighbors)
+                      _normalize_vectors, _get_solids, _triangle_neighbors,
+                      complete_surface_info, _compute_nearest, fast_cross_3d,
+                      mesh_dist)
 from .utils import (get_subjects_dir, run_subprocess, has_freesurfer,
                     has_nibabel, check_fname, logger, verbose,
-                    check_version, _get_call_line, warn)
+                    check_version, _get_call_line, warn, _check_fname)
 from .parallel import parallel_func, check_n_jobs
 from .transforms import (invert_transform, apply_trans, _print_coord_trans,
                          combine_transforms, _get_trans,
@@ -38,15 +38,16 @@ from .externals.six import string_types
 
 
 def _get_lut():
-    """Helper to get the FreeSurfer LUT"""
+    """Get the FreeSurfer LUT."""
     data_dir = op.join(op.dirname(__file__), 'data')
     lut_fname = op.join(data_dir, 'FreeSurferColorLUT.txt')
     return np.genfromtxt(lut_fname, dtype=None,
-                         usecols=(0, 1), names=['id', 'name'])
+                         usecols=(0, 1, 2, 3, 4, 5),
+                         names=['id', 'name', 'R', 'G', 'B', 'A'])
 
 
 def _get_lut_id(lut, label, use_lut):
-    """Helper to convert a label to a LUT ID number"""
+    """Convert a label to a LUT ID number."""
     if not use_lut:
         return 1
     assert isinstance(label, string_types)
@@ -63,7 +64,7 @@ _src_kind_dict = {
 
 
 class SourceSpaces(list):
-    """Represent a list of source space
+    """Represent a list of source space.
 
     Currently implemented as a list of dictionaries containing the source
     space information
@@ -82,14 +83,109 @@ class SourceSpaces(list):
         Dictionary with information about the creation of the source space
         file. Has keys 'working_dir' and 'command_line'.
     """
-    def __init__(self, source_spaces, info=None):
+
+    def __init__(self, source_spaces, info=None):  # noqa: D102
         super(SourceSpaces, self).__init__(source_spaces)
         if info is None:
             self.info = dict()
         else:
             self.info = dict(info)
 
-    def __repr__(self):
+    @verbose
+    def plot(self, head=False, brain=None, skull=None, subjects_dir=None,
+             trans=None, verbose=None):
+        """Plot the source space.
+
+        Parameters
+        ----------
+        head : bool
+            If True, show head surface.
+        brain : bool | str
+            If True, show the brain surfaces. Can also be a str for
+            surface type (e.g., 'pial', same as True). Default is None,
+            which means 'white' for surface source spaces and False otherwise.
+        skull : bool | str | list of str | list of dict | None
+            Whether to plot skull surface. If string, common choices would be
+            'inner_skull', or 'outer_skull'. Can also be a list to plot
+            multiple skull surfaces. If a list of dicts, each dict must
+            contain the complete surface info (such as you get from
+            :func:`mne.make_bem_model`). True is an alias of 'outer_skull'.
+            The subjects bem and bem/flash folders are searched for the 'surf'
+            files. Defaults to None, which is False for surface source spaces,
+            and True otherwise.
+        subjects_dir : string, or None
+            Path to SUBJECTS_DIR if it is not set in the environment.
+        trans : str | 'auto' | dict | None
+            The full path to the head<->MRI transform ``*-trans.fif`` file
+            produced during coregistration. If trans is None, an identity
+            matrix is assumed. This is only needed when the source space is in
+            head coordinates.
+        verbose : bool, str, int, or None
+            If not None, override default verbose level (see
+            :func:`mne.verbose` and :ref:`Logging documentation <tut_logging>`
+            for more).
+
+        Returns
+        -------
+        fig : instance of mlab Figure
+            The figure.
+        """
+        from .viz import plot_alignment
+
+        surfaces = list()
+        bem = None
+
+        if brain is None:
+            brain = 'white' if any(ss['type'] == 'surf'
+                                   for ss in self) else False
+
+        if isinstance(brain, string_types):
+            surfaces.append(brain)
+        elif brain:
+            surfaces.append('brain')
+
+        if skull is None:
+            skull = False if self.kind == 'surface' else True
+
+        if isinstance(skull, string_types):
+            surfaces.append(skull)
+        elif skull is True:
+            surfaces.append('outer_skull')
+        elif skull is not False:  # list
+            if isinstance(skull[0], dict):  # bem
+                skull_map = {FIFF.FIFFV_BEM_SURF_ID_BRAIN: 'inner_skull',
+                             FIFF.FIFFV_BEM_SURF_ID_SKULL: 'outer_skull',
+                             FIFF.FIFFV_BEM_SURF_ID_HEAD: 'outer_skin'}
+                for this_skull in skull:
+                    surfaces.append(skull_map[this_skull['id']])
+                bem = skull
+            else:  # list of str
+                for surf in skull:
+                    surfaces.append(surf)
+
+        if head:
+            surfaces.append('head')
+
+        if self[0]['coord_frame'] == FIFF.FIFFV_COORD_HEAD:
+            coord_frame = 'head'
+            if trans is None:
+                raise ValueError('Source space is in head coordinates, but no '
+                                 'head<->MRI transform was given. Please '
+                                 'specify the full path to the appropriate '
+                                 '*-trans.fif file as the "trans" parameter.')
+        else:
+            coord_frame = 'mri'
+
+        info = create_info(0, 1000., 'eeg')
+
+        return plot_alignment(
+            info, trans=trans, subject=self[0]['subject_his_id'],
+            subjects_dir=subjects_dir, surfaces=surfaces,
+            coord_frame=coord_frame, meg=(), eeg=False, dig=False, ecog=False,
+            bem=bem, src=self
+        )
+
+    def __repr__(self):  # noqa: D105
         ss_repr = []
         for ss in self:
             ss_type = ss['type']
@@ -108,17 +204,18 @@ class SourceSpaces(list):
 
     @property
     def kind(self):
-        """The kind of source space (surface, volume, discrete)"""
+        """The kind of source space (surface, volume, discrete)."""
         ss_types = list(set([ss['type'] for ss in self]))
         if len(ss_types) != 1:
             return 'combined'
         return _src_kind_dict[ss_types[0]]
 
     def __add__(self, other):
+        """Combine source spaces."""
         return SourceSpaces(list.__add__(self, other))
 
     def copy(self):
-        """Make a copy of the source spaces
+        """Make a copy of the source spaces.
 
         Returns
         -------
@@ -128,21 +225,24 @@ class SourceSpaces(list):
         src = deepcopy(self)
         return src
 
-    def save(self, fname):
-        """Save the source spaces to a fif file
+    def save(self, fname, overwrite=False):
+        """Save the source spaces to a fif file.
 
         Parameters
         ----------
         fname : str
             File to write.
+        overwrite : bool
+            If True, the destination file (if it exists) will be overwritten.
+            If False (default), an error will be raised if the file exists.
         """
-        write_source_spaces(fname, self)
+        write_source_spaces(fname, self, overwrite)
 
     @verbose
     def export_volume(self, fname, include_surfaces=True,
                       include_discrete=True, dest='mri', trans=None,
                       mri_resolution=False, use_lut=True, verbose=None):
-        """Exports source spaces to nifti or mgz file
+        """Export source spaces to nifti or mgz file.
 
         Parameters
         ----------
@@ -171,13 +271,14 @@ class SourceSpaces(list):
             If True, assigns a numeric value to each source space that
             corresponds to a color on the freesurfer lookup table.
         verbose : bool, str, int, or None
-            If not None, override default verbose level (see mne.verbose).
+            If not None, override default verbose level (see
+            :func:`mne.verbose` and :ref:`Logging documentation <tut_logging>`
+            for more).
 
         Notes
         -----
         This method requires nibabel.
         """
-
         # import nibabel or raise error
         try:
             import nibabel as nib
@@ -403,7 +504,7 @@ class SourceSpaces(list):
 
 
 def _add_patch_info(s):
-    """Patch information in a source space
+    """Patch information in a source space.
 
     Generate the patch information from the 'nearest' vector in
     a source space. For vertex in the source space it provides
@@ -445,7 +546,7 @@ def _add_patch_info(s):
 @verbose
 def _read_source_spaces_from_tree(fid, tree, patch_stats=False,
                                   verbose=None):
-    """Read the source spaces from a FIF file
+    """Read the source spaces from a FIF file.
 
     Parameters
     ----------
@@ -456,7 +557,8 @@ def _read_source_spaces_from_tree(fid, tree, patch_stats=False,
     patch_stats : bool, optional (default False)
         Calculate and add cortical patch statistics to the surfaces.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -484,7 +586,7 @@ def _read_source_spaces_from_tree(fid, tree, patch_stats=False,
 
 @verbose
 def read_source_spaces(fname, patch_stats=False, verbose=None):
-    """Read the source spaces from a FIF file
+    """Read the source spaces from a FIF file.
 
     Parameters
     ----------
@@ -494,7 +596,8 @@ def read_source_spaces(fname, patch_stats=False, verbose=None):
     patch_stats : bool, optional (default False)
         Calculate and add cortical patch statistics to the surfaces.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -531,8 +634,7 @@ def read_source_spaces(fname, patch_stats=False, verbose=None):
 
 @verbose
 def _read_one_source_space(fid, this, verbose=None):
-    """Read one source space
-    """
+    """Read one source space."""
     FIFF_BEM_SURF_NTRI = 3104
     FIFF_BEM_SURF_TRIANGLES = 3106
 
@@ -651,7 +753,7 @@ def _read_one_source_space(fid, this, verbose=None):
     if tag is None:
         raise ValueError('Coordinate frame information not found')
 
-    res['coord_frame'] = tag.data
+    res['coord_frame'] = tag.data[0]
 
     #   Vertices, normals, and triangles
     tag = find_tag(fid, this, FIFF.FIFF_MNE_SOURCE_SPACE_POINTS)
@@ -743,7 +845,9 @@ def _read_one_source_space(fid, this, verbose=None):
         logger.info('    Distance information added...')
 
     tag = find_tag(fid, this, FIFF.FIFF_SUBJ_HIS_ID)
-    if tag is not None:
+    if tag is None:
+        res['subject_his_id'] = None
+    else:
         res['subject_his_id'] = tag.data
 
     return res
@@ -751,8 +855,7 @@ def _read_one_source_space(fid, this, verbose=None):
 
 @verbose
 def _complete_source_space_info(this, verbose=None):
-    """Add more info on surface
-    """
+    """Add more info on surface."""
     #   Main triangulation
     logger.info('    Completing triangulation info...')
     this['tri_area'] = np.zeros(this['ntri'])
@@ -761,9 +864,7 @@ def _complete_source_space_info(this, verbose=None):
     r3 = this['rr'][this['tris'][:, 2], :]
     this['tri_cent'] = (r1 + r2 + r3) / 3.0
     this['tri_nn'] = fast_cross_3d((r2 - r1), (r3 - r1))
-    size = np.sqrt(np.sum(this['tri_nn'] ** 2, axis=1))
-    this['tri_area'] = size / 2.0
-    this['tri_nn'] /= size[:, None]
+    this['tri_area'] = _normalize_vectors(this['tri_nn']) / 2.0
     logger.info('[done]')
 
     #   Selected triangles
@@ -774,13 +875,12 @@ def _complete_source_space_info(this, verbose=None):
         r3 = this['rr'][this['use_tris'][:, 2], :]
         this['use_tri_cent'] = (r1 + r2 + r3) / 3.0
         this['use_tri_nn'] = fast_cross_3d((r2 - r1), (r3 - r1))
-        this['use_tri_area'] = np.sqrt(np.sum(this['use_tri_nn'] ** 2, axis=1)
-                                       ) / 2.0
+        this['use_tri_area'] = np.linalg.norm(this['use_tri_nn'], axis=1) / 2.
     logger.info('[done]')
 
 
 def find_source_space_hemi(src):
-    """Return the hemisphere id for a source space
+    """Return the hemisphere id for a source space.
 
     Parameters
     ----------
@@ -803,7 +903,7 @@ def find_source_space_hemi(src):
 
 
 def label_src_vertno_sel(label, src):
-    """ Find vertex numbers and indices from label
+    """Find vertex numbers and indices from label.
 
     Parameters
     ----------
@@ -857,7 +957,7 @@ def _get_vertno(src):
 
 @verbose
 def _write_source_spaces_to_fid(fid, src, verbose=None):
-    """Write the source spaces to a FIF file
+    """Write the source spaces to a FIF file.
 
     Parameters
     ----------
@@ -866,7 +966,8 @@ def _write_source_spaces_to_fid(fid, src, verbose=None):
     src : list
         The list of source spaces.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
     """
     for s in src:
         logger.info('    Write a source space...')
@@ -878,8 +979,8 @@ def _write_source_spaces_to_fid(fid, src, verbose=None):
 
 
 @verbose
-def write_source_spaces(fname, src, verbose=None):
-    """Write source spaces to a file
+def write_source_spaces(fname, src, overwrite=False, verbose=None):
+    """Write source spaces to a file.
 
     Parameters
     ----------
@@ -888,14 +989,19 @@ def write_source_spaces(fname, src, verbose=None):
         -src.fif.gz.
     src : SourceSpaces
         The source spaces (as returned by read_source_spaces).
+    overwrite : bool
+        If True, the destination file (if it exists) will be overwritten.
+        If False (default), an error will be raised if the file exists.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     See Also
     --------
     read_source_spaces
     """
     check_fname(fname, 'source space', ('-src.fif', '-src.fif.gz'))
+    _check_fname(fname, overwrite=overwrite)
 
     fid = start_file(fname)
     start_block(fid, FIFF.FIFFB_MNE)
@@ -921,7 +1027,7 @@ def write_source_spaces(fname, src, verbose=None):
 
 
 def _write_one_source_space(fid, this, verbose=None):
-    """Write one source space"""
+    """Write one source space."""
     if this['type'] == 'surf':
         src_type = FIFF.FIFFV_MNE_SPACE_SURFACE
     elif this['type'] == 'vol':
@@ -1018,7 +1124,7 @@ def _write_one_source_space(fid, this, verbose=None):
 @verbose
 def vertex_to_mni(vertices, hemis, subject, subjects_dir=None, mode=None,
                   verbose=None):
-    """Convert the array of vertices for a hemisphere to MNI coordinates
+    """Convert the array of vertices for a hemisphere to MNI coordinates.
 
     Parameters
     ----------
@@ -1036,7 +1142,8 @@ def vertex_to_mni(vertices, hemis, subject, subjects_dir=None, mode=None,
         back to 'freesurfer' if it fails. Results should be equivalent with
         either option, but nibabel may be quicker (and more pythonic).
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -1077,7 +1184,7 @@ def vertex_to_mni(vertices, hemis, subject, subjects_dir=None, mode=None,
 
 @verbose
 def _read_talxfm(subject, subjects_dir, mode=None, verbose=None):
-    """Read MNI transform from FreeSurfer talairach.xfm file
+    """Read MNI transform from FreeSurfer talairach.xfm file.
 
     Adapted from freesurfer m-files. Altered to deal with Norig
     and Torig correctly.
@@ -1182,26 +1289,63 @@ def _read_talxfm(subject, subjects_dir, mode=None, verbose=None):
 # Creation and decimation
 
 @verbose
-def setup_source_space(subject, fname=True, spacing='oct6', surface='white',
-                       overwrite=False, subjects_dir=None, add_dist=True,
-                       n_jobs=1, verbose=None):
-    """Setup a bilater hemisphere surface-based source space with subsampling
+def _check_spacing(spacing, verbose=None):
+    """Check spacing parameter."""
+    # check to make sure our parameters are good, parse 'spacing'
+    space_err = ('"spacing" must be a string with values '
+                 '"ico#", "oct#", or "all", and "ico" and "oct"'
+                 'numbers must be integers')
+    if not isinstance(spacing, string_types) or len(spacing) < 3:
+        raise ValueError(space_err)
+    if spacing == 'all':
+        stype = 'all'
+        sval = ''
+    elif spacing[:3] == 'ico':
+        stype = 'ico'
+        sval = spacing[3:]
+    elif spacing[:3] == 'oct':
+        stype = 'oct'
+        sval = spacing[3:]
+    else:
+        raise ValueError(space_err)
+    try:
+        if stype in ['ico', 'oct']:
+            sval = int(sval)
+        elif stype == 'spacing':  # spacing
+            sval = float(sval)
+    except Exception:
+        raise ValueError(space_err)
+    if stype == 'all':
+        logger.info('Include all vertices')
+        ico_surf = None
+        src_type_str = 'all'
+    else:
+        src_type_str = '%s = %s' % (stype, sval)
+        if stype == 'ico':
+            logger.info('Icosahedron subdivision grade %s' % sval)
+            ico_surf = _get_ico_surface(sval)
+        elif stype == 'oct':
+            logger.info('Octahedron subdivision grade %s' % sval)
+            ico_surf = _tessellate_sphere_surf(sval)
+    return stype, sval, ico_surf, src_type_str
+
+
+ at verbose
+def setup_source_space(subject, spacing='oct6', surface='white',
+                       subjects_dir=None, add_dist=True, n_jobs=1,
+                       verbose=None):
+    """Set up bilateral hemisphere surface-based source space with subsampling.
 
     Parameters
     ----------
     subject : str
         Subject to process.
-    fname : str | None | bool
-        Filename to use. If True, a default name will be used. If None,
-        the source space will not be saved (only returned).
     spacing : str
         The spacing to use. Can be ``'ico#'`` for a recursively subdivided
         icosahedron, ``'oct#'`` for a recursively subdivided octahedron,
         or ``'all'`` for all points.
     surface : str
         The surface to use.
-    overwrite: bool
-        If True, overwrite output file (if it exists).
     subjects_dir : string, or None
         Path to SUBJECTS_DIR if it is not set in the environment.
     add_dist : bool
@@ -1211,7 +1355,8 @@ def setup_source_space(subject, fname=True, spacing='oct6', surface='white',
         Number of jobs to run in parallel. Will use at most 2 jobs
         (one for each hemisphere).
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -1222,92 +1367,42 @@ def setup_source_space(subject, fname=True, spacing='oct6', surface='white',
     --------
     setup_volume_source_space
     """
-    cmd = ('setup_source_space(%s, fname=%s, spacing=%s, surface=%s, '
-           'overwrite=%s, subjects_dir=%s, add_dist=%s, verbose=%s)'
-           % (subject, fname, spacing, surface, overwrite,
-              subjects_dir, add_dist, verbose))
-    # check to make sure our parameters are good, parse 'spacing'
-    space_err = ('"spacing" must be a string with values '
-                 '"ico#", "oct#", or "all", and "ico" and "oct"'
-                 'numbers must be integers')
-    if not isinstance(spacing, string_types) or len(spacing) < 3:
-        raise ValueError(space_err)
-    if spacing == 'all':
-        stype = 'all'
-        sval = ''
-    elif spacing[:3] == 'ico':
-        stype = 'ico'
-        sval = spacing[3:]
-    elif spacing[:3] == 'oct':
-        stype = 'oct'
-        sval = spacing[3:]
-    else:
-        raise ValueError(space_err)
-    try:
-        if stype in ['ico', 'oct']:
-            sval = int(sval)
-        elif stype == 'spacing':  # spacing
-            sval = float(sval)
-    except:
-        raise ValueError(space_err)
-    subjects_dir = get_subjects_dir(subjects_dir)
+    cmd = ('setup_source_space(%s, spacing=%s, surface=%s, '
+           'subjects_dir=%s, add_dist=%s, verbose=%s)'
+           % (subject, spacing, surface, subjects_dir, add_dist, verbose))
+
+    subjects_dir = get_subjects_dir(subjects_dir, raise_error=True)
     surfs = [op.join(subjects_dir, subject, 'surf', hemi + surface)
              for hemi in ['lh.', 'rh.']]
-    bem_dir = op.join(subjects_dir, subject, 'bem')
-
     for surf, hemi in zip(surfs, ['LH', 'RH']):
         if surf is not None and not op.isfile(surf):
             raise IOError('Could not find the %s surface %s'
                           % (hemi, surf))
 
-    if not (fname is True or fname is None or isinstance(fname, string_types)):
-        raise ValueError('"fname" must be a string, True, or None')
-    if fname is True:
-        extra = '%s-%s' % (stype, sval) if sval != '' else stype
-        fname = op.join(bem_dir, '%s-%s-src.fif' % (subject, extra))
-    if fname is not None and op.isfile(fname) and overwrite is False:
-        raise IOError('file "%s" exists, use overwrite=True if you want '
-                      'to overwrite the file' % fname)
-
     logger.info('Setting up the source space with the following parameters:\n')
     logger.info('SUBJECTS_DIR = %s' % subjects_dir)
     logger.info('Subject      = %s' % subject)
     logger.info('Surface      = %s' % surface)
-    if stype == 'ico':
-        src_type_str = 'ico = %s' % sval
-        logger.info('Icosahedron subdivision grade %s\n' % sval)
-    elif stype == 'oct':
-        src_type_str = 'oct = %s' % sval
-        logger.info('Octahedron subdivision grade %s\n' % sval)
-    else:
-        src_type_str = 'all'
-        logger.info('Include all vertices\n')
+    stype, sval, ico_surf, src_type_str = _check_spacing(spacing)
+    logger.info('')
+    del spacing
 
-    # Create the fif file
-    if fname is not None:
-        logger.info('>>> 1. Creating the source space file %s...' % fname)
-    else:
-        logger.info('>>> 1. Creating the source space...\n')
+    logger.info('>>> 1. Creating the source space...\n')
 
     # mne_make_source_space ... actually make the source spaces
     src = []
 
     # pre-load ico/oct surf (once) for speed, if necessary
-    if stype in ['ico', 'oct']:
-        # ### from mne_ico_downsample.c ###
-        if stype == 'ico':
-            logger.info('Doing the icosahedral vertex picking...')
-            ico_surf = _get_ico_surface(sval)
-        else:
-            logger.info('Doing the octahedral vertex picking...')
-            ico_surf = _tessellate_sphere_surf(sval)
-    else:
-        ico_surf = None
-
+    if stype != 'all':
+        logger.info('Doing the %shedral vertex picking...'
+                    % (dict(ico='icosa', oct='octa')[stype],))
     for hemi, surf in zip(['lh', 'rh'], surfs):
         logger.info('Loading %s...' % surf)
         # Setup the surface spacing in the MRI coord frame
-        s = _create_surf_spacing(surf, hemi, subject, stype, sval, ico_surf,
+        if stype != 'all':
+            logger.info('Mapping %s %s -> %s (%d) ...'
+                        % (hemi, subject, stype, sval))
+        s = _create_surf_spacing(surf, hemi, subject, stype, ico_surf,
                                  subjects_dir)
         logger.info('loaded %s %d/%d selected to source space (%s)'
                     % (op.split(surf)[1], s['nuse'], s['np'], src_type_str))
@@ -1320,7 +1415,7 @@ def setup_source_space(subject, fname=True, spacing='oct6', surface='white',
         # Add missing fields
         s.update(dict(dist=None, dist_limit=None, nearest=None, type='surf',
                       nearest_dist=None, pinfo=None, patch_inds=None, id=s_id,
-                      coord_frame=np.array((FIFF.FIFFV_COORD_MRI,), np.int32)))
+                      coord_frame=FIFF.FIFFV_COORD_MRI))
         s['rr'] /= 1000.0
         del s['tri_area']
         del s['tri_cent']
@@ -1334,29 +1429,23 @@ def setup_source_space(subject, fname=True, spacing='oct6', surface='white',
         add_source_space_distances(src, n_jobs=n_jobs, verbose=verbose)
 
     # write out if requested, then return the data
-    if fname is not None:
-        write_source_spaces(fname, src)
-        logger.info('Wrote %s' % fname)
     logger.info('You are now one step closer to computing the gain matrix')
     return src
 
 
 @verbose
-def setup_volume_source_space(subject, fname=None, pos=5.0, mri=None,
+def setup_volume_source_space(subject=None, pos=5.0, mri=None,
                               sphere=(0.0, 0.0, 0.0, 90.0), bem=None,
                               surface=None, mindist=5.0, exclude=0.0,
-                              overwrite=False, subjects_dir=None,
-                              volume_label=None, add_interpolator=True,
-                              verbose=None):
-    """Setup a volume source space with grid spacing or discrete source space
+                              subjects_dir=None, volume_label=None,
+                              add_interpolator=True, verbose=None):
+    """Set up a volume source space with grid spacing or discrete source space.
 
     Parameters
     ----------
-    subject : str
-        Subject to process.
-    fname : str | None
-        Filename to use. If None, the source space will not be saved
-        (only returned).
+    subject : str | None
+        Subject to process. If None, the path to the mri volume must be
+        absolute. Defaults to None.
     pos : float | dict
         Positions to use for sources. If float, a grid will be constructed
         with the spacing given by `pos` in mm, generating a volume source
@@ -1385,17 +1474,16 @@ def setup_volume_source_space(subject, fname=None, pos=5.0, mri=None,
     exclude : float
         Exclude points closer than this distance (mm) from the center of mass
         of the bounding surface.
-    overwrite: bool
-        If True, overwrite output file (if it exists).
     subjects_dir : string, or None
         Path to SUBJECTS_DIR if it is not set in the environment.
-    volume_label : str | None
+    volume_label : str | list | None
         Region of interest corresponding with freesurfer lookup table.
     add_interpolator : bool
         If True and ``mri`` is not None, then an interpolation matrix
         will be produced.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -1417,7 +1505,6 @@ def setup_volume_source_space(subject, fname=None, pos=5.0, mri=None,
     must be provided, and 'mri' must refer to a .mgh or .mgz file with values
     corresponding to the freesurfer lookup-table (typically aseg.mgz).
     """
-
     subjects_dir = get_subjects_dir(subjects_dir)
 
     if bem is not None and surface is not None:
@@ -1425,7 +1512,11 @@ def setup_volume_source_space(subject, fname=None, pos=5.0, mri=None,
                          'specified')
     if mri is not None:
         if not op.isfile(mri):
-            raise IOError('mri file "%s" not found' % mri)
+            if subject is None:
+                raise IOError('mri file "%s" not found' % mri)
+            mri = op.join(subjects_dir, subject, 'mri', mri)
+            if not op.isfile(mri):
+                raise IOError('mri file "%s" not found' % mri)
         if isinstance(pos, dict):
             raise ValueError('Cannot create interpolation matrix for '
                              'discrete source space, mri must be None if '
@@ -1435,11 +1526,17 @@ def setup_volume_source_space(subject, fname=None, pos=5.0, mri=None,
         if mri is None:
             raise RuntimeError('"mri" must be provided if "volume_label" is '
                                'not None')
+        if not isinstance(volume_label, list):
+                volume_label = [volume_label]
+
         # Check that volume label is found in .mgz file
         volume_labels = get_volume_labels_from_aseg(mri)
-        if volume_label not in volume_labels:
-            raise ValueError('Volume %s not found in file %s. Double check '
-                             'freesurfer lookup table.' % (volume_label, mri))
+
+        for label in volume_label:
+            if label not in volume_labels:
+                raise ValueError('Volume %s not found in file %s. Double '
+                                 'check  freesurfer lookup table.'
+                                 % (label, mri))
 
     sphere = np.asarray(sphere)
     if sphere.size != 4:
@@ -1454,7 +1551,7 @@ def setup_volume_source_space(subject, fname=None, pos=5.0, mri=None,
                 raise KeyError('surface, if dict, must have entries "rr" '
                                'and "tris"')
             # let's make sure we have geom info
-            surface = _read_surface_geom(surface, verbose=False)
+            complete_surface_info(surface, copy=False, verbose=False)
             surf_extra = 'dict()'
         elif isinstance(surface, string_types):
             if not op.isfile(surface):
@@ -1482,7 +1579,6 @@ def setup_volume_source_space(subject, fname=None, pos=5.0, mri=None,
         logger.info('Assuming input in millimeters')
         logger.info('Assuming input in MRI coordinates')
 
-    logger.info('Output file           : %s', fname)
     if isinstance(pos, float):
         logger.info('grid                  : %.1f mm' % pos)
         logger.info('mindist               : %.1f mm' % mindist)
@@ -1509,7 +1605,7 @@ def setup_volume_source_space(subject, fname=None, pos=5.0, mri=None,
         elif surface is not None:
             if isinstance(surface, string_types):
                 # read the surface in the MRI coordinate frame
-                surf = _read_surface_geom(surface)
+                surf = read_surface(surface, return_dict=True)[-1]
             else:
                 surf = surface
             logger.info('Loaded bounding surface from %s (%d nodes)'
@@ -1518,43 +1614,45 @@ def setup_volume_source_space(subject, fname=None, pos=5.0, mri=None,
             surf['rr'] *= 1e-3  # must be converted to meters
         else:  # Load an icosahedron and use that as the surface
             logger.info('Setting up the sphere...')
-            surf = _get_ico_surface(3)
-
-            # Scale and shift
-
-            # center at origin and make radius 1
-            _normalize_vectors(surf['rr'])
-
-            # normalize to sphere (in MRI coord frame)
-            surf['rr'] *= sphere[3] / 1000.0  # scale by radius
-            surf['rr'] += sphere[:3] / 1000.0  # move by center
-            _complete_surface_info(surf, True)
+            surf = dict(R=sphere[3] / 1000., r0=sphere[:3] / 1000.)
         # Make the grid of sources in MRI space
-        sp = _make_volume_source_space(surf, pos, exclude, mindist, mri,
-                                       volume_label)
+        if volume_label is not None:
+            sp = []
+            for label in volume_label:
+                vol_sp = _make_volume_source_space(surf, pos, exclude, mindist,
+                                                   mri, label)
+                sp.append(vol_sp)
+        else:
+            sp = _make_volume_source_space(surf, pos, exclude, mindist, mri,
+                                           volume_label)
 
     # Compute an interpolation matrix to show data in MRI_VOXEL coord frame
+    if not isinstance(sp, list):
+        sp = [sp]
+
     if mri is not None:
-        _add_interpolator(sp, mri, add_interpolator)
-    elif sp['type'] == 'vol':
+        for s in sp:
+            _add_interpolator(s, mri, add_interpolator)
+    elif sp[0]['type'] == 'vol':
         # If there is no interpolator, it's actually a discrete source space
-        sp['type'] = 'discrete'
+        sp[0]['type'] = 'discrete'
 
-    if 'vol_dims' in sp:
-        del sp['vol_dims']
+    for s in sp:
+        if 'vol_dims' in s:
+            del s['vol_dims']
 
     # Save it
-    sp.update(dict(nearest=None, dist=None, use_tris=None, patch_inds=None,
-                   dist_limit=None, pinfo=None, ntri=0, nearest_dist=None,
-                   nuse_tri=0, tris=None))
-    sp = SourceSpaces([sp], dict(working_dir=os.getcwd(), command_line='None'))
-    if fname is not None:
-        write_source_spaces(fname, sp, verbose=False)
+    for s in sp:
+        s.update(dict(nearest=None, dist=None, use_tris=None, patch_inds=None,
+                      dist_limit=None, pinfo=None, ntri=0, nearest_dist=None,
+                      nuse_tri=0, tris=None, subject_his_id=subject))
+
+    sp = SourceSpaces(sp, dict(working_dir=os.getcwd(), command_line='None'))
     return sp
 
 
 def _make_voxel_ras_trans(move, ras, voxel_size):
-    """Make a transformation from MRI_VOXEL to MRI surface RAS (i.e. MRI)"""
+    """Make a transformation from MRI_VOXEL to MRI surface RAS (i.e. MRI)."""
     assert voxel_size.ndim == 1
     assert voxel_size.size == 3
     rot = ras.T * voxel_size[np.newaxis, :]
@@ -1567,7 +1665,7 @@ def _make_voxel_ras_trans(move, ras, voxel_size):
 
 
 def _make_discrete_source_space(pos, coord_frame='mri'):
-    """Use a discrete set of source locs/oris to make src space
+    """Use a discrete set of source locs/oris to make src space.
 
     Parameters
     ----------
@@ -1588,9 +1686,9 @@ def _make_discrete_source_space(pos, coord_frame='mri'):
                        % (list(_str_to_frame.keys()), coord_frame))
     coord_frame = _str_to_frame[coord_frame]  # now an int
 
-    # process points
-    rr = pos['rr'].copy()
-    nn = pos['nn'].copy()
+    # process points (copy and cast)
+    rr = np.array(pos['rr'], float)
+    nn = np.array(pos['nn'], float)
     if not (rr.ndim == nn.ndim == 2 and nn.shape[0] == nn.shape[0] and
             rr.shape[1] == nn.shape[1]):
         raise RuntimeError('"rr" and "nn" must both be 2D arrays with '
@@ -1612,15 +1710,20 @@ def _make_discrete_source_space(pos, coord_frame='mri'):
 
 def _make_volume_source_space(surf, grid, exclude, mindist, mri=None,
                               volume_label=None, do_neighbors=True, n_jobs=1):
-    """Make a source space which covers the volume bounded by surf"""
-
+    """Make a source space which covers the volume bounded by surf."""
     # Figure out the grid size in the MRI coordinate frame
-    mins = np.min(surf['rr'], axis=0)
-    maxs = np.max(surf['rr'], axis=0)
-    cm = np.mean(surf['rr'], axis=0)  # center of mass
+    if 'rr' in surf:
+        mins = np.min(surf['rr'], axis=0)
+        maxs = np.max(surf['rr'], axis=0)
+        cm = np.mean(surf['rr'], axis=0)  # center of mass
+        maxdist = np.linalg.norm(surf['rr'] - cm, axis=1).max()
+    else:
+        mins = surf['r0'] - surf['R']
+        maxs = surf['r0'] + surf['R']
+        cm = surf['r0'].copy()
+        maxdist = surf['R']
 
     # Define the sphere which fits the surface
-    maxdist = np.sqrt(np.max(np.sum((surf['rr'] - cm) ** 2, axis=1)))
 
     logger.info('Surface CM = (%6.1f %6.1f %6.1f) mm'
                 % (1000 * cm[0], 1000 * cm[1], 1000 * cm[2]))
@@ -1659,13 +1762,23 @@ def _make_volume_source_space(surf, grid, exclude, mindist, mri=None,
     logger.info('%d sources before omitting any.', sp['nuse'])
 
     # Exclude infeasible points
-    dists = np.sqrt(np.sum((sp['rr'] - cm) ** 2, axis=1))
+    dists = np.linalg.norm(sp['rr'] - cm, axis=1)
     bads = np.where(np.logical_or(dists < exclude, dists > maxdist))[0]
     sp['inuse'][bads] = False
     sp['nuse'] -= len(bads)
     logger.info('%d sources after omitting infeasible sources.', sp['nuse'])
 
-    _filter_source_spaces(surf, mindist, None, [sp], n_jobs)
+    if 'rr' in surf:
+        _filter_source_spaces(surf, mindist, None, [sp], n_jobs)
+    else:  # sphere
+        vertno = np.where(sp['inuse'])[0]
+        bads = (np.linalg.norm(sp['rr'][vertno] - surf['r0'], axis=-1) >=
+                surf['R'] - mindist / 1000.)
+        sp['nuse'] -= bads.sum()
+        sp['inuse'][vertno[bads]] = False
+        sp['vertno'] = np.where(sp['inuse'])[0]
+        del vertno
+    del surf
     logger.info('%d sources remaining after excluding the sources outside '
                 'the surface and less than %6.1f mm inside.'
                 % (sp['nuse'], mindist))
@@ -1780,7 +1893,7 @@ def _make_volume_source_space(surf, grid, exclude, mindist, mri=None,
         # Filter out points too far from volume region voxels
         dists = _compute_nearest(rr_voi, sp['rr'], return_dists=True)[1]
         # Maximum distance from center of mass of a voxel to any of its corners
-        maxdist = np.sqrt(((trans[:3, :3].sum(0) / 2.) ** 2).sum())
+        maxdist = np.linalg.norm(trans[:3, :3].sum(0) / 2.)
         bads = np.where(dists > maxdist)[0]
 
         # Update source info
@@ -1827,7 +1940,7 @@ def _vol_vertex(width, height, jj, kk, pp):
 
 
 def _get_mri_header(fname):
-    """Get MRI header using nibabel"""
+    """Get MRI header using nibabel."""
     import nibabel as nib
     img = nib.load(fname)
     try:
@@ -1837,7 +1950,7 @@ def _get_mri_header(fname):
 
 
 def _get_mgz_header(fname):
-    """Adapted from nibabel to quickly extract header info"""
+    """Adapted from nibabel to quickly extract header info."""
     if not fname.endswith('.mgz'):
         raise IOError('Filename must end with .mgz')
     header_dtd = [('version', '>i4'), ('dims', '>i4', (4,)),
@@ -1874,7 +1987,7 @@ def _get_mgz_header(fname):
 
 
 def _add_interpolator(s, mri_name, add_interpolator):
-    """Compute a sparse matrix to interpolate the data into an MRI volume"""
+    """Compute a sparse matrix to interpolate the data into an MRI volume."""
     # extract transformation information from mri
     logger.info('Reading %s...' % mri_name)
     header = _get_mgz_header(mri_name)
@@ -2002,7 +2115,7 @@ def _add_interpolator(s, mri_name, add_interpolator):
 @verbose
 def _filter_source_spaces(surf, limit, mri_head_t, src, n_jobs=1,
                           verbose=None):
-    """Remove all source space points closer than a given limit (in mm)"""
+    """Remove all source space points closer than a given limit (in mm)."""
     if src[0]['coord_frame'] == FIFF.FIFFV_COORD_HEAD and mri_head_t is None:
         raise RuntimeError('Source spaces are in head coordinates and no '
                            'coordinate transform was provided!')
@@ -2058,19 +2171,26 @@ def _filter_source_spaces(surf, limit, mri_head_t, src, n_jobs=1,
             logger.info('%d source space point%s omitted because of the '
                         '%6.1f-mm distance limit.' % tuple(extras))
         # Adjust the patch inds as well if necessary
-        if omit + omit_outside > 0 and s.get('patch_inds') is not None:
-            if s['nearest'] is None:
-                # This shouldn't happen, but if it does, we can probably come
-                # up with a more clever solution
-                raise RuntimeError('Cannot adjust patch information properly, '
-                                   'please contact the mne-python developers')
-            _add_patch_info(s)
+        if omit + omit_outside > 0:
+            _adjust_patch_info(s)
     logger.info('Thank you for waiting.')
 
 
 @verbose
+def _adjust_patch_info(s, verbose=None):
+    """Adjust patch information in place after vertex omission."""
+    if s.get('patch_inds') is not None:
+        if s['nearest'] is None:
+            # This shouldn't happen, but if it does, we can probably come
+            # up with a more clever solution
+            raise RuntimeError('Cannot adjust patch information properly, '
+                               'please contact the mne-python developers')
+        _add_patch_info(s)
+
+
+ at verbose
 def _points_outside_surface(rr, surf, n_jobs=1, verbose=None):
-    """Check whether points are outside a surface
+    """Check whether points are outside a surface.
 
     Parameters
     ----------
@@ -2086,52 +2206,16 @@ def _points_outside_surface(rr, surf, n_jobs=1, verbose=None):
     """
     rr = np.atleast_2d(rr)
     assert rr.shape[1] == 3
+    assert n_jobs > 0
     parallel, p_fun, _ = parallel_func(_get_solids, n_jobs)
     tot_angles = parallel(p_fun(surf['rr'][tris], rr)
                           for tris in np.array_split(surf['tris'], n_jobs))
     return np.abs(np.sum(tot_angles, axis=0) / (2 * np.pi) - 1.0) > 1e-5
 
 
-def _get_solids(tri_rrs, fros):
-    """Helper for computing _sum_solids_div total angle in chunks"""
-    # NOTE: This incorporates the division by 4PI that used to be separate
-    # for tri_rr in tri_rrs:
-    #     v1 = fros - tri_rr[0]
-    #     v2 = fros - tri_rr[1]
-    #     v3 = fros - tri_rr[2]
-    #     triple = np.sum(fast_cross_3d(v1, v2) * v3, axis=1)
-    #     l1 = np.sqrt(np.sum(v1 * v1, axis=1))
-    #     l2 = np.sqrt(np.sum(v2 * v2, axis=1))
-    #     l3 = np.sqrt(np.sum(v3 * v3, axis=1))
-    #     s = (l1 * l2 * l3 +
-    #          np.sum(v1 * v2, axis=1) * l3 +
-    #          np.sum(v1 * v3, axis=1) * l2 +
-    #          np.sum(v2 * v3, axis=1) * l1)
-    #     tot_angle -= np.arctan2(triple, s)
-
-    # This is the vectorized version, but with a slicing heuristic to
-    # prevent memory explosion
-    tot_angle = np.zeros((len(fros)))
-    slices = np.r_[np.arange(0, len(fros), 100), [len(fros)]]
-    for i1, i2 in zip(slices[:-1], slices[1:]):
-        v1 = fros[i1:i2] - tri_rrs[:, 0, :][:, np.newaxis]
-        v2 = fros[i1:i2] - tri_rrs[:, 1, :][:, np.newaxis]
-        v3 = fros[i1:i2] - tri_rrs[:, 2, :][:, np.newaxis]
-        triples = _fast_cross_nd_sum(v1, v2, v3)
-        l1 = np.sqrt(np.sum(v1 * v1, axis=2))
-        l2 = np.sqrt(np.sum(v2 * v2, axis=2))
-        l3 = np.sqrt(np.sum(v3 * v3, axis=2))
-        ss = (l1 * l2 * l3 +
-              np.sum(v1 * v2, axis=2) * l3 +
-              np.sum(v1 * v3, axis=2) * l2 +
-              np.sum(v2 * v3, axis=2) * l1)
-        tot_angle[i1:i2] = -np.sum(np.arctan2(triples, ss), axis=0)
-    return tot_angle
-
-
 @verbose
 def _ensure_src(src, kind=None, verbose=None):
-    """Helper to ensure we have a source space"""
+    """Ensure we have a source space."""
     if isinstance(src, string_types):
         if not op.isfile(src):
             raise IOError('Source space file "%s" not found' % src)
@@ -2165,7 +2249,7 @@ def _ensure_src_subject(src, subject):
 
 @verbose
 def add_source_space_distances(src, dist_limit=np.inf, n_jobs=1, verbose=None):
-    """Compute inter-source distances along the cortical surface
+    """Compute inter-source distances along the cortical surface.
 
     This function will also try to add patch info for the source space.
     It will only occur if the ``dist_limit`` is sufficiently high that all
@@ -2184,7 +2268,8 @@ def add_source_space_distances(src, dist_limit=np.inf, n_jobs=1, verbose=None):
         Number of jobs to run in parallel. Will only use (up to) as many
         cores as there are source spaces.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -2206,6 +2291,7 @@ def add_source_space_distances(src, dist_limit=np.inf, n_jobs=1, verbose=None):
     the source space to disk, as the computed distances will automatically be
     stored along with the source space data for future use.
     """
+    from scipy.sparse.csgraph import dijkstra
     n_jobs = check_n_jobs(n_jobs)
     src = _ensure_src(src)
     if not np.isscalar(dist_limit):
@@ -2222,8 +2308,7 @@ def add_source_space_distances(src, dist_limit=np.inf, n_jobs=1, verbose=None):
         # can't do introspection on dijkstra function because it's Cython,
         # so we'll just try quickly here
         try:
-            sparse.csgraph.dijkstra(sparse.csr_matrix(np.zeros((2, 2))),
-                                    limit=1.0)
+            dijkstra(sparse.csr_matrix(np.zeros((2, 2))), limit=1.0)
         except TypeError:
             raise RuntimeError('Cannot use "limit < np.inf" unless scipy '
                                '> 0.13 is installed')
@@ -2272,11 +2357,12 @@ def add_source_space_distances(src, dist_limit=np.inf, n_jobs=1, verbose=None):
 
 
 def _do_src_distances(con, vertno, run_inds, limit):
-    """Helper to compute source space distances in chunks"""
+    """Compute source space distances in chunks."""
+    from scipy.sparse.csgraph import dijkstra
     if limit < np.inf:
-        func = partial(sparse.csgraph.dijkstra, limit=limit)
+        func = partial(dijkstra, limit=limit)
     else:
-        func = sparse.csgraph.dijkstra
+        func = dijkstra
     chunk_size = 20  # save memory by chunking (only a little slower)
     lims = np.r_[np.arange(0, len(run_inds), chunk_size), len(run_inds)]
     n_chunks = len(lims) - 1
@@ -2299,19 +2385,23 @@ def _do_src_distances(con, vertno, run_inds, limit):
     return d, min_idx, min_dist
 
 
-def get_volume_labels_from_aseg(mgz_fname):
-    """Returns a list of names of segmented volumes.
+def get_volume_labels_from_aseg(mgz_fname, return_colors=False):
+    """Return a list of names and colors of segmented volumes.
 
     Parameters
     ----------
     mgz_fname : str
         Filename to read. Typically aseg.mgz or some variant in the freesurfer
         pipeline.
+    return_colors : bool
+        If True returns also the labels colors
 
     Returns
     -------
     label_names : list of str
         The names of segmented volumes included in this mgz file.
+    label_colors : list of str
+        The RGB colors of the labels included in this mgz file.
 
     Notes
     -----
@@ -2324,14 +2414,94 @@ def get_volume_labels_from_aseg(mgz_fname):
 
     # Get the unique label names
     lut = _get_lut()
+
     label_names = [lut[lut['id'] == ii]['name'][0].decode('utf-8')
                    for ii in np.unique(mgz_data)]
-    label_names = sorted(label_names, key=lambda n: n.lower())
-    return label_names
+    label_colors = [[lut[lut['id'] == ii]['R'][0],
+                     lut[lut['id'] == ii]['G'][0],
+                     lut[lut['id'] == ii]['B'][0],
+                     lut[lut['id'] == ii]['A'][0]]
+                    for ii in np.unique(mgz_data)]
+
+    order = np.argsort(label_names)
+    label_names = [label_names[k] for k in order]
+    label_colors = [label_colors[k] for k in order]
+
+    if return_colors:
+        return label_names, label_colors
+    else:
+        return label_names
+
+
+def get_volume_labels_from_src(src, subject, subjects_dir):
+    """Return a list of Label of segmented volumes included in the src space.
+
+    Parameters
+    ----------
+    src : instance of SourceSpaces
+        The source space containing the volume regions
+    subject: str
+        Subject name
+    subjects_dir: str
+        Freesurfer folder of the subjects
+
+    Returns
+    -------
+    labels_aseg : list of Label
+        List of Label of segmented volumes included in src space.
+
+    """
+    import os.path as op
+    import numpy as np
+
+    from . import Label
+    from . import get_volume_labels_from_aseg
+
+    # Read the aseg file
+    aseg_fname = op.join(subjects_dir, subject, 'mri', 'aseg.mgz')
+    if not op.isfile(aseg_fname):
+        raise IOError('aseg file "%s" not found' % aseg_fname)
+    all_labels_aseg = get_volume_labels_from_aseg(aseg_fname,
+                                                  return_colors=True)
+
+    # Create a list of Label
+    if len(src) < 2:
+        raise ValueError('No vol src space in src')
+
+    if any(np.any(s['type'] != 'vol') for s in src[2:]):
+            raise ValueError('source spaces have to be of vol type')
+
+    labels_aseg = list()
+    for nr in range(2, len(src)):
+        vertices = src[nr]['vertno']
+
+        pos = src[nr]['rr'][src[nr]['vertno'], :]
+        roi_str = src[nr]['seg_name']
+        try:
+            ind = all_labels_aseg[0].index(roi_str)
+            color = np.array(all_labels_aseg[1][ind]) / 255
+        except ValueError:
+            pass
+
+        if 'left' in roi_str.lower():
+            hemi = 'lh'
+            roi_str = roi_str.replace('Left-', '') + '-lh'
+        elif 'right' in roi_str.lower():
+            hemi = 'rh'
+            roi_str = roi_str.replace('Right-', '') + '-rh'
+        else:
+            hemi = 'both'
+
+        label = Label(vertices=vertices, pos=pos, hemi=hemi,
+                      name=roi_str, color=color,
+                      subject=subject)
+        labels_aseg.append(label)
+
+    return labels_aseg
 
 
 def _get_hemi(s):
-    """Helper to get a hemisphere from a given source space"""
+    """Get a hemisphere from a given source space."""
     if s['type'] != 'surf':
         raise RuntimeError('Only surface source spaces supported')
     if s['id'] == FIFF.FIFFV_MNE_SURF_LEFT_HEMI:
@@ -2344,7 +2514,7 @@ def _get_hemi(s):
 
 def _get_vertex_map_nn(fro_src, subject_from, subject_to, hemi, subjects_dir,
                        to_neighbor_tri=None):
-    """Helper to get a nearest-neigbor vertex match for a given hemi src
+    """Get a nearest-neigbor vertex match for a given hemi src.
 
     The to_neighbor_tri can optionally be passed in to avoid recomputation
     if it's already available.
@@ -2355,9 +2525,13 @@ def _get_vertex_map_nn(fro_src, subject_from, subject_to, hemi, subjects_dir,
                 % (hemi, subject_from, subject_to))
     regs = [op.join(subjects_dir, s, 'surf', '%s.sphere.reg' % hemi)
             for s in (subject_from, subject_to)]
-    reg_fro, reg_to = [_read_surface_geom(r, patch_stats=False) for r in regs]
-    if to_neighbor_tri is None:
-        to_neighbor_tri = _triangle_neighbors(reg_to['tris'], reg_to['np'])
+    reg_fro, reg_to = [read_surface(r, return_dict=True)[-1] for r in regs]
+    if to_neighbor_tri is not None:
+        reg_to['neighbor_tri'] = to_neighbor_tri
+    if 'neighbor_tri' not in reg_to:
+        reg_to['neighbor_tri'] = _triangle_neighbors(reg_to['tris'],
+                                                     reg_to['np'])
+
     morph_inuse = np.zeros(len(reg_to['rr']), bool)
     best = np.zeros(fro_src['np'], int)
     ones = _compute_nearest(reg_to['rr'], reg_fro['rr'][fro_src['vertno']])
@@ -2385,7 +2559,7 @@ def _get_vertex_map_nn(fro_src, subject_from, subject_to, hemi, subjects_dir,
 @verbose
 def morph_source_spaces(src_from, subject_to, surf='white', subject_from=None,
                         subjects_dir=None, verbose=None):
-    """Morph an existing source space to a different subject
+    """Morph an existing source space to a different subject.
 
     .. warning:: This can be used in place of morphing source estimates for
                  multiple subjects, but there may be consequences in terms
@@ -2402,10 +2576,11 @@ def morph_source_spaces(src_from, subject_to, surf='white', subject_from=None,
     subject_from : str | None
         The "from" subject. For most source spaces this shouldn't need
         to be provided, since it is stored in the source space itself.
-    subjects_dir : string, or None
+    subjects_dir : str | None
         Path to SUBJECTS_DIR if it is not set in the environment.
-    verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+    verbose : bool | str | int | None
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -2425,8 +2600,8 @@ def morph_source_spaces(src_from, subject_to, surf='white', subject_from=None,
         hemi, idx, id_ = _get_hemi(fro)
         to = op.join(subjects_dir, subject_to, 'surf', '%s.%s' % (hemi, surf,))
         logger.info('Reading destination surface %s' % (to,))
-        to = _read_surface_geom(to, patch_stats=False, verbose=False)
-        _complete_surface_info(to)
+        to = read_surface(to, return_dict=True, verbose=False)[-1]
+        complete_surface_info(to, copy=False)
         # Now we morph the vertices to the destination
         # The C code does something like this, but with a nearest-neighbor
         # mapping instead of the weighted one::
@@ -2460,7 +2635,7 @@ def morph_source_spaces(src_from, subject_to, surf='white', subject_from=None,
 @verbose
 def _get_morph_src_reordering(vertices, src_from, subject_from, subject_to,
                               subjects_dir=None, verbose=None):
-    """Get the reordering indices for a morphed source space
+    """Get the reordering indices for a morphed source space.
 
     Parameters
     ----------
@@ -2475,7 +2650,8 @@ def _get_morph_src_reordering(vertices, src_from, subject_from, subject_to,
     subjects_dir : string, or None
         Path to SUBJECTS_DIR if it is not set in the environment.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -2523,7 +2699,7 @@ def _get_morph_src_reordering(vertices, src_from, subject_from, subject_to,
 
 def _compare_source_spaces(src0, src1, mode='exact', nearest=True,
                            dist_tol=1.5e-3):
-    """Compare two source spaces
+    """Compare two source spaces.
 
     Note: this function is also used by forward/tests/test_make_forward.py
     """
@@ -2533,7 +2709,7 @@ def _compare_source_spaces(src0, src1, mode='exact', nearest=True,
     if mode != 'exact' and 'approx' not in mode:  # 'nointerp' can be appended
         raise RuntimeError('unknown mode %s' % mode)
 
-    for s0, s1 in zip(src0, src1):
+    for si, (s0, s1) in enumerate(zip(src0, src1)):
         # first check the keys
         a, b = set(s0.keys()), set(s1.keys())
         assert_equal(a, b, str(a ^ b))
@@ -2586,10 +2762,11 @@ def _compare_source_spaces(src0, src1, mode='exact', nearest=True,
                     assert_true(len((s0['dist'] - s1['dist']).data) == 0)
         else:  # 'approx' in mode:
             # deal with vertno, inuse, and use_tris carefully
-            assert_array_equal(s0['vertno'], np.where(s0['inuse'])[0],
-                               'left hemisphere vertices')
-            assert_array_equal(s1['vertno'], np.where(s1['inuse'])[0],
-                               'right hemisphere vertices')
+            for ii, s in enumerate((s0, s1)):
+                assert_array_equal(s['vertno'], np.where(s['inuse'])[0],
+                                   'src%s[%s]["vertno"] != '
+                                   'np.where(src%s[%s]["inuse"])[0]'
+                                   % (ii, si, ii, si))
             assert_equal(len(s0['vertno']), len(s1['vertno']))
             agreement = np.mean(s0['inuse'] == s1['inuse'])
             assert_true(agreement >= 0.99, "%s < 0.99" % agreement)
diff --git a/mne/stats/__init__.py b/mne/stats/__init__.py
index 6a94d61..c6b7079 100644
--- a/mne/stats/__init__.py
+++ b/mne/stats/__init__.py
@@ -1,7 +1,8 @@
-"""Functions for statistical analysis"""
+"""Functions for statistical analysis."""
 
-from .parametric import f_threshold_mway_rm, f_mway_rm
-from .permutations import permutation_t_test
+from .parametric import (f_threshold_mway_rm, f_mway_rm, f_oneway,
+                         _parametric_ci)
+from .permutations import permutation_t_test, _ci, _bootstrap_ci
 from .cluster_level import (permutation_cluster_test,
                             permutation_cluster_1samp_test,
                             spatio_temporal_cluster_1samp_test,
diff --git a/mne/stats/cluster_level.py b/mne/stats/cluster_level.py
index 0e9d787..72fc8a2 100755
--- a/mne/stats/cluster_level.py
+++ b/mne/stats/cluster_level.py
@@ -16,12 +16,13 @@ from scipy import sparse
 
 from .parametric import f_oneway
 from ..parallel import parallel_func, check_n_jobs
-from ..utils import split_list, logger, verbose, ProgressBar, warn
+from ..utils import (split_list, logger, verbose, ProgressBar, warn, _pl,
+                     check_random_state)
 from ..source_estimate import SourceEstimate
 
 
 def _get_clusters_spatial(s, neighbors):
-    """Helper function to form spatial clusters using neighbor lists
+    """Form spatial clusters using neighbor lists.
 
     This is equivalent to _get_components with n_times = 1, with a properly
     reconfigured connectivity matrix (formed as "neighbors" list)
@@ -57,7 +58,7 @@ def _get_clusters_spatial(s, neighbors):
 
 
 def _reassign(check, clusters, base, num):
-    """Helper function to reassign cluster numbers"""
+    """Reassign cluster numbers."""
     # reconfigure check matrix
     check[check == num] = base
     # concatenate new values into clusters array
@@ -67,7 +68,9 @@ def _reassign(check, clusters, base, num):
 
 
 def _get_clusters_st_1step(keepers, neighbors):
-    """Directly calculate connectivity based on knowledge that time points are
+    """Directly calculate connectivity.
+
+    This uses knowledge that time points are
     only connected to adjacent neighbors for data organized as time x space.
 
     This algorithm time increases linearly with the number of time points,
@@ -75,7 +78,8 @@ def _get_clusters_st_1step(keepers, neighbors):
 
     This algorithm creates clusters for each time point using a method more
     efficient than the standard graph method (but otherwise equivalent), then
-    combines these clusters across time points in a reasonable way."""
+    combines these clusters across time points in a reasonable way.
+    """
     n_src = len(neighbors)
     n_times = len(keepers)
     # start cluster numbering at 1 for diffing convenience
@@ -112,11 +116,14 @@ def _get_clusters_st_1step(keepers, neighbors):
 
 
 def _get_clusters_st_multistep(keepers, neighbors, max_step=1):
-    """Directly calculate connectivity based on knowledge that time points are
+    """Directly calculate connectivity.
+
+    This uses knowledge that time points are
     only connected to adjacent neighbors for data organized as time x space.
 
     This algorithm time increases linearly with the number of time points,
-    compared to with the square for the standard (graph) algorithm."""
+    compared to with the square for the standard (graph) algorithm.
+    """
     n_src = len(neighbors)
     n_times = len(keepers)
     t_border = list()
@@ -169,7 +176,7 @@ def _get_clusters_st_multistep(keepers, neighbors, max_step=1):
 
 
 def _get_clusters_st(x_in, neighbors, max_step=1):
-    """Helper function to choose the most efficient version"""
+    """Choose the most efficient version."""
     n_src = len(neighbors)
     n_times = x_in.size // n_src
     cl_goods = np.where(x_in)[0]
@@ -199,23 +206,8 @@ def _get_clusters_st(x_in, neighbors, max_step=1):
 
 
 def _get_components(x_in, connectivity, return_list=True):
-    """get connected components from a mask and a connectivity matrix"""
-    try:
-        from sklearn.utils._csgraph import cs_graph_components
-    except ImportError:
-        try:
-            from scikits.learn.utils._csgraph import cs_graph_components
-        except ImportError:
-            try:
-                from sklearn.utils.sparsetools import connected_components
-                cs_graph_components = connected_components
-            except ImportError:
-                # in theory we might be able to shoehorn this into using
-                # _get_clusters_spatial if we transform connectivity into
-                # a neighbor list, and it might end up being faster anyway,
-                # but for now:
-                raise ImportError('scikit-learn must be installed')
-
+    """Get connected components from a mask and a connectivity matrix."""
+    from scipy.sparse.csgraph import connected_components
     mask = np.logical_and(x_in[connectivity.row], x_in[connectivity.col])
     data = connectivity.data[mask]
     row = connectivity.row[mask]
@@ -226,7 +218,7 @@ def _get_components(x_in, connectivity, return_list=True):
     col = np.concatenate((col, idx))
     data = np.concatenate((data, np.ones(len(idx), dtype=data.dtype)))
     connectivity = sparse.coo_matrix((data, (row, col)), shape=shape)
-    _, components = cs_graph_components(connectivity)
+    _, components = connected_components(connectivity)
     if return_list:
         start = np.min(components)
         stop = np.max(components)
@@ -243,8 +235,7 @@ def _get_components(x_in, connectivity, return_list=True):
 
 def _find_clusters(x, threshold, tail=0, connectivity=None, max_step=1,
                    include=None, partitions=None, t_power=1, show_info=False):
-    """For a given 1d-array (test statistic), find all clusters which
-    are above/below a certain threshold. Returns a list of 2-tuples.
+    """Find all clusters which are above/below a certain threshold.
 
     When doing a two-tailed test (tail == 0), only points with the same
     sign will be clustered together.
@@ -405,8 +396,7 @@ def _find_clusters(x, threshold, tail=0, connectivity=None, max_step=1,
 
 def _find_clusters_1dir_parts(x, x_in, connectivity, max_step, partitions,
                               t_power, ndimage):
-    """Deal with partitions, and pass the work to _find_clusters_1dir
-    """
+    """Deal with partitions, and pass the work to _find_clusters_1dir."""
     if partitions is None:
         clusters, sums = _find_clusters_1dir(x, x_in, connectivity, max_step,
                                              t_power, ndimage)
@@ -425,7 +415,7 @@ def _find_clusters_1dir_parts(x, x_in, connectivity, max_step, partitions,
 
 
 def _find_clusters_1dir(x, x_in, connectivity, max_step, t_power, ndimage):
-    """Actually call the clustering algorithm"""
+    """Actually call the clustering algorithm."""
     if connectivity is None:
         labels, n_labels = ndimage.label(x_in)
 
@@ -474,7 +464,7 @@ def _find_clusters_1dir(x, x_in, connectivity, max_step, t_power, ndimage):
 
 
 def _cluster_indices_to_mask(components, n_tot):
-    """Convert to the old format of clusters, which were bool arrays"""
+    """Convert to the old format of clusters, which were bool arrays."""
     for ci, c in enumerate(components):
         components[ci] = np.zeros((n_tot), dtype=bool)
         components[ci][c] = True
@@ -482,7 +472,7 @@ def _cluster_indices_to_mask(components, n_tot):
 
 
 def _cluster_mask_to_indices(components):
-    """Convert to the old format of clusters, which were bool arrays"""
+    """Convert to the old format of clusters, which were bool arrays."""
     for ci, c in enumerate(components):
         if not isinstance(c, slice):
             components[ci] = np.where(c)[0]
@@ -490,7 +480,7 @@ def _cluster_mask_to_indices(components):
 
 
 def _pval_from_histogram(T, H0, tail):
-    """Get p-values from stats values given an H0 distribution
+    """Get p-values from stats values given an H0 distribution.
 
     For each stat compute a p-value as percentile of its statistics
     within all statistics in surrogate data
@@ -500,17 +490,19 @@ def _pval_from_histogram(T, H0, tail):
 
     # from pct to fraction
     if tail == -1:  # up tail
-        pval = np.array([np.sum(H0 <= t) for t in T])
+        pval = np.array([np.mean(H0 <= t) for t in T])
     elif tail == 1:  # low tail
-        pval = np.array([np.sum(H0 >= t) for t in T])
+        pval = np.array([np.mean(H0 >= t) for t in T])
     else:  # both tails
-        pval = np.array([np.sum(abs(H0) >= abs(t)) for t in T])
+        pval = np.array([np.mean(abs(H0) >= abs(t)) for t in T])
 
-    pval = (pval + 1.0) / (H0.size + 1.0)  # the init data is one resampling
     return pval
 
 
 def _setup_connectivity(connectivity, n_vertices, n_times):
+    if not sparse.issparse(connectivity):
+        raise ValueError("If connectivity matrix is given, it must be a"
+                         "scipy sparse matrix.")
     if connectivity.shape[0] == n_vertices:  # use global algorithm
         connectivity = connectivity.tocoo()
         n_times = None
@@ -526,32 +518,29 @@ def _setup_connectivity(connectivity, n_vertices, n_times):
 
 
 def _do_permutations(X_full, slices, threshold, tail, connectivity, stat_fun,
-                     max_step, include, partitions, t_power, seeds,
+                     max_step, include, partitions, t_power, orders,
                      sample_shape, buffer_size, progress_bar):
-
     n_samp, n_vars = X_full.shape
 
     if buffer_size is not None and n_vars <= buffer_size:
         buffer_size = None  # don't use buffer for few variables
 
     # allocate space for output
-    max_cluster_sums = np.empty(len(seeds), dtype=np.double)
+    max_cluster_sums = np.empty(len(orders), dtype=np.double)
 
     if buffer_size is not None:
         # allocate buffer, so we don't need to allocate memory during loop
         X_buffer = [np.empty((len(X_full[s]), buffer_size), dtype=X_full.dtype)
                     for s in slices]
 
-    for seed_idx, seed in enumerate(seeds):
+    for seed_idx, order in enumerate(orders):
         if progress_bar is not None:
             if (not (seed_idx + 1) % 32) or (seed_idx == 0):
                 progress_bar.update(seed_idx + 1)
 
         # shuffle sample indices
-        rng = np.random.RandomState(seed)
-        idx_shuffled = np.arange(n_samp)
-        rng.shuffle(idx_shuffled)
-        idx_shuffle_list = [idx_shuffled[s] for s in slices]
+        assert order is not None
+        idx_shuffle_list = [order[s] for s in slices]
 
         if buffer_size is None:
             # shuffle all data at once
@@ -594,7 +583,7 @@ def _do_permutations(X_full, slices, threshold, tail, connectivity, stat_fun,
 
 
 def _do_1samp_permutations(X, slices, threshold, tail, connectivity, stat_fun,
-                           max_step, include, partitions, t_power, seeds,
+                           max_step, include, partitions, t_power, orders,
                            sample_shape, buffer_size, progress_bar):
     n_samp, n_vars = X.shape
     assert slices is None  # should be None for the 1 sample case
@@ -603,29 +592,23 @@ def _do_1samp_permutations(X, slices, threshold, tail, connectivity, stat_fun,
         buffer_size = None  # don't use buffer for few variables
 
     # allocate space for output
-    max_cluster_sums = np.empty(len(seeds), dtype=np.double)
+    max_cluster_sums = np.empty(len(orders), dtype=np.double)
 
     if buffer_size is not None:
         # allocate a buffer so we don't need to allocate memory in loop
         X_flip_buffer = np.empty((n_samp, buffer_size), dtype=X.dtype)
 
-    for seed_idx, seed in enumerate(seeds):
+    for seed_idx, order in enumerate(orders):
         if progress_bar is not None:
             if not (seed_idx + 1) % 32 or seed_idx == 0:
                 progress_bar.update(seed_idx + 1)
 
-        if isinstance(seed, np.ndarray):
-            # new surrogate data with specified sign flip
-            if not seed.size == n_samp:
-                raise ValueError('rng string must be n_samples long')
-            signs = 2 * seed[:, None].astype(int) - 1
-            if not np.all(np.equal(np.abs(signs), 1)):
-                raise ValueError('signs from rng must be +/- 1')
-        else:
-            rng = np.random.RandomState(seed)
-            # new surrogate data with random sign flip
-            signs = np.sign(0.5 - rng.rand(n_samp))
-            signs = signs[:, np.newaxis]
+        assert isinstance(order, np.ndarray)
+        # new surrogate data with specified sign flip
+        assert order.size == n_samp  # should be guaranteed by parent
+        signs = 2 * order[:, None].astype(int) - 1
+        if not np.all(np.equal(np.abs(signs), 1)):
+            raise ValueError('signs from rng must be +/- 1')
 
         if buffer_size is None:
             # be careful about non-writable memmap (GH#1507)
@@ -678,7 +661,7 @@ def _permutation_cluster_test(X, threshold, n_permutations, tail, stat_fun,
                               exclude, step_down_p, t_power, out_type,
                               check_disjoint, buffer_size):
     n_jobs = check_n_jobs(n_jobs)
-    """ Aux Function
+    """Aux Function.
 
     Note. X is required to be a list. Depending on the length of X
     either a 1 sample t-test or an f-test / more sample permutation scheme
@@ -686,6 +669,11 @@ def _permutation_cluster_test(X, threshold, n_permutations, tail, stat_fun,
     """
     if out_type not in ['mask', 'indices']:
         raise ValueError('out_type must be either \'mask\' or \'indices\'')
+    if not isinstance(threshold, dict) and (tail < 0 and threshold > 0 or
+                                            tail > 0 and threshold < 0 or
+                                            tail == 0 and threshold < 0):
+        raise ValueError('incompatible tail and threshold signs, got %s and %s'
+                         % (tail, threshold))
 
     # check dimensions for each group in X (a list at this stage).
     X = [x[:, np.newaxis] if x.ndim == 1 else x for x in X]
@@ -763,10 +751,49 @@ def _permutation_cluster_test(X, threshold, n_permutations, tail, stat_fun,
     # The stat should have the same shape as the samples
     T_obs.shape = sample_shape
 
-    if len(X) == 1:  # 1 sample test
+    # convert our seed to orders
+    # check to see if we can do an exact test
+    # (for a two-tailed test, we can exploit symmetry to just do half)
+    extra = ''
+    rng = check_random_state(seed)
+    del seed
+    if len(X) == 1:  # 1-sample test
         do_perm_func = _do_1samp_permutations
         X_full = X[0]
         slices = None
+        # determine ordering
+        max_perms = 2 ** (n_samples - (tail == 0)) - 1
+        if max_perms < n_permutations:
+            # omit first perm b/c accounted for in H0.append() later;
+            # convert to binary array representation
+            orders = np.arange(max_perms)
+            extra = ' (exact test)'
+            orders = [np.fromiter(np.binary_repr(s + 1, n_samples), dtype=int)
+                      for s in orders]
+        elif n_samples <= 20:  # fast way to do it for small(ish) n_samples
+            orders = rng.choice(max_perms, n_permutations - 1, replace=False)
+            orders = [np.fromiter(np.binary_repr(s + 1, n_samples), dtype=int)
+                      for s in orders]
+        else:  # n_samples >= 64
+            # Here we can just use the hash-table (w/collision detection)
+            # functionality of a dict to ensure uniqueness
+            orders = np.zeros((n_permutations - 1, n_samples), int)
+            hashes = {}
+            ii = 0
+            # in the symmetric case, we should never flip one of the subjects
+            # to prevent positive/negative equivalent collisions
+            use_samples = n_samples - (tail == 0)
+            while ii < n_permutations - 1:
+                signs = tuple((rng.rand(use_samples) < 0.5).astype(int))
+                if signs not in hashes:
+                    orders[ii, :use_samples] = signs
+                    if tail == 0 and rng.rand() < 0.5:
+                        # To undo the non-flipping of the last subject in the
+                        # tail == 0 case, half the time we use the positive
+                        # last subject, half the time negative last subject
+                        orders[ii] = 1 - orders[ii]
+                    hashes[signs] = None
+                    ii += 1
     else:
         do_perm_func = _do_permutations
         X_full = np.concatenate(X, axis=0)
@@ -774,8 +801,15 @@ def _permutation_cluster_test(X, threshold, n_permutations, tail, stat_fun,
         splits_idx = np.append([0], np.cumsum(n_samples_per_condition))
         slices = [slice(splits_idx[k], splits_idx[k + 1])
                   for k in range(len(X))]
+        orders = [rng.permutation(len(X_full))
+                  for _ in range(n_permutations - 1)]
+    del rng
     parallel, my_do_perm_func, _ = parallel_func(do_perm_func, n_jobs)
 
+    if len(clusters) == 0:
+        warn('No clusters found, returning empty H0, clusters, and cluster_pv')
+        return T_obs, np.array([]), np.array([]), np.array([])
+
     # Step 2: If we have some clusters, repeat process on permuted data
     # -------------------------------------------------------------------
 
@@ -784,75 +818,63 @@ def _permutation_cluster_test(X, threshold, n_permutations, tail, stat_fun,
         return (ProgressBar(len(seeds), spinner=True) if
                 logger.level <= logging.INFO else None)
 
-    if len(clusters) > 0:
-        # check to see if we can do an exact test
-        # note for a two-tailed test, we can exploit symmetry to just do half
-        seeds = None
-        if len(X) == 1:
-            max_perms = 2 ** (n_samples - (tail == 0))
-            if max_perms <= n_permutations:
-                # omit first perm b/c accounted for in _pval_from_histogram,
-                # convert to binary array representation
-                seeds = [np.fromiter(np.binary_repr(s, n_samples), dtype=int)
-                         for s in range(1, max_perms)]
-
-        if seeds is None:
-            if seed is None:
-                seeds = [None] * n_permutations
+    # Step 3: repeat permutations for step-down-in-jumps procedure
+    n_removed = 1  # number of new clusters added
+    total_removed = 0
+    step_down_include = None  # start out including all points
+    n_step_downs = 0
+
+    while n_removed > 0:
+        # actually do the clustering for each partition
+        if include is not None:
+            if step_down_include is not None:
+                this_include = np.logical_and(include, step_down_include)
             else:
-                seeds = list(seed + np.arange(n_permutations))
-
-        # Step 3: repeat permutations for step-down-in-jumps procedure
-        n_removed = 1  # number of new clusters added
-        total_removed = 0
-        step_down_include = None  # start out including all points
-        n_step_downs = 0
-
-        while n_removed > 0:
-            # actually do the clustering for each partition
-            if include is not None:
-                if step_down_include is not None:
-                    this_include = np.logical_and(include, step_down_include)
-                else:
-                    this_include = include
-            else:
-                this_include = step_down_include
-            logger.info('Permuting ...')
-            H0 = parallel(my_do_perm_func(X_full, slices, threshold, tail,
-                          connectivity, stat_fun, max_step, this_include,
-                          partitions, t_power, s, sample_shape, buffer_size,
-                          get_progress_bar(s))
-                          for s in split_list(seeds, n_jobs))
-            H0 = np.concatenate(H0)
-            logger.info('Computing cluster p-values')
-            cluster_pv = _pval_from_histogram(cluster_stats, H0, tail)
-
-            # figure out how many new ones will be removed for step-down
-            to_remove = np.where(cluster_pv < step_down_p)[0]
-            n_removed = to_remove.size - total_removed
-            total_removed = to_remove.size
-            step_down_include = np.ones(n_tests, dtype=bool)
-            for ti in to_remove:
-                step_down_include[clusters[ti]] = False
-            if connectivity is None:
-                step_down_include.shape = sample_shape
-            n_step_downs += 1
-            if step_down_p > 0:
-                a_text = 'additional ' if n_step_downs > 1 else ''
-                pl = '' if n_removed == 1 else 's'
-                logger.info('Step-down-in-jumps iteration #%i found %i %s'
-                            'cluster%s to exclude from subsequent iterations'
-                            % (n_step_downs, n_removed, a_text, pl))
-        logger.info('Done.')
-        # The clusters should have the same shape as the samples
-        clusters = _reshape_clusters(clusters, sample_shape)
-        return T_obs, clusters, cluster_pv, H0
-    else:
-        return T_obs, np.array([]), np.array([]), np.array([])
+                this_include = include
+        else:
+            this_include = step_down_include
+        logger.info('Permuting %d times%s...' % (len(orders), extra))
+        H0 = parallel(my_do_perm_func(X_full, slices, threshold, tail,
+                      connectivity, stat_fun, max_step, this_include,
+                      partitions, t_power, order, sample_shape, buffer_size,
+                      get_progress_bar(order))
+                      for order in split_list(orders, n_jobs))
+        # include original (true) ordering
+        if tail == -1:  # up tail
+            orig = cluster_stats.min()
+        elif tail == 1:
+            orig = cluster_stats.max()
+        else:
+            orig = abs(cluster_stats).max()
+        H0.insert(0, [orig])
+        H0 = np.concatenate(H0)
+        logger.info('Computing cluster p-values')
+        cluster_pv = _pval_from_histogram(cluster_stats, H0, tail)
+
+        # figure out how many new ones will be removed for step-down
+        to_remove = np.where(cluster_pv < step_down_p)[0]
+        n_removed = to_remove.size - total_removed
+        total_removed = to_remove.size
+        step_down_include = np.ones(n_tests, dtype=bool)
+        for ti in to_remove:
+            step_down_include[clusters[ti]] = False
+        if connectivity is None:
+            step_down_include.shape = sample_shape
+        n_step_downs += 1
+        if step_down_p > 0:
+            a_text = 'additional ' if n_step_downs > 1 else ''
+            logger.info('Step-down-in-jumps iteration #%i found %i %s'
+                        'cluster%s to exclude from subsequent iterations'
+                        % (n_step_downs, n_removed, a_text,
+                           _pl(n_removed)))
+    logger.info('Done.')
+    # The clusters should have the same shape as the samples
+    clusters = _reshape_clusters(clusters, sample_shape)
+    return T_obs, clusters, cluster_pv, H0
 
 
 def ttest_1samp_no_p(X, sigma=0, method='relative'):
-    """t-test with variance adjustment and no p-value calculation
+    """Perform t-test with variance adjustment and no p-value calculation.
 
     Parameters
     ----------
@@ -902,7 +924,7 @@ def permutation_cluster_test(X, threshold=None, n_permutations=1024,
                              seed=None, max_step=1, exclude=None,
                              step_down_p=0, t_power=1, out_type='mask',
                              check_disjoint=False, buffer_size=1000):
-    """Cluster-level statistical permutation test
+    """Cluster-level statistical permutation test.
 
     For a list of nd-arrays of data, e.g. 2d for time series or 3d for
     time-frequency power values, calculate some statistics corrected for
@@ -941,10 +963,11 @@ def permutation_cluster_test(X, threshold=None, n_permutations=1024,
         be symmetric and only the upper triangular half is used.
         Default is None, i.e, a regular lattice connectivity.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
     n_jobs : int
         Number of permutations to run in parallel (requires joblib package).
-    seed : int or None
+    seed : int | instance of RandomState | None
         Seed the random number generator for results reproducibility.
     max_step : int
         When connectivity is a n_vertices x n_vertices matrix, specify the
@@ -987,23 +1010,20 @@ def permutation_cluster_test(X, threshold=None, n_permutations=1024,
 
     Returns
     -------
-    T_obs : array of shape [n_tests]
+    T_obs : array, shape (n_tests,)
         T-statistic observed for all variables.
     clusters : list
         List type defined by out_type above.
     cluster_pv : array
         P-value for each cluster
-    H0 : array of shape [n_permutations]
+    H0 : array, shape (n_permutations,)
         Max cluster level stats observed under permutation.
 
-    Notes
-    -----
-    Reference:
-    Cluster permutation algorithm as described in
-    Maris/Oostenveld (2007),
-    "Nonparametric statistical testing of EEG- and MEG-data"
-    Journal of Neuroscience Methods, Vol. 164, No. 1., pp. 177-190.
-    doi:10.1016/j.jneumeth.2007.03.024
+    References
+    ----------
+    .. [1] Maris/Oostenveld (2007), "Nonparametric statistical testing of
+       EEG- and MEG-data" Journal of Neuroscience Methods,
+       Vol. 164, No. 1., pp. 177-190. doi:10.1016/j.jneumeth.2007.03.024.
     """
     from scipy import stats
     ppf = stats.f.ppf
@@ -1027,9 +1047,6 @@ def permutation_cluster_test(X, threshold=None, n_permutations=1024,
                                      buffer_size=buffer_size)
 
 
-permutation_cluster_test.__test__ = False
-
-
 @verbose
 def permutation_cluster_1samp_test(X, threshold=None, n_permutations=1024,
                                    tail=0, stat_fun=ttest_1samp_no_p,
@@ -1037,13 +1054,13 @@ def permutation_cluster_1samp_test(X, threshold=None, n_permutations=1024,
                                    seed=None, max_step=1, exclude=None,
                                    step_down_p=0, t_power=1, out_type='mask',
                                    check_disjoint=False, buffer_size=1000):
-    """Non-parametric cluster-level 1 sample T-test
+    """Non-parametric cluster-level 1 sample T-test.
 
     From a array of observations, e.g. signal amplitudes or power spectrum
     estimates etc., calculate if the observed mean significantly deviates
     from 0. The procedure uses a cluster analysis with permutation test
     for calculating corrected p-values. Randomized data are generated with
-    random sign flips.
+    random sign flips. See [1]_ for more information.
 
     Parameters
     ----------
@@ -1057,7 +1074,7 @@ def permutation_cluster_1samp_test(X, threshold=None, n_permutations=1024,
         If a dict is used, then threshold-free cluster enhancement (TFCE)
         will be used.
     n_permutations : int
-        The number of permutations to compute.
+        The maximum number of permutations to compute.
     tail : -1 or 0 or 1 (default = 0)
         If tail is 1, the statistic is thresholded above threshold.
         If tail is -1, the statistic is thresholded below threshold.
@@ -1073,14 +1090,12 @@ def permutation_cluster_1samp_test(X, threshold=None, n_permutations=1024,
         Use square n_vertices matrix for datasets with a large temporal
         extent to save on memory and computation time.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
     n_jobs : int
         Number of permutations to run in parallel (requires joblib package).
-    seed : int or None
+    seed : int | instance of RandomState | None
         Seed the random number generator for results reproducibility.
-        Note that if n_permutations >= 2^(n_samples) [or (2^(n_samples-1)) for
-        two-tailed tests], this value will be ignored since an exact test
-        (full permutation test) will be performed.
     max_step : int
         When connectivity is a n_vertices x n_vertices matrix, specify the
         maximum number of steps between vertices along the second dimension
@@ -1122,23 +1137,30 @@ def permutation_cluster_1samp_test(X, threshold=None, n_permutations=1024,
 
     Returns
     -------
-    T_obs : array of shape [n_tests]
+    T_obs : array, shape (n_tests,)
         T-statistic observed for all variables
     clusters : list
         List type defined by out_type above.
     cluster_pv : array
         P-value for each cluster
-    H0 : array of shape [n_permutations]
+    H0 : array, shape (n_permutations,)
         Max cluster level stats observed under permutation.
 
     Notes
     -----
-    Reference:
-    Cluster permutation algorithm as described in
-    Maris/Oostenveld (2007),
-    "Nonparametric statistical testing of EEG- and MEG-data"
-    Journal of Neuroscience Methods, Vol. 164, No. 1., pp. 177-190.
-    doi:10.1016/j.jneumeth.2007.03.024
+    If ``n_permutations >= 2 ** (n_samples - (tail == 0))``,
+    ``n_permutations`` and ``seed`` will be ignored since an exact test
+    (full permutation test) will be performed.
+
+    If no initial clusters are found, i.e., all points in the true
+    distribution are below the threshold, then ``clusters``, ``cluster_pv``,
+    and ``H0`` will all be empty arrays.
+
+    References
+    ----------
+    .. [1] Maris/Oostenveld (2007), "Nonparametric statistical testing of
+       EEG- and MEG-data" Journal of Neuroscience Methods,
+       Vol. 164, No. 1., pp. 177-190. doi:10.1016/j.jneumeth.2007.03.024.
     """
     from scipy import stats
     ppf = stats.t.ppf
@@ -1164,9 +1186,6 @@ def permutation_cluster_1samp_test(X, threshold=None, n_permutations=1024,
                                      buffer_size=buffer_size)
 
 
-permutation_cluster_1samp_test.__test__ = False
-
-
 @verbose
 def spatio_temporal_cluster_1samp_test(X, threshold=None,
                                        n_permutations=1024, tail=0,
@@ -1176,20 +1195,22 @@ def spatio_temporal_cluster_1samp_test(X, threshold=None,
                                        spatial_exclude=None, step_down_p=0,
                                        t_power=1, out_type='indices',
                                        check_disjoint=False, buffer_size=1000):
-    """Non-parametric cluster-level 1 sample T-test for spatio-temporal data
+    """Non-parametric cluster-level 1 sample T-test for spatio-temporal data.
 
     This function provides a convenient wrapper for data organized in the form
-    (observations x time x space) to use permutation_cluster_1samp_test.
+    (observations x time x space) to use
+    :func:`mne.stats.permutation_cluster_1samp_test`. See [1]_ for more
+    information.
 
     Parameters
     ----------
     X : array
-        Array of shape observations x time x vertices.
+        Array data, shape ``(n_observations, n_times, n_vertices)``.
     threshold : float | dict | None
         If threshold is None, it will choose a t-threshold equivalent to
         p < 0.05 for the given number of (within-subject) observations.
         If a dict is used, then threshold-free cluster enhancement (TFCE)
-        will be used.
+        [2]_ will be used.
     n_permutations : int
         The number of permutations to compute.
     tail : -1 or 0 or 1 (default = 0)
@@ -1207,14 +1228,12 @@ def spatio_temporal_cluster_1samp_test(X, threshold=None,
         Use square n_vertices matrix for datasets with a large temporal
         extent to save on memory and computation time.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
     n_jobs : int
         Number of permutations to run in parallel (requires joblib package).
-    seed : int or None
+    seed : int | instance of RandomState | None
         Seed the random number generator for results reproducibility.
-        Note that if n_permutations >= 2^(n_samples) [or (2^(n_samples-1)) for
-        two-tailed tests], this value will be ignored since an exact test
-        (full permutation test) will be performed.
     max_step : int
         When connectivity is a n_vertices x n_vertices matrix, specify the
         maximum number of steps between vertices along the second dimension
@@ -1254,28 +1273,33 @@ def spatio_temporal_cluster_1samp_test(X, threshold=None,
 
     Returns
     -------
-    T_obs : array of shape [n_tests]
+    T_obs : array, shape (n_times * n_vertices,)
         T-statistic observed for all variables.
     clusters : list
         List type defined by out_type above.
     cluster_pv: array
         P-value for each cluster
-    H0 : array of shape [n_permutations]
+    H0 : array, shape (n_permutations,)
         Max cluster level stats observed under permutation.
 
     Notes
     -----
-    Reference:
-    Cluster permutation algorithm as described in
-    Maris/Oostenveld (2007),
-    "Nonparametric statistical testing of EEG- and MEG-data"
-    Journal of Neuroscience Methods, Vol. 164, No. 1., pp. 177-190.
-    doi:10.1016/j.jneumeth.2007.03.024
-
-    TFCE originally described in Smith/Nichols (2009),
-    "Threshold-free cluster enhancement: Addressing problems of
-    smoothing, threshold dependence, and localisation in cluster
-    inference", NeuroImage 44 (2009) 83-98.
+    If ``n_permutations >= 2 ** (n_samples - (tail == 0))``,
+    ``n_permutations`` and ``seed`` will be ignored since an exact test
+    (full permutation test) will be performed.
+
+    If no initial clusters are found, i.e., all points in the true
+    distribution are below the threshold, then ``clusters``, ``cluster_pv``,
+    and ``H0`` will all be empty arrays.
+
+    References
+    ----------
+    .. [1] Maris/Oostenveld, "Nonparametric statistical testing of
+       EEG- and MEG-data" Journal of Neuroscience Methods,
+       Vol. 164, No. 1., pp. 177-190. doi:10.1016/j.jneumeth.2007.03.024
+    .. [2] Smith/Nichols (2009), "Threshold-free cluster enhancement:
+       Addressing problems of smoothing, threshold dependence, and
+       localisation in cluster inference", NeuroImage 44 (2009) 83-98.
     """
     n_samples, n_times, n_vertices = X.shape
 
@@ -1300,9 +1324,6 @@ def spatio_temporal_cluster_1samp_test(X, threshold=None,
     return out
 
 
-spatio_temporal_cluster_1samp_test.__test__ = False
-
-
 @verbose
 def spatio_temporal_cluster_test(X, threshold=1.67, n_permutations=1024,
                                  tail=0, stat_fun=f_oneway,
@@ -1310,15 +1331,17 @@ def spatio_temporal_cluster_test(X, threshold=1.67, n_permutations=1024,
                                  seed=None, max_step=1, spatial_exclude=None,
                                  step_down_p=0, t_power=1, out_type='indices',
                                  check_disjoint=False, buffer_size=1000):
-    """Non-parametric cluster-level test for spatio-temporal data
+    """Non-parametric cluster-level test for spatio-temporal data.
 
     This function provides a convenient wrapper for data organized in the form
-    (observations x time x space) to use permutation_cluster_test.
+    (observations x time x space) to use
+    :func:`mne.stats.permutation_cluster_test`. See [1]_ for more information.
 
     Parameters
     ----------
     X: list of arrays
-        Array of shape (observations, time, vertices) in each group.
+        List of data arrays, shape ``(n_observations, n_times, n_vertices)``
+        in each group.
     threshold: float
         The threshold for the statistic.
     n_permutations: int
@@ -1333,10 +1356,11 @@ def spatio_temporal_cluster_test(X, threshold=1.67, n_permutations=1024,
         be symmetric and only the upper triangular half is used.
         Default is None, i.e, a regular lattice connectivity.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
     n_jobs : int
         Number of permutations to run in parallel (requires joblib package).
-    seed : int or None
+    seed : int | instance of RandomState | None
         Seed the random number generator for results reproducibility.
     max_step : int
         When connectivity is a n_vertices x n_vertices matrix, specify the
@@ -1377,23 +1401,20 @@ def spatio_temporal_cluster_test(X, threshold=1.67, n_permutations=1024,
 
     Returns
     -------
-    T_obs : array of shape [n_tests]
+    T_obs : array, shape (n_times * n_vertices,)
         T-statistic observed for all variables
     clusters : list
         List type defined by out_type above.
     cluster_pv: array
         P-value for each cluster
-    H0 : array of shape [n_permutations]
+    H0 : array, shape (n_permutations,)
         Max cluster level stats observed under permutation.
 
-    Notes
-    -----
-    Reference:
-    Cluster permutation algorithm as described in
-    Maris/Oostenveld (2007),
-    "Nonparametric statistical testing of EEG- and MEG-data"
-    Journal of Neuroscience Methods, Vol. 164, No. 1., pp. 177-190.
-    doi:10.1016/j.jneumeth.2007.03.024
+    References
+    ----------
+    .. [1] Maris/Oostenveld (2007), "Nonparametric statistical testing of
+       EEG- and MEG-data", Journal of Neuroscience Methods,
+       Vol. 164, No. 1., pp. 177-190. doi:10.1016/j.jneumeth.2007.03.024.
     """
     n_samples, n_times, n_vertices = X[0].shape
 
@@ -1417,14 +1438,12 @@ def spatio_temporal_cluster_test(X, threshold=1.67, n_permutations=1024,
     return out
 
 
-spatio_temporal_cluster_test.__test__ = False
-
-
 def _st_mask_from_s_inds(n_times, n_vertices, vertices, set_as=True):
-    """This function returns a boolean mask vector to apply to a spatio-
-    temporal connectivity matrix (n_times * n_vertices square) to include (or
-    exclude) certain spatial coordinates. This is useful for excluding certain
-    regions from analysis (e.g., medial wall vertices).
+    """Compute mask to apply to a spatio-temporal connectivity matrix.
+
+    This can be used to include (or exclude) certain spatial coordinates.
+    This is useful for excluding certain regions from analysis (e.g.,
+    medial wall vertices).
 
     Parameters
     ----------
@@ -1453,8 +1472,7 @@ def _st_mask_from_s_inds(n_times, n_vertices, vertices, set_as=True):
 
 @verbose
 def _get_partitions_from_connectivity(connectivity, n_times, verbose=None):
-    """Use indices to specify disjoint subsets (e.g., hemispheres) based on
-    connectivity"""
+    """Specify disjoint subsets (e.g., hemispheres) based on connectivity."""
     if isinstance(connectivity, list):
         test = np.ones(len(connectivity))
         test_conn = np.zeros((len(connectivity), len(connectivity)),
@@ -1483,7 +1501,7 @@ def _get_partitions_from_connectivity(connectivity, n_times, verbose=None):
 
 
 def _reshape_clusters(clusters, sample_shape):
-    """Reshape cluster masks or indices to be of the correct shape"""
+    """Reshape cluster masks or indices to be of the correct shape."""
     # format of the bool mask and indices are ndarrays
     if len(clusters) > 0 and isinstance(clusters[0], np.ndarray):
         if clusters[0].dtype == bool:  # format of mask
@@ -1495,10 +1513,10 @@ def _reshape_clusters(clusters, sample_shape):
 
 def summarize_clusters_stc(clu, p_thresh=0.05, tstep=1e-3, tmin=0,
                            subject='fsaverage', vertices=None):
-    """ Assemble summary SourceEstimate from spatiotemporal cluster results
+    """Assemble summary SourceEstimate from spatiotemporal cluster results.
 
     This helps visualizing results from spatio-temporal-clustering
-    permutation tests
+    permutation tests.
 
     Parameters
     ----------
diff --git a/mne/stats/multi_comp.py b/mne/stats/multi_comp.py
index a26b4a7..96899d7 100644
--- a/mne/stats/multi_comp.py
+++ b/mne/stats/multi_comp.py
@@ -9,14 +9,13 @@ import numpy as np
 
 
 def _ecdf(x):
-    '''no frills empirical cdf used in fdrcorrection
-    '''
+    """No frills empirical cdf used in fdrcorrection."""
     nobs = len(x)
     return np.arange(1, nobs + 1) / float(nobs)
 
 
 def fdr_correction(pvals, alpha=0.05, method='indep'):
-    """P-value correction with False Discovery Rate (FDR)
+    """P-value correction with False Discovery Rate (FDR).
 
     Correction for multiple comparison using FDR.
 
@@ -79,7 +78,7 @@ def fdr_correction(pvals, alpha=0.05, method='indep'):
 
 
 def bonferroni_correction(pval, alpha=0.05):
-    """P-value correction with Bonferroni method
+    """P-value correction with Bonferroni method.
 
     Parameters
     ----------
diff --git a/mne/stats/parametric.py b/mne/stats/parametric.py
index 49acff1..593372d 100644
--- a/mne/stats/parametric.py
+++ b/mne/stats/parametric.py
@@ -16,8 +16,7 @@ from ..externals.six import string_types
 
 
 def _f_oneway(*args):
-    """
-    Performs a 1-way ANOVA.
+    """Perform a 1-way ANOVA.
 
     The one-way ANOVA tests the null hypothesis that 2 or more groups have
     the same population mean. The test is applied to samples from two or
@@ -89,12 +88,12 @@ def _f_oneway(*args):
 
 
 def f_oneway(*args):
-    """Call scipy.stats.f_oneway, but return only f-value"""
+    """Call scipy.stats.f_oneway, but return only f-value."""
     return _f_oneway(*args)[0]
 
 
 def _map_effects(n_factors, effects):
-    """Map effects to indices"""
+    """Map effects to indices."""
     if n_factors > len(ascii_uppercase):
         raise ValueError('Maximum number of factors supported is 26')
 
@@ -152,14 +151,14 @@ def _map_effects(n_factors, effects):
     return selection, names
 
 
-def _get_contrast_indices(effect_idx, n_factors):
-    """Henson's factor coding, see num2binvec"""
+def _get_contrast_indices(effect_idx, n_factors):  # noqa: D401
+    """Henson's factor coding, see num2binvec."""
     binrepr = np.binary_repr(effect_idx, n_factors)
     return np.array([int(i) for i in binrepr], dtype=int)
 
 
 def _iter_contrasts(n_subjects, factor_levels, effect_picks):
-    """ Aux Function: Setup contrasts """
+    """Set up contrasts."""
     from scipy.signal import detrend
     sc = []
     n_factors = len(factor_levels)
@@ -186,7 +185,7 @@ def _iter_contrasts(n_subjects, factor_levels, effect_picks):
 
 def f_threshold_mway_rm(n_subjects, factor_levels, effects='A*B',
                         pvalue=0.05):
-    """ Compute f-value thesholds for a two-way ANOVA
+    """Compute f-value thesholds for a two-way ANOVA.
 
     Parameters
     ----------
@@ -235,7 +234,7 @@ def f_threshold_mway_rm(n_subjects, factor_levels, effects='A*B',
 
 def f_mway_rm(data, factor_levels, effects='all', alpha=0.05,
               correction=False, return_pvals=True):
-    """M-way repeated measures ANOVA for fully balanced designs
+    """Compute M-way repeated measures ANOVA for fully balanced designs.
 
     Parameters
     ----------
@@ -337,3 +336,16 @@ def f_mway_rm(data, factor_levels, effects='all', alpha=0.05,
 
     # handle single effect returns
     return [np.squeeze(np.asarray(vv)) for vv in (fvalues, pvalues)]
+
+
+def _parametric_ci(arr, ci=.95):
+    """Calculate the `ci`% parametric confidence interval for `arr`."""
+    from scipy import stats
+    mean, sigma = arr.mean(0), stats.sem(arr, 0)
+    # This is highly convoluted to support 17th century Scipy
+    # XXX Fix when Scipy 0.12 support is dropped!
+    # then it becomes just:
+    # return stats.t.interval(ci, loc=mean, scale=sigma, df=arr.shape[0])
+    return np.asarray([stats.t.interval(ci, arr.shape[0],
+                       loc=mean_, scale=sigma_)
+                       for mean_, sigma_ in zip(mean, sigma)]).T
diff --git a/mne/stats/permutations.py b/mne/stats/permutations.py
index a20892a..fae7487 100644
--- a/mne/stats/permutations.py
+++ b/mne/stats/permutations.py
@@ -1,5 +1,4 @@
-"""T-test with permutations
-"""
+"""T-test with permutations."""
 
 # Authors: Alexandre Gramfort <alexandre.gramfort at telecom-paristech.fr>
 #          Fernando Perez (bin_perm_rep function)
@@ -9,19 +8,20 @@
 from math import sqrt
 import numpy as np
 
+from ..utils import check_random_state
 from ..parallel import parallel_func
 from .. import verbose
 
 
 def bin_perm_rep(ndim, a=0, b=1):
-    """bin_perm_rep(ndim) -> ndim permutations with repetitions of (a,b).
+    """Ndim permutations with repetitions of (a,b).
 
     Returns an array with all the possible permutations with repetitions of
     (0,1) in ndim dimensions.  The array is shaped as (2**ndim,ndim), and is
     ordered with the last index changing fastest.  For examble, for ndim=3:
 
-    Examples:
-
+    Examples
+    --------
     >>> bin_perm_rep(3)
     array([[0, 0, 0],
            [0, 0, 1],
@@ -32,7 +32,6 @@ def bin_perm_rep(ndim, a=0, b=1):
            [1, 1, 0],
            [1, 1, 1]])
     """
-
     # Create the leftmost column as 0,0,...,1,1,...
     nperms = 2 ** ndim
     perms = np.empty((nperms, ndim), type(a))
@@ -49,7 +48,7 @@ def bin_perm_rep(ndim, a=0, b=1):
 
 
 def _max_stat(X, X2, perms, dof_scaling):
-    """Aux function for permutation_t_test (for parallel comp)"""
+    """Aux function for permutation_t_test (for parallel comp)."""
     n_samples = len(X)
     mus = np.dot(perms, X) / float(n_samples)
     stds = np.sqrt(X2[None, :] - mus ** 2) * dof_scaling  # std with splitting
@@ -90,7 +89,8 @@ def permutation_t_test(X, n_permutations=10000, tail=0, n_jobs=1,
     n_jobs : int
         Number of CPUs to use for computation.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -127,6 +127,7 @@ def permutation_t_test(X, n_permutations=10000, tail=0, n_jobs=1,
     std0 = np.sqrt(X2 - mu0 ** 2) * dof_scaling  # get std with var splitting
     T_obs = np.mean(X, axis=0) / (std0 / sqrt(n_samples))
 
+    # XXX Someday this should be refactored with the cluster code
     if do_exact:
         perms = bin_perm_rep(n_samples, a=1, b=-1)[1:, :]
     else:
@@ -149,4 +150,34 @@ def permutation_t_test(X, n_permutations=10000, tail=0, n_jobs=1,
 
     return T_obs, p_values, H0
 
-permutation_t_test.__test__ = False  # for nosetests
+
+def _bootstrap_ci(arr, ci=.95, n_bootstraps=2000, stat_fun='mean',
+                  random_state=None):
+    """Get confidence intervals from non-parametric bootstrap."""
+    if stat_fun == "mean":
+        def stat_fun(x):
+            return x.mean(axis=0)
+    elif stat_fun == 'median':
+        def stat_fun(x):
+            return np.median(x, axis=0)
+    elif not callable(stat_fun):
+        raise ValueError("stat_fun must be 'mean', 'median' or callable.")
+    n_trials = arr.shape[0]
+    indices = np.arange(n_trials, dtype=int)  # BCA would be cool to have too
+    rng = check_random_state(random_state)
+    boot_indices = rng.choice(indices, replace=True,
+                              size=(n_bootstraps, len(indices)))
+    stat = np.array([stat_fun(arr[inds]) for inds in boot_indices])
+    ci = (((1 - ci) / 2) * 100, ((1 - ((1 - ci) / 2))) * 100)
+    ci_low, ci_up = np.percentile(stat, ci, axis=0)
+    return np.array([ci_low, ci_up])
+
+
+def _ci(arr, ci=.95, method="bootstrap", n_bootstraps=2000, random_state=None):
+    """Calculate confidence interval. Aux function for plot_compare_evokeds."""
+    if method == "bootstrap":
+        return _bootstrap_ci(arr, ci=ci, n_bootstraps=n_bootstraps,
+                             random_state=random_state)
+    else:
+        from . import _parametric_ci
+        return _parametric_ci(arr, ci=ci)
diff --git a/mne/stats/regression.py b/mne/stats/regression.py
index 80377bc..f0fbd3f 100644
--- a/mne/stats/regression.py
+++ b/mne/stats/regression.py
@@ -14,14 +14,14 @@ from scipy import linalg, sparse
 
 from ..externals.six import string_types
 from ..source_estimate import SourceEstimate
-from ..epochs import _BaseEpochs
+from ..epochs import BaseEpochs
 from ..evoked import Evoked, EvokedArray
 from ..utils import logger, _reject_data_segments, warn
 from ..io.pick import pick_types, pick_info
 
 
 def linear_regression(inst, design_matrix, names=None):
-    """Fit Ordinary Least Squares regression (OLS)
+    """Fit Ordinary Least Squares regression (OLS).
 
     Parameters
     ----------
@@ -60,7 +60,7 @@ def linear_regression(inst, design_matrix, names=None):
     if names is None:
         names = ['x%i' % i for i in range(design_matrix.shape[1])]
 
-    if isinstance(inst, _BaseEpochs):
+    if isinstance(inst, BaseEpochs):
         picks = pick_types(inst.info, meg=True, eeg=True, ref_meg=True,
                            stim=False, eog=False, ecg=False,
                            emg=False, exclude=['bads'])
@@ -90,12 +90,9 @@ def linear_regression(inst, design_matrix, names=None):
         parameters = [p[name] for p in lm_params]
         for ii, value in enumerate(parameters):
             out_ = out.copy()
-            if isinstance(out_, SourceEstimate):
-                out_._data[:] = value
-            elif isinstance(out_, Evoked):
-                out_.data[:] = value
-            else:
+            if not isinstance(out_, (SourceEstimate, Evoked)):
                 raise RuntimeError('Invalid container.')
+            out_._data[:] = value
             parameters[ii] = out_
         lm_fits[name] = lm(*parameters)
     logger.info('Done')
@@ -103,7 +100,7 @@ def linear_regression(inst, design_matrix, names=None):
 
 
 def _fit_lm(data, design_matrix, names):
-    """Aux function"""
+    """Aux function."""
     from scipy import stats
     n_samples = len(data)
     n_features = np.product(data.shape[1:])
@@ -155,7 +152,7 @@ def _fit_lm(data, design_matrix, names):
 def linear_regression_raw(raw, events, event_id=None, tmin=-.1, tmax=1,
                           covariates=None, reject=None, flat=None, tstep=1.,
                           decim=1, picks=None, solver='cholesky'):
-    """Estimate regression-based evoked potentials/fields by linear modelling
+    """Estimate regression-based evoked potentials/fields by linear modeling.
 
     This models the full M/EEG time course, including correction for
     overlapping potentials and allowing for continuous/scalar predictors.
@@ -174,10 +171,11 @@ def linear_regression_raw(raw, events, event_id=None, tmin=-.1, tmax=1,
     events : ndarray of int, shape (n_events, 3)
         An array where the first column corresponds to samples in raw
         and the last to integer codes in event_id.
-    event_id : dict
+    event_id : dict | None
         As in Epochs; a dictionary where the values may be integers or
         iterables of integers, corresponding to the 3rd column of
         events, and the keys are condition names.
+        If None, uses all events in the events array.
     tmin : float | dict
         If float, gives the lower limit (in seconds) for the time window for
         which all event types' effects are estimated. If a dict, can be used to
@@ -192,7 +190,7 @@ def linear_regression_raw(raw, events, event_id=None, tmin=-.1, tmax=1,
         used.
     covariates : dict-like | None
         If dict-like (e.g., a pandas DataFrame), values have to be array-like
-        and of the same length as the columns in ```events```. Keys correspond
+        and of the same length as the rows in ```events```. Keys correspond
         to additional event types/conditions to be estimated and are matched
         with the time points given by the first column of ```events```. If
         None, only binary events (from event_id) are used.
@@ -227,8 +225,11 @@ def linear_regression_raw(raw, events, event_id=None, tmin=-.1, tmax=1,
     solver : str | function
         Either a function which takes as its inputs the sparse predictor
         matrix X and the observation matrix Y, and returns the coefficient
-        matrix b; or a string. If str, must be ``'cholesky'``, in which case
-        the solver used is ``linalg.solve(dot(X.T, X), dot(X.T, y))``.
+        matrix b; or a string.
+        X is of shape (n_times, n_predictors * time_window_length).
+        y is of shape (n_channels, n_times).
+        If str, must be ``'cholesky'``, in which case the solver used is
+        ``linalg.solve(dot(X.T, X), dot(X.T, y))``.
 
     Returns
     -------
@@ -243,21 +244,30 @@ def linear_regression_raw(raw, events, event_id=None, tmin=-.1, tmax=1,
            waveforms: II. Non-linear effects, overlap correction, and practical
            considerations. Psychophysiology, 52(2), 169-189.
     """
-
     if isinstance(solver, string_types):
+        if solver not in {"cholesky"}:
+            raise ValueError("No such solver: {0}".format(solver))
         if solver == 'cholesky':
             def solver(X, y):
                 a = (X.T * X).toarray()  # dot product of sparse matrices
-                return linalg.solve(a, X.T * y.T, sym_pos=True,
+                return linalg.solve(a, X.T * y, sym_pos=True,
                                     overwrite_a=True, overwrite_b=True).T
-
-        else:
-            raise ValueError("No such solver: {0}".format(solver))
+    elif callable(solver):
+        warn("When using a custom solver, note that since MNE 0.15, this "
+             "function will pass the transposed data (n_channels, n_times) "
+             "to the solver. If you are using a solver that expects a "
+             "different format, it will give wrong results and might in "
+             "extreme cases crash your session.")
+    else:
+        raise TypeError("The solver must be a str or a callable.")
 
     # build data
     data, info, events = _prepare_rerp_data(raw, events, picks=picks,
                                             decim=decim)
 
+    if event_id is None:
+        event_id = dict((str(v), v) for v in set(events[:, 2]))
+
     # build predictors
     X, conds, cond_length, tmin_s, tmax_s = _prepare_rerp_preds(
         n_samples=data.shape[1], sfreq=info["sfreq"], events=events,
@@ -267,7 +277,11 @@ def linear_regression_raw(raw, events, event_id=None, tmin=-.1, tmax=1,
     X, data = _clean_rerp_input(X, data, reject, flat, decim, info, tstep)
 
     # solve linear system
-    coefs = solver(X, data)
+    coefs = solver(X, data.T)
+    if coefs.shape[0] != data.shape[0]:
+        raise ValueError("solver output has unexcepted shape. Supply a "
+                         "function that returns coefficients in the form "
+                         "(n_targets, n_features), where targets == channels.")
 
     # construct Evoked objects to be returned from output
     evokeds = _make_evokeds(coefs, conds, cond_length, tmin_s, tmax_s, info)
@@ -276,8 +290,7 @@ def linear_regression_raw(raw, events, event_id=None, tmin=-.1, tmax=1,
 
 
 def _prepare_rerp_data(raw, events, picks=None, decim=1):
-    """Prepare events and data, primarily for `linear_regression_raw`. See
-    there for an explanation of parameters and output."""
+    """Prepare events and data, primarily for `linear_regression_raw`."""
     if picks is None:
         picks = pick_types(raw.info, meg=True, eeg=True, ref_meg=True)
     info = pick_info(raw.info, picks)
@@ -305,9 +318,7 @@ def _prepare_rerp_data(raw, events, picks=None, decim=1):
 
 def _prepare_rerp_preds(n_samples, sfreq, events, event_id=None, tmin=-.1,
                         tmax=1, covariates=None):
-    """Build predictor matrix as well as metadata (e.g. condition time
-    windows), primarily for `linear_regression_raw`. See there for
-    an explanation of parameters and output."""
+    """Build predictor matrix and metadata (e.g. condition time windows)."""
     conds = list(event_id)
     if covariates is not None:
         conds += list(covariates)
@@ -362,8 +373,7 @@ def _prepare_rerp_preds(n_samples, sfreq, events, event_id=None, tmin=-.1,
 
 
 def _clean_rerp_input(X, data, reject, flat, decim, info, tstep):
-    """Remove empty and contaminated points from data and predictor matrices,
-    for `linear_regression_raw`. See there for an explanation of parameters."""
+    """Remove empty and contaminated points from data & predictor matrices."""
     # find only those positions where at least one predictor isn't 0
     has_val = np.unique(X.nonzero()[0])
 
@@ -378,9 +388,10 @@ def _clean_rerp_input(X, data, reject, flat, decim, info, tstep):
 
 
 def _make_evokeds(coefs, conds, cond_length, tmin_s, tmax_s, info):
-    """Create a dictionary of Evoked objects from a coefs matrix and condition
-    durations, primarily for `linear_regression_raw`. See there for an
-    explanation of parameters and output."""
+    """Create a dictionary of Evoked objects.
+
+    These will be created from a coefs matrix and condition durations.
+    """
     evokeds = dict()
     cumul = 0
     for cond in conds:
diff --git a/mne/stats/tests/test_cluster_level.py b/mne/stats/tests/test_cluster_level.py
index 06b5bb7..30f3085 100644
--- a/mne/stats/tests/test_cluster_level.py
+++ b/mne/stats/tests/test_cluster_level.py
@@ -1,3 +1,8 @@
+# Authors: Eric Larson <larson.eric.d at gmail.com>
+#          Alexandre Gramfort <alexandre.gramfort at telecom-paristech.fr>
+#
+# License: BSD (3-clause)
+
 from functools import partial
 import os
 import warnings
@@ -14,7 +19,7 @@ from mne.stats.cluster_level import (permutation_cluster_test,
                                      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, _TempDir, catch_logging
+from mne.utils import run_tests_if_main, _TempDir, catch_logging
 
 warnings.simplefilter('always')  # enable b/c these tests throw warnings
 
@@ -46,8 +51,7 @@ def _get_conditions():
 
 
 def test_cache_dir():
-    """Test use of 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)
@@ -80,14 +84,25 @@ def test_cache_dir():
             del os.environ['MNE_MEMMAP_MIN_SIZE']
 
 
+def test_permutation_large_n_samples():
+    """Test that non-replacement works with large N."""
+    X = np.random.RandomState(0).randn(72, 1) + 1
+    for n_samples in (11, 72):
+        tails = (0, 1) if n_samples <= 20 else (0,)
+        for tail in tails:
+            H0 = permutation_cluster_1samp_test(
+                X[:n_samples], threshold=1e-4, tail=tail)[-1]
+            assert H0.shape == (1024,)
+            assert len(np.unique(H0)) >= 1024 - (H0 == 0).sum()
+
+
 def test_permutation_step_down_p():
-    """Test cluster level permutations with step_down_p
-    """
+    """Test cluster level permutations with step_down_p."""
     try:
         try:
             from sklearn.feature_extraction.image import grid_to_graph
         except ImportError:
-            from scikits.learn.feature_extraction.image import grid_to_graph  # noqa
+            from scikits.learn.feature_extraction.image import grid_to_graph  # noqa: F401,E501 analysis:ignore
     except ImportError:
         return
     rng = np.random.RandomState(0)
@@ -114,8 +129,7 @@ def test_permutation_step_down_p():
 
 
 def test_cluster_permutation_test():
-    """Test cluster level permutations tests
-    """
+    """Test cluster level permutations tests."""
     condition1_1d, condition2_1d, condition1_2d, condition2_2d = \
         _get_conditions()
     for condition1, condition2 in zip((condition1_1d, condition1_2d),
@@ -139,10 +153,8 @@ def test_cluster_permutation_test():
         assert_array_equal(cluster_p_values, cluster_p_values_buff)
 
 
- at slow_test
 def test_cluster_permutation_t_test():
-    """Test cluster level permutations T-test
-    """
+    """Test cluster level permutations T-test."""
     condition1_1d, condition2_1d, condition1_2d, condition2_2d = \
         _get_conditions()
 
@@ -188,8 +200,7 @@ def test_cluster_permutation_t_test():
 
 
 def test_cluster_permutation_with_connectivity():
-    """Test cluster level permutations with connectivity matrix
-    """
+    """Test cluster level permutations with connectivity matrix."""
     try:
         try:
             from sklearn.feature_extraction.image import grid_to_graph
@@ -314,6 +325,10 @@ def test_cluster_permutation_with_connectivity():
         assert_raises(ValueError, spatio_temporal_func, X1d_3,
                       connectivity=connectivity, tail=-1,
                       threshold=dict(start=-1, step=1))
+        # Make sure connectivity has to be sparse
+        assert_raises(ValueError, spatio_temporal_func, X1d_3,
+                      n_permutations=50, connectivity=connectivity.todense(),
+                      max_step=1, threshold=1.67)
 
         # wrong type for threshold
         assert_raises(TypeError, spatio_temporal_func, X1d_3,
@@ -332,10 +347,8 @@ def test_cluster_permutation_with_connectivity():
         assert_true(np.min(out_connectivity_6[2]) < 0.05)
 
 
- at slow_test
 def test_permutation_connectivity_equiv():
-    """Test cluster level permutations with and without connectivity
-    """
+    """Test cluster level permutations with and without connectivity."""
     try:
         try:
             from sklearn.feature_extraction.image import grid_to_graph
@@ -351,69 +364,66 @@ def test_permutation_connectivity_equiv():
     # add some significant points
     X[:, :, 0:2] += 10  # span two time points and two spatial points
     X[:, 1, 3] += 20  # span one time point
-    max_steps = [1, 1, 1, 2]
+    max_steps = [1, 1, 1, 2, 1]
     # This will run full algorithm in two ways, then the ST-algorithm in 2 ways
     # All of these should give the same results
-    conns = [None, grid_to_graph(n_time, n_space),
-             grid_to_graph(1, n_space), grid_to_graph(1, n_space)]
+    conns = [None,
+             grid_to_graph(n_time, n_space),
+             grid_to_graph(1, n_space),
+             grid_to_graph(1, n_space),
+             None]
     stat_map = None
-    thresholds = [2, dict(start=1.5, step=1.0)]
-    sig_counts = [2, 5]
-    sdps = [0, 0.05, 0.05]
-    ots = ['mask', 'mask', 'indices']
+    thresholds = [2, 2, 2, 2, dict(start=0.01, step=1.0)]
+    sig_counts = [2, 2, 2, 2, 5]
     stat_fun = partial(ttest_1samp_no_p, sigma=1e-3)
-    for thresh, count in zip(thresholds, sig_counts):
-        cs = None
-        ps = None
-        for max_step, conn in zip(max_steps, conns):
-            for sdp, ot in zip(sdps, ots):
-                t, clusters, p, H0 = \
-                    permutation_cluster_1samp_test(
-                        X, threshold=thresh, connectivity=conn, n_jobs=2,
-                        max_step=max_step, stat_fun=stat_fun,
-                        step_down_p=sdp, out_type=ot)
-                # make sure our output datatype is correct
-                if ot == 'mask':
-                    assert_true(isinstance(clusters[0], np.ndarray))
-                    assert_true(clusters[0].dtype == bool)
-                    assert_array_equal(clusters[0].shape, X.shape[1:])
-                else:  # ot == 'indices'
-                    assert_true(isinstance(clusters[0], tuple))
-
-                # make sure all comparisons were done; for TFCE, no perm
-                # should come up empty
-                if count == 8:
-                    assert_true(not np.any(H0 == 0))
-                inds = np.where(p < 0.05)[0]
-                assert_true(len(inds) == count)
-                this_cs = [clusters[ii] for ii in inds]
-                this_ps = p[inds]
-                this_stat_map = np.zeros((n_time, n_space), dtype=bool)
-                for ci, c in enumerate(this_cs):
-                    if isinstance(c, tuple):
-                        this_c = np.zeros((n_time, n_space), bool)
-                        for x, y in zip(c[0], c[1]):
-                            this_stat_map[x, y] = True
-                            this_c[x, y] = True
-                        this_cs[ci] = this_c
-                        c = this_c
-                    this_stat_map[c] = True
-                if cs is None:
-                    ps = this_ps
-                    cs = this_cs
-                if stat_map is None:
-                    stat_map = this_stat_map
-                assert_array_equal(ps, this_ps)
-                assert_true(len(cs) == len(this_cs))
-                for c1, c2 in zip(cs, this_cs):
-                    assert_array_equal(c1, c2)
-                assert_array_equal(stat_map, this_stat_map)
-
-
- at slow_test
-def spatio_temporal_cluster_test_connectivity():
-    """Test spatio-temporal cluster permutations
-    """
+
+    cs = None
+    ps = None
+    for thresh, count, max_step, conn in zip(thresholds, sig_counts,
+                                             max_steps, conns):
+        t, clusters, p, H0 = \
+            permutation_cluster_1samp_test(
+                X, threshold=thresh, connectivity=conn, n_jobs=2,
+                max_step=max_step, stat_fun=stat_fun)
+        # make sure our output datatype is correct
+        assert_true(isinstance(clusters[0], np.ndarray))
+        assert_true(clusters[0].dtype == bool)
+        assert_array_equal(clusters[0].shape, X.shape[1:])
+
+        # make sure all comparisons were done; for TFCE, no perm
+        # should come up empty
+        inds = np.where(p < 0.05)[0]
+        assert_equal(len(inds), count)
+        if isinstance(thresh, dict):
+            assert_equal(len(clusters), n_time * n_space)
+            assert_true(np.all(H0 != 0))
+            continue
+        this_cs = [clusters[ii] for ii in inds]
+        this_ps = p[inds]
+        this_stat_map = np.zeros((n_time, n_space), dtype=bool)
+        for ci, c in enumerate(this_cs):
+            if isinstance(c, tuple):
+                this_c = np.zeros((n_time, n_space), bool)
+                for x, y in zip(c[0], c[1]):
+                    this_stat_map[x, y] = True
+                    this_c[x, y] = True
+                this_cs[ci] = this_c
+                c = this_c
+            this_stat_map[c] = True
+        if cs is None:
+            ps = this_ps
+            cs = this_cs
+        if stat_map is None:
+            stat_map = this_stat_map
+        assert_array_equal(ps, this_ps)
+        assert_true(len(cs) == len(this_cs))
+        for c1, c2 in zip(cs, this_cs):
+            assert_array_equal(c1, c2)
+        assert_array_equal(stat_map, this_stat_map)
+
+
+def test_spatio_temporal_cluster_connectivity():
+    """Test spatio-temporal cluster permutations."""
     try:
         try:
             from sklearn.feature_extraction.image import grid_to_graph
@@ -455,17 +465,21 @@ def spatio_temporal_cluster_test_connectivity():
                                      threshold=threshold, n_jobs=2,
                                      buffer_size=None)
     assert_array_equal(p_values_no_conn, p_values2)
+    assert_raises(ValueError, spatio_temporal_cluster_test,
+                  [data1_2d, data2_2d], tail=1, threshold=-2.)
+    assert_raises(ValueError, spatio_temporal_cluster_test,
+                  [data1_2d, data2_2d], tail=-1, threshold=2.)
+    assert_raises(ValueError, spatio_temporal_cluster_test,
+                  [data1_2d, data2_2d], tail=0, threshold=-1)
 
 
 def ttest_1samp(X):
-    """Returns T-values
-    """
+    """Return T-values."""
     return stats.ttest_1samp(X, 0)[0]
 
 
 def test_summarize_clusters():
-    """Test cluster summary stcs
-    """
+    """Test cluster summary stcs."""
     clu = (np.random.random([1, 20484]),
            [(np.array([0]), np.array([0, 2, 4]))],
            np.array([0.02, 0.1]),
@@ -476,4 +490,30 @@ def test_summarize_clusters():
     assert_raises(RuntimeError, summarize_clusters_stc, clu)
 
 
+def test_permutation_test_H0():
+    """Test that H0 is populated properly during testing."""
+    rng = np.random.RandomState(0)
+    data = rng.rand(7, 10, 1) - 0.5
+    with warnings.catch_warnings(record=True) as w:
+        t, clust, p, h0 = spatio_temporal_cluster_1samp_test(
+            data, threshold=100, n_permutations=1024, seed=rng)
+    assert_equal(len(w), 1)
+    assert_true('No clusters found' in str(w[0].message))
+    assert_equal(len(h0), 0)
+
+    for n_permutations in (1024, 65, 64, 63):
+        t, clust, p, h0 = spatio_temporal_cluster_1samp_test(
+            data, threshold=0.1, n_permutations=n_permutations, seed=rng)
+        assert_equal(len(h0), min(n_permutations, 64))
+        assert_true(isinstance(clust[0], tuple))  # sets of indices
+    for tail, thresh in zip((-1, 0, 1), (-0.1, 0.1, 0.1)):
+        with warnings.catch_warnings(record=True) as w:
+            t, clust, p, h0 = spatio_temporal_cluster_1samp_test(
+                data, threshold=thresh, seed=rng, tail=tail, out_type='mask')
+        assert_equal(len(w), 0)
+        assert_true(isinstance(clust[0], np.ndarray))  # bool mask
+        # same as "128 if tail else 64"
+        assert_equal(len(h0), 2 ** (7 - (tail == 0)))  # exact test
+
+
 run_tests_if_main()
diff --git a/mne/stats/tests/test_permutations.py b/mne/stats/tests/test_permutations.py
index 8ac0bac..05558ab 100644
--- a/mne/stats/tests/test_permutations.py
+++ b/mne/stats/tests/test_permutations.py
@@ -1,13 +1,18 @@
+# Authors: Alexandre Gramfort <alexandre.gramfort at telecom-paristech.fr>
+#
+# License: BSD (3-clause)
+
+from numpy.testing import (assert_array_equal, assert_almost_equal,
+                           assert_allclose)
 import numpy as np
-from numpy.testing import assert_array_equal, assert_almost_equal
 from scipy import stats
 
-from mne.stats.permutations import permutation_t_test
+from mne.stats.permutations import permutation_t_test, _ci, _bootstrap_ci
+from mne.utils import run_tests_if_main
 
 
 def test_permutation_t_test():
-    """Test T-test based on permutations
-    """
+    """Test T-test based on permutations."""
     # 1 sample t-test
     np.random.seed(10)
     n_samples, n_tests = 30, 5
@@ -31,3 +36,15 @@ def test_permutation_t_test():
     T_obs_scipy, p_values_scipy = stats.ttest_1samp(X[:, 0], 0)
     assert_almost_equal(T_obs[0], T_obs_scipy, 8)
     assert_almost_equal(p_values[0], p_values_scipy, 2)
+
+
+def test_ci():
+    # isolated test of CI functions
+    arr = np.linspace(0, 1, 1000)[..., np.newaxis]
+    assert_allclose(_ci(arr, method="parametric"),
+                    _ci(arr, method="bootstrap"), rtol=.005)
+    assert_allclose(_bootstrap_ci(arr, stat_fun="median", random_state=0),
+                    _bootstrap_ci(arr, stat_fun="mean", random_state=0),
+                    rtol=.1)
+
+run_tests_if_main()
diff --git a/mne/stats/tests/test_regression.py b/mne/stats/tests/test_regression.py
index 5b2f9a2..e0fa1fe 100644
--- a/mne/stats/tests/test_regression.py
+++ b/mne/stats/tests/test_regression.py
@@ -19,6 +19,7 @@ from mne import read_source_estimate
 from mne.datasets import testing
 from mne.stats.regression import linear_regression, linear_regression_raw
 from mne.io import RawArray
+from mne.utils import requires_sklearn
 
 warnings.simplefilter('always')
 
@@ -36,10 +37,10 @@ def test_regression():
     event_id = dict(aud_l=1, aud_r=2)
 
     # Setup for reading the raw data
-    raw = mne.io.read_raw_fif(raw_fname, add_eeg_ref=False)
+    raw = mne.io.read_raw_fif(raw_fname)
     events = mne.read_events(event_fname)[:10]
     epochs = mne.Epochs(raw, events, event_id, tmin, tmax, proj=True,
-                        baseline=(None, 0), add_eeg_ref=False)
+                        baseline=(None, 0))
     picks = np.arange(len(epochs.ch_names))
     evoked = epochs.average(picks=picks)
     design_matrix = epochs.events[:, 1:].astype(np.float64)
@@ -89,7 +90,7 @@ def test_continuous_regression_no_overlap():
     """Test regression without overlap correction, on real data."""
     tmin, tmax = -.1, .5
 
-    raw = mne.io.read_raw_fif(raw_fname, preload=True, add_eeg_ref=False)
+    raw = mne.io.read_raw_fif(raw_fname, preload=True)
     raw.apply_proj()
     events = mne.read_events(event_fname)
     event_id = dict(audio_l=1, audio_r=2)
@@ -97,7 +98,7 @@ def test_continuous_regression_no_overlap():
     raw = raw.pick_channels(raw.ch_names[:2])
 
     epochs = mne.Epochs(raw, events, event_id, tmin, tmax,
-                        baseline=None, reject=None, add_eeg_ref=False)
+                        baseline=None, reject=None)
 
     revokeds = linear_regression_raw(raw, events, event_id,
                                      tmin=tmin, tmax=tmax,
@@ -120,6 +121,8 @@ def test_continuous_regression_no_overlap():
                   events, event_id, tmin, tmax, decim=2)
 
 
+ at requires_sklearn
+ at testing.requires_testing_data
 def test_continuous_regression_with_overlap():
     """Test regression with overlap correction."""
     signal = np.zeros(100000)
@@ -132,6 +135,24 @@ def test_continuous_regression_with_overlap():
     signal = np.convolve(signal, effect)[:len(signal)]
     raw = RawArray(signal[np.newaxis, :], mne.create_info(1, 100, 'eeg'))
 
-    assert_allclose(effect,
-                    linear_regression_raw(raw, events, {1: 1}, tmin=0)[1]
-                    .data.flatten())
+    assert_allclose(effect, linear_regression_raw(
+        raw, events, {1: 1}, tmin=0)[1].data.flatten())
+
+    # test that sklearn solvers can be used
+    from sklearn.linear_model.ridge import ridge_regression
+
+    def solver(X, y):
+        return ridge_regression(X, y, alpha=0.)
+
+    with warnings.catch_warnings(record=True):  # transpose
+        assert_allclose(effect, linear_regression_raw(
+            raw, events, tmin=0, solver=solver)['1'].data.flatten())
+
+    # test bad solvers
+    def solT(X, y):
+        return ridge_regression(X, y, alpha=0.).T
+    with warnings.catch_warnings(record=True):  # transpose
+        assert_raises(ValueError, linear_regression_raw, raw, events,
+                      solver=solT)
+    assert_raises(ValueError, linear_regression_raw, raw, events, solver='err')
+    assert_raises(TypeError, linear_regression_raw, raw, events, solver=0)
diff --git a/mne/surface.py b/mne/surface.py
index 32564ad..3d0f66c 100644
--- a/mne/surface.py
+++ b/mne/surface.py
@@ -4,17 +4,17 @@
 #
 # License: BSD (3-clause)
 
+from copy import deepcopy
+from distutils.version import LooseVersion
+from glob import glob
 import os
 from os import path as op
 import sys
 from struct import pack
-from glob import glob
-from distutils.version import LooseVersion
 
 import numpy as np
 from scipy.sparse import coo_matrix, csr_matrix, eye as speye
 
-from .bem import read_bem_surfaces
 from .io.constants import FIFF
 from .io.open import fiff_open
 from .io.tree import dir_tree_find
@@ -25,7 +25,7 @@ from .channels.channels import _get_meg_system
 from .transforms import transform_surface_to
 from .utils import logger, verbose, get_subjects_dir, warn
 from .externals.six import string_types
-from .fixes import _read_volume_info, _serialize_volume_info
+from .fixes import _serialize_volume_info, _get_read_geometry
 
 
 ###############################################################################
@@ -34,7 +34,7 @@ from .fixes import _read_volume_info, _serialize_volume_info
 @verbose
 def get_head_surf(subject, source=('bem', 'head'), subjects_dir=None,
                   verbose=None):
-    """Load the subject head surface
+    """Load the subject head surface.
 
     Parameters
     ----------
@@ -51,17 +51,25 @@ def get_head_surf(subject, source=('bem', 'head'), subjects_dir=None,
         Path to the SUBJECTS_DIR. If None, the path is obtained by using
         the environment variable SUBJECTS_DIR.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
     surf : dict
         The head surface.
     """
+    return _get_head_surface(subject=subject, source=source,
+                             subjects_dir=subjects_dir)
+
+
+def _get_head_surface(subject, source, subjects_dir, raise_error=True):
+    """Load the subject head surface."""
+    from .bem import read_bem_surfaces
     # Load the head surface from the BEM
     subjects_dir = get_subjects_dir(subjects_dir, raise_error=True)
     if not isinstance(subject, string_types):
-        raise TypeError('subject must be a string, not %s' % (type(subject,)))
+        raise TypeError('subject must be a string, not %s.' % (type(subject,)))
     # use realpath to allow for linked surfaces (c.f. MNE manual 196-197)
     if isinstance(source, string_types):
         source = [source]
@@ -77,7 +85,7 @@ def get_head_surf(subject, source=('bem', 'head'), subjects_dir=None,
             # let's do a more sophisticated search
             path = op.join(subjects_dir, subject, 'bem')
             if not op.isdir(path):
-                raise IOError('Subject bem directory "%s" does not exist'
+                raise IOError('Subject bem directory "%s" does not exist.'
                               % path)
             files = sorted(glob(op.join(path, '%s*%s.fif'
                                         % (subject, this_source))))
@@ -94,15 +102,18 @@ def get_head_surf(subject, source=('bem', 'head'), subjects_dir=None,
             break
 
     if surf is None:
-        raise IOError('No file matching "%s*%s" and containing a head '
-                      'surface found' % (subject, this_source))
-    logger.info('Using surface from %s' % this_head)
+        if raise_error:
+            raise IOError('No file matching "%s*%s" and containing a head '
+                          'surface found.' % (subject, this_source))
+        else:
+            return surf
+    logger.info('Using surface from %s.' % this_head)
     return surf
 
 
 @verbose
 def get_meg_helmet_surf(info, trans=None, verbose=None):
-    """Load the MEG helmet associated with the MEG sensors
+    """Load the MEG helmet associated with the MEG sensors.
 
     Parameters
     ----------
@@ -113,13 +124,15 @@ def get_meg_helmet_surf(info, trans=None, verbose=None):
         read_trans(). Can be None, in which case the surface will
         be in head coordinates instead of MRI coordinates.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
     surf : dict
         The MEG helmet as a surface.
     """
+    from .bem import read_bem_surfaces
     system = _get_meg_system(info)
     logger.info('Getting helmet for system %s' % system)
     fname = op.join(op.split(__file__)[0], 'data', 'helmets',
@@ -139,7 +152,7 @@ def get_meg_helmet_surf(info, trans=None, verbose=None):
 # EFFICIENCY UTILITIES
 
 def fast_cross_3d(x, y):
-    """Compute cross product between list of 3D vectors
+    """Compute cross product between list of 3D vectors.
 
     Much faster than np.cross() when the number of cross products
     becomes large (>500). This is because np.cross() methods become
@@ -176,14 +189,14 @@ def fast_cross_3d(x, y):
 
 
 def _fast_cross_nd_sum(a, b, c):
-    """Fast cross and sum"""
+    """Fast cross and sum."""
     return ((a[..., 1] * b[..., 2] - a[..., 2] * b[..., 1]) * c[..., 0] +
             (a[..., 2] * b[..., 0] - a[..., 0] * b[..., 2]) * c[..., 1] +
             (a[..., 0] * b[..., 1] - a[..., 1] * b[..., 0]) * c[..., 2])
 
 
 def _accumulate_normals(tris, tri_nn, npts):
-    """Efficiently accumulate triangle normals"""
+    """Efficiently accumulate triangle normals."""
     # this code replaces the following, but is faster (vectorized):
     #
     # this['nn'] = np.zeros((this['np'], 3))
@@ -200,7 +213,7 @@ def _accumulate_normals(tris, tri_nn, npts):
 
 
 def _triangle_neighbors(tris, npts):
-    """Efficiently compute vertex neighboring triangles"""
+    """Efficiently compute vertex neighboring triangles."""
     # this code replaces the following, but is faster (vectorized):
     #
     # this['neighbor_tri'] = [list() for _ in xrange(this['np'])]
@@ -223,7 +236,7 @@ def _triangle_neighbors(tris, npts):
 
 
 def _triangle_coords(r, geom, best):
-    """Get coordinates of a vertex projected to a triangle"""
+    """Get coordinates of a vertex projected to a triangle."""
     r1 = geom['r1'][best]
     tri_nn = geom['nn'][best]
     r12 = geom['r12'][best]
@@ -241,57 +254,99 @@ def _triangle_coords(r, geom, best):
     return x, y, z
 
 
+def _project_onto_surface(rrs, surf, project_rrs=False, return_nn=False):
+    """Project points onto (scalp) surface."""
+    surf_geom = _get_tri_supp_geom(surf)
+    coords = np.empty((len(rrs), 3))
+    tri_idx = np.empty((len(rrs),), int)
+    for ri, rr in enumerate(rrs):
+        # Get index of closest tri on scalp BEM to electrode position
+        tri_idx[ri] = _find_nearest_tri_pt(rr, surf_geom)[2]
+        # Calculate a linear interpolation between the vertex values to
+        # get coords of pt projected onto closest triangle
+        coords[ri] = _triangle_coords(rr, surf_geom, tri_idx[ri])
+    weights = np.array([1. - coords[:, 0] - coords[:, 1], coords[:, 0],
+                       coords[:, 1]])
+    out = (weights, tri_idx)
+    if project_rrs:  #
+        out += (np.einsum('ij,jik->jk', weights,
+                          surf['rr'][surf['tris'][tri_idx]]),)
+    if return_nn:
+        out += (surf_geom['nn'][tri_idx],)
+    return out
+
+
 @verbose
-def _complete_surface_info(this, do_neighbor_vert=False, verbose=None):
-    """Complete surface info"""
+def complete_surface_info(surf, do_neighbor_vert=False, copy=True,
+                          verbose=None):
+    """Complete surface information.
+
+    Parameters
+    ----------
+    surf : dict
+        The surface.
+    do_neighbor_vert : bool
+        If True, add neighbor vertex information.
+    copy : bool
+        If True (default), make a copy. If False, operate in-place.
+    verbose : bool, str, int, or None
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
+
+    Returns
+    -------
+    surf : dict
+        The transformed surface.
+    """
+    if copy:
+        surf = deepcopy(surf)
     # based on mne_source_space_add_geometry_info() in mne_add_geometry_info.c
 
     #   Main triangulation [mne_add_triangle_data()]
-    this['tri_area'] = np.zeros(this['ntri'])
-    r1 = this['rr'][this['tris'][:, 0], :]
-    r2 = this['rr'][this['tris'][:, 1], :]
-    r3 = this['rr'][this['tris'][:, 2], :]
-    this['tri_cent'] = (r1 + r2 + r3) / 3.0
-    this['tri_nn'] = fast_cross_3d((r2 - r1), (r3 - r1))
+    surf['ntri'] = surf.get('ntri', len(surf['tris']))
+    surf['np'] = surf.get('np', len(surf['rr']))
+    surf['tri_area'] = np.zeros(surf['ntri'])
+    r1 = surf['rr'][surf['tris'][:, 0], :]
+    r2 = surf['rr'][surf['tris'][:, 1], :]
+    r3 = surf['rr'][surf['tris'][:, 2], :]
+    surf['tri_cent'] = (r1 + r2 + r3) / 3.0
+    surf['tri_nn'] = fast_cross_3d((r2 - r1), (r3 - r1))
 
     #   Triangle normals and areas
-    size = np.sqrt(np.sum(this['tri_nn'] ** 2, axis=1))
-    this['tri_area'] = size / 2.0
-    zidx = np.where(size == 0)[0]
-    for idx in zidx:
-        logger.info('    Warning: zero size triangle # %s' % idx)
-    size[zidx] = 1.0  # prevent ugly divide-by-zero
-    this['tri_nn'] /= size[:, None]
+    surf['tri_area'] = _normalize_vectors(surf['tri_nn']) / 2.0
+    zidx = np.where(surf['tri_area'] == 0)[0]
+    if len(zidx) > 0:
+        logger.info('    Warning: zero size triangles: %s' % zidx)
 
     #    Find neighboring triangles, accumulate vertex normals, normalize
     logger.info('    Triangle neighbors and vertex normals...')
-    this['neighbor_tri'] = _triangle_neighbors(this['tris'], this['np'])
-    this['nn'] = _accumulate_normals(this['tris'], this['tri_nn'], this['np'])
-    _normalize_vectors(this['nn'])
+    surf['neighbor_tri'] = _triangle_neighbors(surf['tris'], surf['np'])
+    surf['nn'] = _accumulate_normals(surf['tris'], surf['tri_nn'], surf['np'])
+    _normalize_vectors(surf['nn'])
 
     #   Check for topological defects
-    idx = np.where([len(n) == 0 for n in this['neighbor_tri']])[0]
+    idx = np.where([len(n) == 0 for n in surf['neighbor_tri']])[0]
     if len(idx) > 0:
         logger.info('    Vertices [%s] do not have any neighboring'
                     'triangles!' % ','.join([str(ii) for ii in idx]))
-    idx = np.where([len(n) < 3 for n in this['neighbor_tri']])[0]
+    idx = np.where([len(n) < 3 for n in surf['neighbor_tri']])[0]
     if len(idx) > 0:
         logger.info('    Vertices [%s] have fewer than three neighboring '
                     'tris, omitted' % ','.join([str(ii) for ii in idx]))
-    for k in idx:
-        this['neighbor_tri'] = np.array([], int)
+        for k in idx:
+            surf['neighbor_tri'][k] = np.array([], int)
 
     #   Determine the neighboring vertices and fix errors
     if do_neighbor_vert is True:
         logger.info('    Vertex neighbors...')
-        this['neighbor_vert'] = [_get_surf_neighbors(this, k)
-                                 for k in range(this['np'])]
+        surf['neighbor_vert'] = [_get_surf_neighbors(surf, k)
+                                 for k in range(surf['np'])]
 
-    return this
+    return surf
 
 
 def _get_surf_neighbors(surf, k):
-    """Calculate the surface neighbors based on triangulation"""
+    """Calculate the surface neighbors based on triangulation."""
     verts = surf['tris'][surf['neighbor_tri'][k]]
     verts = np.setdiff1d(verts, [k], assume_unique=False)
     assert np.all(verts < surf['np'])
@@ -307,14 +362,15 @@ def _get_surf_neighbors(surf, k):
 
 
 def _normalize_vectors(rr):
-    """Normalize surface vertices"""
-    size = np.sqrt(np.sum(rr * rr, axis=1))
-    size[size == 0] = 1.0  # avoid divide-by-zero
-    rr /= size[:, np.newaxis]  # operate in-place
+    """Normalize surface vertices."""
+    size = np.linalg.norm(rr, axis=1)
+    mask = (size > 0)
+    rr[mask] /= size[mask, np.newaxis]  # operate in-place
+    return size
 
 
 def _compute_nearest(xhs, rr, use_balltree=True, return_dists=False):
-    """Find nearest neighbors
+    """Find nearest neighbors.
 
     Note: The rows in xhs and rr must all be unit-length vectors, otherwise
     the result will be incorrect.
@@ -379,7 +435,7 @@ def _compute_nearest(xhs, rr, use_balltree=True, return_dists=False):
 # Handle freesurfer
 
 def _fread3(fobj):
-    """Docstring"""
+    """Read 3 bytes and adjust."""
     b1, b2, b3 = np.fromfile(fobj, ">u1", 3)
     return (b1 << 16) + (b2 << 8) + b3
 
@@ -407,8 +463,8 @@ def read_curvature(filepath):
 
 
 @verbose
-def read_surface(fname, read_metadata=False, verbose=None):
-    """Load a Freesurfer surface mesh in triangular format
+def read_surface(fname, read_metadata=False, return_dict=False, verbose=None):
+    """Load a Freesurfer surface mesh in triangular format.
 
     Parameters
     ----------
@@ -430,8 +486,11 @@ def read_surface(fname, read_metadata=False, verbose=None):
 
         .. versionadded:: 0.13.0
 
+    return_dict : bool
+        If True, a dictionary with surface parameters is returned.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -442,103 +501,29 @@ def read_surface(fname, read_metadata=False, verbose=None):
         together form a face).
     volume_info : dict-like
         If read_metadata is true, key-value pairs found in the geometry file.
+    surf : dict
+        The surface parameters. Only returned if ``return_dict`` is True.
 
     See Also
     --------
     write_surface
     read_tri
     """
-    try:
-        import nibabel as nib
-        has_nibabel = True
-    except ImportError:
-        has_nibabel = False
-    if has_nibabel and LooseVersion(nib.__version__) > LooseVersion('2.1.0'):
-        return nib.freesurfer.read_geometry(fname, read_metadata=read_metadata)
-
-    volume_info = dict()
-    TRIANGLE_MAGIC = 16777214
-    QUAD_MAGIC = 16777215
-    NEW_QUAD_MAGIC = 16777213
-    with open(fname, "rb", buffering=0) as fobj:  # buffering=0 for np bug
-        magic = _fread3(fobj)
-        # Quad file or new quad
-        if magic in (QUAD_MAGIC, NEW_QUAD_MAGIC):
-            create_stamp = ''
-            nvert = _fread3(fobj)
-            nquad = _fread3(fobj)
-            (fmt, div) = (">i2", 100.) if magic == QUAD_MAGIC else (">f4", 1.)
-            coords = np.fromfile(fobj, fmt, nvert * 3).astype(np.float) / div
-            coords = coords.reshape(-1, 3)
-            quads = _fread3_many(fobj, nquad * 4)
-            quads = quads.reshape(nquad, 4)
-
-            # Face splitting follows
-            faces = np.zeros((2 * nquad, 3), dtype=np.int)
-            nface = 0
-            for quad in quads:
-                if (quad[0] % 2) == 0:
-                    faces[nface:nface + 2] = [[quad[0], quad[1], quad[3]],
-                                              [quad[2], quad[3], quad[1]]]
-                else:
-                    faces[nface:nface + 2] = [[quad[0], quad[1], quad[2]],
-                                              [quad[0], quad[2], quad[3]]]
-                nface += 2
-        elif magic == TRIANGLE_MAGIC:  # Triangle file
-            create_stamp = fobj.readline()
-            fobj.readline()
-            vnum = np.fromfile(fobj, ">i4", 1)[0]
-            fnum = np.fromfile(fobj, ">i4", 1)[0]
-            coords = np.fromfile(fobj, ">f4", vnum * 3).reshape(vnum, 3)
-            faces = np.fromfile(fobj, ">i4", fnum * 3).reshape(fnum, 3)
-            if read_metadata:
-                volume_info = _read_volume_info(fobj)
-        else:
-            raise ValueError("%s does not appear to be a Freesurfer surface"
-                             % fname)
-        logger.info('Triangle file: %s nvert = %s ntri = %s'
-                    % (create_stamp.strip(), len(coords), len(faces)))
-
-    coords = coords.astype(np.float)  # XXX: due to mayavi bug on mac 32bits
-
-    ret = (coords, faces)
-    if read_metadata:
-        if len(volume_info) == 0:
-            warn('No volume information contained in the file')
-        ret += (volume_info,)
+    ret = _get_read_geometry()(fname, read_metadata=read_metadata)
+    if return_dict:
+        ret += (dict(rr=ret[0], tris=ret[1], ntri=len(ret[1]), use_tris=ret[1],
+                     np=len(ret[0])),)
     return ret
 
 
- at verbose
-def _read_surface_geom(fname, patch_stats=True, norm_rr=False,
-                       read_metadata=False, verbose=None):
-    """Load the surface as dict, optionally add the geometry information"""
-    # based on mne_load_surface_geom() in mne_surface_io.c
-    if isinstance(fname, string_types):
-        ret = read_surface(fname, read_metadata=read_metadata)
-        nvert = len(ret[0])
-        ntri = len(ret[1])
-        s = dict(rr=ret[0], tris=ret[1], use_tris=ret[1], ntri=ntri, np=nvert)
-    elif isinstance(fname, dict):
-        s = fname
-    else:
-        raise RuntimeError('fname cannot be understood as str or dict')
-    if patch_stats is True:
-        s = _complete_surface_info(s)
-    if norm_rr is True:
-        _normalize_vectors(s['rr'])
-    if read_metadata:
-        return s, ret[2]
-    return s
-
-
 ##############################################################################
 # SURFACE CREATION
 
 def _get_ico_surface(grade, patch_stats=False):
-    """Return an icosahedral surface of the desired grade"""
+    """Return an icosahedral surface of the desired grade."""
     # always use verbose=False since users don't need to know we're pulling
     # these from a file
+    from .bem import read_bem_surfaces
     ico_file_name = op.join(op.dirname(__file__), 'data',
                             'icos.fif.gz')
     ico = read_bem_surfaces(ico_file_name, patch_stats, s_id=9000 + grade,
@@ -547,26 +532,27 @@ def _get_ico_surface(grade, patch_stats=False):
 
 
 def _tessellate_sphere_surf(level, rad=1.0):
-    """Return a surface structure instead of the details"""
+    """Return a surface structure instead of the details."""
     rr, tris = _tessellate_sphere(level)
     npt = len(rr)  # called "npt" instead of "np" because of numpy...
     ntri = len(tris)
     nn = rr.copy()
     rr *= rad
-    s = dict(rr=rr, np=npt, tris=tris, use_tris=tris, ntri=ntri, nuse=np,
+    s = dict(rr=rr, np=npt, tris=tris, use_tris=tris, ntri=ntri, nuse=npt,
              nn=nn, inuse=np.ones(npt, int))
     return s
 
 
 def _norm_midpt(ai, bi, rr):
-    a = np.array([rr[aii] for aii in ai])
-    b = np.array([rr[bii] for bii in bi])
-    c = (a + b) / 2.
-    return c / np.sqrt(np.sum(c ** 2, 1))[:, np.newaxis]
+    """Get normalized midpoint."""
+    c = rr[ai]
+    c += rr[bi]
+    _normalize_vectors(c)
+    return c
 
 
 def _tessellate_sphere(mylevel):
-    """Create a tessellation of a unit sphere"""
+    """Create a tessellation of a unit sphere."""
     # Vertices of a unit octahedron
     rr = np.array([[1, 0, 0], [-1, 0, 0],  # xplus, xminus
                    [0, 1, 0], [0, -1, 0],  # yplus, yminus
@@ -584,7 +570,7 @@ def _tessellate_sphere(mylevel):
 
     # Subdivide each starting triangle (mylevel - 1) times
     for _ in range(1, mylevel):
-        """
+        r"""
         Subdivide each triangle in the old approximation and normalize
         the new points thus generated to lie on the surface of the unit
         sphere.
@@ -643,26 +629,27 @@ def _tessellate_sphere(mylevel):
     return rr, tris
 
 
-def _create_surf_spacing(surf, hemi, subject, stype, sval, ico_surf,
-                         subjects_dir):
-    """Load a surf and use the subdivided icosahedron to get points"""
+def _create_surf_spacing(surf, hemi, subject, stype, ico_surf, subjects_dir):
+    """Load a surf and use the subdivided icosahedron to get points."""
     # Based on load_source_space_surf_spacing() in load_source_space.c
-    surf = _read_surface_geom(surf)
-
-    if stype in ['ico', 'oct']:
+    surf = read_surface(surf, return_dict=True)[-1]
+    complete_surface_info(surf, copy=False)
+    if stype == 'all':
+        surf['inuse'] = np.ones(surf['np'], int)
+        surf['use_tris'] = None
+    else:  # ico or oct
         # ## from mne_ico_downsample.c ## #
         surf_name = op.join(subjects_dir, subject, 'surf', hemi + '.sphere')
         logger.info('Loading geometry from %s...' % surf_name)
-        from_surf = _read_surface_geom(surf_name, norm_rr=True,
-                                       patch_stats=False)
-        if not len(from_surf['rr']) == surf['np']:
+        from_surf = read_surface(surf_name, return_dict=True)[-1]
+        complete_surface_info(from_surf, copy=False)
+        _normalize_vectors(from_surf['rr'])
+        if from_surf['np'] != surf['np']:
             raise RuntimeError('Mismatch between number of surface vertices, '
                                'possible parcellation error?')
         _normalize_vectors(ico_surf['rr'])
 
         # Make the maps
-        logger.info('Mapping %s %s -> %s (%d) ...'
-                    % (hemi, subject, stype, sval))
         mmap = _compute_nearest(from_surf['rr'], ico_surf['rr'])
         nmap = len(mmap)
         surf['inuse'] = np.zeros(surf['np'], int)
@@ -690,9 +677,6 @@ def _create_surf_spacing(surf, hemi, subject, stype, sval, ico_surf,
                     'surface...')
         surf['use_tris'] = np.array([mmap[ist] for ist in ico_surf['tris']],
                                     np.int32)
-    else:  # use_all is True
-        surf['inuse'] = np.ones(surf['np'], int)
-        surf['use_tris'] = None
     if surf['use_tris'] is not None:
         surf['nuse_tri'] = len(surf['use_tris'])
     else:
@@ -702,8 +686,7 @@ def _create_surf_spacing(surf, hemi, subject, stype, sval, ico_surf,
 
     # set some final params
     inds = np.arange(surf['np'])
-    sizes = np.sqrt(np.sum(surf['nn'] ** 2, axis=1))
-    surf['nn'][inds] = surf['nn'][inds] / sizes[:, np.newaxis]
+    sizes = _normalize_vectors(surf['nn'])
     surf['inuse'][sizes <= 0] = False
     surf['nuse'] = np.sum(surf['inuse'])
     surf['subject_his_id'] = subject
@@ -711,7 +694,7 @@ def _create_surf_spacing(surf, hemi, subject, stype, sval, ico_surf,
 
 
 def write_surface(fname, coords, faces, create_stamp='', volume_info=None):
-    """Write a triangular Freesurfer surface mesh
+    """Write a triangular Freesurfer surface mesh.
 
     Accepts the same data format as is returned by read_surface().
 
@@ -781,7 +764,7 @@ def write_surface(fname, coords, faces, create_stamp='', volume_info=None):
 # Decimation
 
 def _decimate_surface(points, triangles, reduction):
-    """Aux function"""
+    """Aux function."""
     if 'DISPLAY' not in os.environ and sys.platform != 'win32':
         os.environ['ETS_TOOLKIT'] = 'null'
     try:
@@ -800,11 +783,11 @@ def _decimate_surface(points, triangles, reduction):
     out = decimate.output
     tris = out.polys.to_array()
     # n-tuples + interleaved n-next -- reshape trick
-    return out.points.to_array(), tris.reshape(tris.size / 4, 4)[:, 1:]
+    return out.points.to_array(), tris.reshape(tris.size // 4, 4)[:, 1:]
 
 
 def decimate_surface(points, triangles, n_triangles):
-    """ Decimate surface data
+    """Decimate surface data.
 
     Note. Requires TVTK to be installed for this to function.
 
@@ -829,7 +812,6 @@ def decimate_surface(points, triangles, n_triangles):
     triangles : ndarray
         The decimated triangles.
     """
-
     reduction = 1 - (float(n_triangles) / len(triangles))
     return _decimate_surface(points, triangles, reduction)
 
@@ -838,9 +820,9 @@ def decimate_surface(points, triangles, n_triangles):
 # Morph maps
 
 @verbose
-def read_morph_map(subject_from, subject_to, subjects_dir=None,
+def read_morph_map(subject_from, subject_to, subjects_dir=None, xhemi=False,
                    verbose=None):
-    """Read morph map
+    """Read morph map.
 
     Morph maps can be generated with mne_make_morph_maps. If one isn't
     available, it will be generated automatically and saved to the
@@ -854,8 +836,13 @@ def read_morph_map(subject_from, subject_to, subjects_dir=None,
         Name of the subject on which to morph as named in the SUBJECTS_DIR.
     subjects_dir : string
         Path to SUBJECTS_DIR is not set in the environment.
+    xhemi : bool
+        Morph across hemisphere. Currently only implemented for
+        ``subject_to == subject_from``. See notes at
+        :func:`mne.compute_morph_matrix`.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -872,28 +859,43 @@ def read_morph_map(subject_from, subject_to, subjects_dir=None,
         except Exception:
             warn('Could not find or make morph map directory "%s"' % mmap_dir)
 
-    # Does the file exist
-    fname = op.join(mmap_dir, '%s-%s-morph.fif' % (subject_from, subject_to))
-    if not op.exists(fname):
-        fname = op.join(mmap_dir, '%s-%s-morph.fif'
-                        % (subject_to, subject_from))
-        if not op.exists(fname):
-            warn('Morph map "%s" does not exist, creating it and saving it to '
-                 'disk (this may take a few minutes)' % fname)
-            logger.info('Creating morph map %s -> %s'
-                        % (subject_from, subject_to))
-            mmap_1 = _make_morph_map(subject_from, subject_to, subjects_dir)
-            logger.info('Creating morph map %s -> %s'
-                        % (subject_to, subject_from))
-            mmap_2 = _make_morph_map(subject_to, subject_from, subjects_dir)
-            try:
-                _write_morph_map(fname, subject_from, subject_to,
-                                 mmap_1, mmap_2)
-            except Exception as exp:
-                warn('Could not write morph-map file "%s" (error: %s)'
-                     % (fname, exp))
-            return mmap_1
+    # filename components
+    if xhemi:
+        if subject_to != subject_from:
+            raise NotImplementedError(
+                "Morph-maps between hemispheres are currently only "
+                "implemented for subject_to == subject_from")
+        map_name_temp = '%s-%s-xhemi'
+        log_msg = 'Creating morph map %s -> %s xhemi'
+    else:
+        map_name_temp = '%s-%s'
+        log_msg = 'Creating morph map %s -> %s'
+
+    map_names = [map_name_temp % (subject_from, subject_to),
+                 map_name_temp % (subject_to, subject_from)]
+
+    # find existing file
+    for map_name in map_names:
+        fname = op.join(mmap_dir, '%s-morph.fif' % map_name)
+        if op.exists(fname):
+            return _read_morph_map(fname, subject_from, subject_to)
+    # if file does not exist, make it
+    warn('Morph map "%s" does not exist, creating it and saving it to '
+         'disk (this may take a few minutes)' % fname)
+    logger.info(log_msg % (subject_from, subject_to))
+    mmap_1 = _make_morph_map(subject_from, subject_to, subjects_dir, xhemi)
+    if subject_to == subject_from:
+        mmap_2 = None
+    else:
+        logger.info(log_msg % (subject_to, subject_from))
+        mmap_2 = _make_morph_map(subject_to, subject_from, subjects_dir,
+                                 xhemi)
+    _write_morph_map(fname, subject_from, subject_to, mmap_1, mmap_2)
+    return mmap_1
+
 
+def _read_morph_map(fname, subject_from, subject_to):
+    """Read a morph map from disk."""
     f, tree, _ = fiff_open(fname)
     with f as fid:
         # Locate all maps
@@ -927,10 +929,15 @@ def read_morph_map(subject_from, subject_to, subjects_dir=None,
 
 
 def _write_morph_map(fname, subject_from, subject_to, mmap_1, mmap_2):
-    """Write a morph map to disk"""
-    fid = start_file(fname)
+    """Write a morph map to disk."""
+    try:
+        fid = start_file(fname)
+    except Exception as exp:
+        warn('Could not write morph-map file "%s" (error: %s)'
+             % (fname, exp))
+        return
+
     assert len(mmap_1) == 2
-    assert len(mmap_2) == 2
     hemis = [FIFF.FIFFV_MNE_SURF_LEFT_HEMI, FIFF.FIFFV_MNE_SURF_RIGHT_HEMI]
     for m, hemi in zip(mmap_1, hemis):
         start_block(fid, FIFF.FIFFB_MNE_MORPH_MAP)
@@ -939,33 +946,39 @@ def _write_morph_map(fname, subject_from, subject_to, mmap_1, mmap_2):
         write_int(fid, FIFF.FIFF_MNE_HEMI, hemi)
         write_float_sparse_rcs(fid, FIFF.FIFF_MNE_MORPH_MAP, m)
         end_block(fid, FIFF.FIFFB_MNE_MORPH_MAP)
-    for m, hemi in zip(mmap_2, hemis):
-        start_block(fid, FIFF.FIFFB_MNE_MORPH_MAP)
-        write_string(fid, FIFF.FIFF_MNE_MORPH_MAP_FROM, subject_to)
-        write_string(fid, FIFF.FIFF_MNE_MORPH_MAP_TO, subject_from)
-        write_int(fid, FIFF.FIFF_MNE_HEMI, hemi)
-        write_float_sparse_rcs(fid, FIFF.FIFF_MNE_MORPH_MAP, m)
-        end_block(fid, FIFF.FIFFB_MNE_MORPH_MAP)
+    # don't write mmap_2 if it is identical (subject_to == subject_from)
+    if mmap_2 is not None:
+        assert len(mmap_2) == 2
+        for m, hemi in zip(mmap_2, hemis):
+            start_block(fid, FIFF.FIFFB_MNE_MORPH_MAP)
+            write_string(fid, FIFF.FIFF_MNE_MORPH_MAP_FROM, subject_to)
+            write_string(fid, FIFF.FIFF_MNE_MORPH_MAP_TO, subject_from)
+            write_int(fid, FIFF.FIFF_MNE_HEMI, hemi)
+            write_float_sparse_rcs(fid, FIFF.FIFF_MNE_MORPH_MAP, m)
+            end_block(fid, FIFF.FIFFB_MNE_MORPH_MAP)
     end_file(fid)
 
 
 def _get_tri_dist(p, q, p0, q0, a, b, c, dist):
-    """Auxiliary function for getting the distance to a triangle edge"""
-    return np.sqrt((p - p0) * (p - p0) * a +
-                   (q - q0) * (q - q0) * b +
-                   (p - p0) * (q - q0) * c +
-                   dist * dist)
-
-
-def _get_tri_supp_geom(tris, rr):
-    """Create supplementary geometry information using tris and rrs"""
-    r1 = rr[tris[:, 0], :]
-    r12 = rr[tris[:, 1], :] - r1
-    r13 = rr[tris[:, 2], :] - r1
+    """Get the distance to a triangle edge."""
+    p1 = p - p0
+    q1 = q - q0
+    out = p1 * p1 * a
+    out += q1 * q1 * b
+    out += p1 * q1 * c
+    out += dist * dist
+    return np.sqrt(out, out=out)
+
+
+def _get_tri_supp_geom(surf):
+    """Create supplementary geometry information using tris and rrs."""
+    r1 = surf['rr'][surf['tris'][:, 0], :]
+    r12 = surf['rr'][surf['tris'][:, 1], :] - r1
+    r13 = surf['rr'][surf['tris'][:, 2], :] - r1
     r1213 = np.array([r12, r13]).swapaxes(0, 1)
-    a = np.sum(r12 * r12, axis=1)
-    b = np.sum(r13 * r13, axis=1)
-    c = np.sum(r12 * r13, axis=1)
+    a = np.einsum('ij,ij->i', r12, r12)
+    b = np.einsum('ij,ij->i', r13, r13)
+    c = np.einsum('ij,ij->i', r12, r13)
     mat = np.rollaxis(np.array([[b, -c], [-c, a]]), 2)
     mat /= (a * b - c * c)[:, np.newaxis, np.newaxis]
     nn = fast_cross_3d(r12, r13)
@@ -974,9 +987,8 @@ def _get_tri_supp_geom(tris, rr):
                 a=a, b=b, c=c, mat=mat, nn=nn)
 
 
- at verbose
-def _make_morph_map(subject_from, subject_to, subjects_dir=None):
-    """Construct morph map from one subject to another
+def _make_morph_map(subject_from, subject_to, subjects_dir, xhemi):
+    """Construct morph map from one subject to another.
 
     Note that this is close, but not exactly like the C version.
     For example, parts are more accurate due to double precision,
@@ -987,57 +999,61 @@ def _make_morph_map(subject_from, subject_to, subjects_dir=None):
     than just running on a single core :(
     """
     subjects_dir = get_subjects_dir(subjects_dir)
-    morph_maps = list()
+    if xhemi:
+        reg = '%s.sphere.left_right'
+        hemis = (('lh', 'rh'), ('rh', 'lh'))
+    else:
+        reg = '%s.sphere.reg'
+        hemis = (('lh', 'lh'), ('rh', 'rh'))
+
+    return [_make_morph_map_hemi(subject_from, subject_to, subjects_dir,
+                                 reg % hemi_from, reg % hemi_to)
+            for hemi_from, hemi_to in hemis]
+
 
+def _make_morph_map_hemi(subject_from, subject_to, subjects_dir, reg_from,
+                         reg_to):
+    """Construct morph map for one hemisphere."""
     # add speedy short-circuit for self-maps
-    if subject_from == subject_to:
-        for hemi in ['lh', 'rh']:
-            fname = op.join(subjects_dir, subject_from, 'surf',
-                            '%s.sphere.reg' % hemi)
-            from_pts = read_surface(fname, verbose=False)[0]
-            n_pts = len(from_pts)
-            morph_maps.append(speye(n_pts, n_pts, format='csr'))
-        return morph_maps
-
-    for hemi in ['lh', 'rh']:
-        # load surfaces and normalize points to be on unit sphere
-        fname = op.join(subjects_dir, subject_from, 'surf',
-                        '%s.sphere.reg' % hemi)
-        from_pts, from_tris = read_surface(fname, verbose=False)
-        n_from_pts = len(from_pts)
-        _normalize_vectors(from_pts)
-        tri_geom = _get_tri_supp_geom(from_tris, from_pts)
-
-        fname = op.join(subjects_dir, subject_to, 'surf',
-                        '%s.sphere.reg' % hemi)
-        to_pts = read_surface(fname, verbose=False)[0]
-        n_to_pts = len(to_pts)
-        _normalize_vectors(to_pts)
-
-        # from surface: get nearest neighbors, find triangles for each vertex
-        nn_pts_idx = _compute_nearest(from_pts, to_pts)
-        from_pt_tris = _triangle_neighbors(from_tris, len(from_pts))
-        from_pt_tris = [from_pt_tris[pt_idx] for pt_idx in nn_pts_idx]
-
-        # find triangle in which point lies and assoc. weights
-        nn_tri_inds = []
-        nn_tris_weights = []
-        for pt_tris, to_pt in zip(from_pt_tris, to_pts):
-            p, q, idx, dist = _find_nearest_tri_pt(pt_tris, to_pt, tri_geom)
-            nn_tri_inds.append(idx)
-            nn_tris_weights.extend([1. - (p + q), p, q])
-
-        nn_tris = from_tris[nn_tri_inds]
-        row_ind = np.repeat(np.arange(n_to_pts), 3)
-        this_map = csr_matrix((nn_tris_weights, (row_ind, nn_tris.ravel())),
-                              shape=(n_to_pts, n_from_pts))
-        morph_maps.append(this_map)
-
-    return morph_maps
-
-
-def _find_nearest_tri_pt(pt_tris, to_pt, tri_geom, run_all=False):
-    """Find nearest point mapping to a set of triangles
+    if subject_from == subject_to and reg_from == reg_to:
+        fname = op.join(subjects_dir, subject_from, 'surf', reg_from)
+        n_pts = len(read_surface(fname, verbose=False)[0])
+        return speye(n_pts, n_pts, format='csr')
+
+    # load surfaces and normalize points to be on unit sphere
+    fname = op.join(subjects_dir, subject_from, 'surf', reg_from)
+    from_rr, from_tri = read_surface(fname, verbose=False)
+    fname = op.join(subjects_dir, subject_to, 'surf', reg_to)
+    to_rr = read_surface(fname, verbose=False)[0]
+    _normalize_vectors(from_rr)
+    _normalize_vectors(to_rr)
+
+    # from surface: get nearest neighbors, find triangles for each vertex
+    nn_pts_idx = _compute_nearest(from_rr, to_rr)
+    from_pt_tris = _triangle_neighbors(from_tri, len(from_rr))
+    from_pt_tris = [from_pt_tris[pt_idx] for pt_idx in nn_pts_idx]
+
+    # find triangle in which point lies and assoc. weights
+    tri_inds = []
+    weights = []
+    tri_geom = _get_tri_supp_geom(dict(rr=from_rr, tris=from_tri))
+    for pt_tris, to_pt in zip(from_pt_tris, to_rr):
+        p, q, idx, dist = _find_nearest_tri_pt(to_pt, tri_geom, pt_tris,
+                                               run_all=False)
+        tri_inds.append(idx)
+        weights.append([1. - (p + q), p, q])
+
+    nn_idx = from_tri[tri_inds]
+    weights = np.array(weights)
+
+    row_ind = np.repeat(np.arange(len(to_rr)), 3)
+    this_map = csr_matrix((weights.ravel(), (row_ind, nn_idx.ravel())),
+                          shape=(len(to_rr), len(from_rr)))
+    return this_map
+
+
+def _find_nearest_tri_pt(rr, tri_geom, pt_tris=None, run_all=True):
+    """Find nearest point mapping to a set of triangles.
 
     If run_all is False, if the point lies within a triangle, it stops.
     If run_all is True, edges of other triangles are checked in case
@@ -1057,8 +1073,9 @@ def _find_nearest_tri_pt(pt_tris, to_pt, tri_geom, run_all=False):
 
     # This einsum is equivalent to doing:
     # pqs = np.array([np.dot(x, y) for x, y in zip(r1213, r1-to_pt)])
-    r1 = tri_geom['r1'][pt_tris]
-    rrs = to_pt - r1
+    if pt_tris is None:  # use all points
+        pt_tris = slice(len(tri_geom['r1']))
+    rrs = rr - tri_geom['r1'][pt_tris]
     tri_nn = tri_geom['nn'][pt_tris]
     vect = np.einsum('ijk,ik->ij', tri_geom['r1213'][pt_tris], rrs)
     mats = tri_geom['mat'][pt_tris]
@@ -1079,13 +1096,16 @@ def _find_nearest_tri_pt(pt_tris, to_pt, tri_geom, run_all=False):
         p, q = pqs[:, pt]
         dist = dists[pt]
         # re-reference back to original numbers
-        pt = pt_tris[pt]
+        if not isinstance(pt_tris, slice):
+            pt = pt_tris[pt]
 
     if found is False or run_all is True:
         # don't include ones that we might have found before
-        s = np.setdiff1d(np.arange(len(pt_tris)), idx)  # ones to check sides
+        # these are the ones that we want to check thesides of
+        s = np.setdiff1d(np.arange(dists.shape[0]), idx)
         # Tough: must investigate the sides
-        pp, qq, ptt, distt = _nearest_tri_edge(pt_tris[s], to_pt, pqs[:, s],
+        use_pt_tris = s if isinstance(pt_tris, slice) else pt_tris[s]
+        pp, qq, ptt, distt = _nearest_tri_edge(use_pt_tris, rr, pqs[:, s],
                                                dists[s], tri_geom)
         if np.abs(distt) < np.abs(dist):
             p, q, pt, dist = pp, qq, ptt, distt
@@ -1093,7 +1113,7 @@ def _find_nearest_tri_pt(pt_tris, to_pt, tri_geom, run_all=False):
 
 
 def _nearest_tri_edge(pt_tris, to_pt, pqs, dist, tri_geom):
-    """Get nearest location from a point to the edge of a set of triangles"""
+    """Get nearest location from a point to the edge of a set of triangles."""
     # We might do something intelligent here. However, for now
     # it is ok to do it in the hard way
     aa = tri_geom['a'][pt_tris]
@@ -1129,7 +1149,7 @@ def _nearest_tri_edge(pt_tris, to_pt, pqs, dist, tri_geom):
 
 
 def mesh_edges(tris):
-    """Returns sparse matrix with edges as an adjacency matrix
+    """Return sparse matrix with edges as an adjacency matrix.
 
     Parameters
     ----------
@@ -1158,7 +1178,7 @@ def mesh_edges(tris):
 
 
 def mesh_dist(tris, vert):
-    """Compute adjacency matrix weighted by distances
+    """Compute adjacency matrix weighted by distances.
 
     It generates an adjacency matrix where the entries are the distances
     between neighboring vertices.
@@ -1178,15 +1198,14 @@ def mesh_dist(tris, vert):
     edges = mesh_edges(tris).tocoo()
 
     # Euclidean distances between neighboring vertices
-    dist = np.sqrt(np.sum((vert[edges.row, :] - vert[edges.col, :]) ** 2,
-                          axis=1))
+    dist = np.linalg.norm(vert[edges.row, :] - vert[edges.col, :], axis=1)
     dist_matrix = csr_matrix((dist, (edges.row, edges.col)), shape=edges.shape)
     return dist_matrix
 
 
 @verbose
 def read_tri(fname_in, swap=False, verbose=None):
-    """Function for reading triangle definitions from an ascii file.
+    """Read triangle definitions from an ascii file.
 
     Parameters
     ----------
@@ -1196,7 +1215,8 @@ def read_tri(fname_in, swap=False, verbose=None):
         Assume the ASCII file vertex ordering is clockwise instead of
         counterclockwise.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -1240,3 +1260,49 @@ def read_tri(fname_in, swap=False, verbose=None):
     else:
         warn('Node normals were not read.')
     return (rr, tris)
+
+
+def _get_solids(tri_rrs, fros):
+    """Compute _sum_solids_div total angle in chunks."""
+    # NOTE: This incorporates the division by 4PI that used to be separate
+    # for tri_rr in tri_rrs:
+    #     v1 = fros - tri_rr[0]
+    #     v2 = fros - tri_rr[1]
+    #     v3 = fros - tri_rr[2]
+    #     triple = np.sum(fast_cross_3d(v1, v2) * v3, axis=1)
+    #     l1 = np.sqrt(np.sum(v1 * v1, axis=1))
+    #     l2 = np.sqrt(np.sum(v2 * v2, axis=1))
+    #     l3 = np.sqrt(np.sum(v3 * v3, axis=1))
+    #     s = (l1 * l2 * l3 +
+    #          np.sum(v1 * v2, axis=1) * l3 +
+    #          np.sum(v1 * v3, axis=1) * l2 +
+    #          np.sum(v2 * v3, axis=1) * l1)
+    #     tot_angle -= np.arctan2(triple, s)
+
+    # This is the vectorized version, but with a slicing heuristic to
+    # prevent memory explosion
+    tot_angle = np.zeros((len(fros)))
+    slices = np.r_[np.arange(0, len(fros), 100), [len(fros)]]
+    for i1, i2 in zip(slices[:-1], slices[1:]):
+        # shape (3 verts, n_tri, n_fro, 3 X/Y/Z)
+        vs = (fros[np.newaxis, np.newaxis, i1:i2] -
+              tri_rrs.transpose([1, 0, 2])[:, :, np.newaxis])
+        triples = _fast_cross_nd_sum(vs[0], vs[1], vs[2])
+        ls = np.linalg.norm(vs, axis=3)
+        ss = np.prod(ls, axis=0)
+        ss += np.einsum('ijk,ijk,ij->ij', vs[0], vs[1], ls[2])
+        ss += np.einsum('ijk,ijk,ij->ij', vs[0], vs[2], ls[1])
+        ss += np.einsum('ijk,ijk,ij->ij', vs[1], vs[2], ls[0])
+        tot_angle[i1:i2] = -np.sum(np.arctan2(triples, ss), axis=0)
+    return tot_angle
+
+
+def _complete_sphere_surf(sphere, idx, level):
+    """Convert sphere conductor model to surface."""
+    rad = sphere['layers'][idx]['rad']
+    r0 = sphere['r0']
+    surf = _tessellate_sphere_surf(level, rad=rad)
+    surf['rr'] += r0
+    complete_surface_info(surf)
+    surf['coord_frame'] = sphere['coord_frame']
+    return surf
diff --git a/mne/tests/common.py b/mne/tests/common.py
index eef5b3c..4f9cbea 100644
--- a/mne/tests/common.py
+++ b/mne/tests/common.py
@@ -8,14 +8,14 @@ from numpy.testing import assert_allclose, assert_equal, assert_array_equal
 from scipy import linalg
 
 from .. import pick_types, Evoked
-from ..io import _BaseRaw
+from ..io import BaseRaw
 from ..io.constants import FIFF
 from ..bem import fit_sphere_to_headshape
 
 
 def _get_data(x, ch_idx):
     """Helper to get the (n_ch, n_times) data array"""
-    if isinstance(x, _BaseRaw):
+    if isinstance(x, BaseRaw):
         return x[ch_idx][0]
     elif isinstance(x, Evoked):
         return x.data[ch_idx]
@@ -97,9 +97,10 @@ def assert_dig_allclose(info_py, info_bin):
                         err_msg='Failure on %s:\n%s\n%s'
                         % (ii, d_py['r'], d_bin['r']))
     if any(d['kind'] == FIFF.FIFFV_POINT_EXTRA for d in dig_py):
-        r_bin, o_head_bin, o_dev_bin = fit_sphere_to_headshape(info_bin,
-                                                               units='m')
-        r_py, o_head_py, o_dev_py = fit_sphere_to_headshape(info_py, units='m')
+        r_bin, o_head_bin, o_dev_bin = fit_sphere_to_headshape(
+            info_bin, units='m', verbose='error')
+        r_py, o_head_py, o_dev_py = fit_sphere_to_headshape(
+            info_py, units='m', verbose='error')
         assert_allclose(r_py, r_bin, atol=1e-6)
         assert_allclose(o_dev_py, o_dev_bin, rtol=1e-5, atol=1e-6)
         assert_allclose(o_head_py, o_head_bin, rtol=1e-5, atol=1e-6)
diff --git a/mne/tests/test_annotations.py b/mne/tests/test_annotations.py
index 62ff1fa..b4d18f8 100644
--- a/mne/tests/test_annotations.py
+++ b/mne/tests/test_annotations.py
@@ -3,16 +3,19 @@
 # License: BSD 3 clause
 
 from datetime import datetime
-from nose.tools import assert_raises
-from numpy.testing import assert_array_equal, assert_array_almost_equal
 import os.path as op
+import warnings
+
+from nose.tools import assert_raises, assert_true
+from numpy.testing import (assert_equal, assert_array_equal,
+                           assert_array_almost_equal, assert_allclose)
 
 import numpy as np
 
-from mne import create_info
+from mne import create_info, Epochs
 from mne.utils import run_tests_if_main
 from mne.io import read_raw_fif, RawArray, concatenate_raws
-from mne.annotations import Annotations
+from mne.annotations import Annotations, _sync_onset
 from mne.datasets import testing
 
 data_dir = op.join(testing.data_path(download=False), 'MEG', 'sample')
@@ -22,7 +25,7 @@ fif_fname = op.join(data_dir, 'sample_audvis_trunc_raw.fif')
 @testing.requires_testing_data
 def test_annotations():
     """Test annotation class."""
-    raw = read_raw_fif(fif_fname, add_eeg_ref=False)
+    raw = read_raw_fif(fif_fname)
     onset = np.array(range(10))
     duration = np.ones(10)
     description = np.repeat('test', 10)
@@ -41,9 +44,12 @@ def test_annotations():
     orig_time = (meas_date[0] + meas_date[1] * 0.000001 +
                  raw2.first_samp / raw2.info['sfreq'])
     annot = Annotations(onset, duration, description, orig_time)
+    assert_true(' segments' in repr(annot))
     raw2.annotations = annot
     assert_array_equal(raw2.annotations.onset, onset)
     concatenate_raws([raw, raw2])
+    raw.annotations.delete(-1)  # remove boundary annotations
+    raw.annotations.delete(-1)
     assert_array_almost_equal(onset + 20., raw.annotations.onset, decimal=2)
     assert_array_equal(annot.duration, raw.annotations.duration)
     assert_array_equal(raw.annotations.description, np.repeat('test', 10))
@@ -55,13 +61,155 @@ def test_annotations():
                        sfreq=sfreq)
     info['meas_date'] = 0
     raws = []
-    for i, fs in enumerate([1000, 100, 12]):
+    for i, fs in enumerate([12300, 100, 12]):
         raw = RawArray(data.copy(), info, first_samp=fs)
         ants = Annotations([1., 2.], [.5, .5], 'x', fs / sfreq)
         raw.annotations = ants
         raws.append(raw)
+    raw = RawArray(data.copy(), info)
+    raw.annotations = Annotations([1.], [.5], 'x', None)
+    raws.append(raw)
     raw = concatenate_raws(raws)
-    assert_array_equal(raw.annotations.onset, [1., 2., 11., 12., 21., 22.])
+    boundary_idx = np.where(raw.annotations.description == 'BAD boundary')[0]
+    assert_equal(len(boundary_idx), 3)
+    raw.annotations.delete(boundary_idx)
+    boundary_idx = np.where(raw.annotations.description == 'EDGE boundary')[0]
+    assert_equal(len(boundary_idx), 3)
+    raw.annotations.delete(boundary_idx)
+    assert_array_equal(raw.annotations.onset, [1., 2., 11., 12., 21., 22.,
+                                               31.])
+    raw.annotations.delete(2)
+    assert_array_equal(raw.annotations.onset, [1., 2., 12., 21., 22., 31.])
+    raw.annotations.append(5, 1.5, 'y')
+    assert_array_equal(raw.annotations.onset, [1., 2., 12., 21., 22., 31., 5])
+    assert_array_equal(raw.annotations.duration, [.5, .5, .5, .5, .5, .5, 1.5])
+    assert_array_equal(raw.annotations.description, ['x', 'x', 'x', 'x', 'x',
+                                                     'x', 'y'])
+
+    # Test concatenating annotations with and without orig_time.
+    raw = read_raw_fif(fif_fname)
+    last_time = raw.last_samp / raw.info['sfreq']
+    raw2 = raw.copy()
+    raw.annotations = Annotations([45.], [3], 'test', raw.info['meas_date'])
+    raw2.annotations = Annotations([2.], [3], 'BAD', None)
+    raw = concatenate_raws([raw, raw2])
+    raw.annotations.delete(-1)  # remove boundary annotations
+    raw.annotations.delete(-1)
+    assert_array_almost_equal(raw.annotations.onset, [45., 2. + last_time],
+                              decimal=2)
+
+
+ at testing.requires_testing_data
+def test_raw_reject():
+    """Test raw data getter with annotation reject."""
+    info = create_info(['a', 'b', 'c', 'd', 'e'], 100, ch_types='eeg')
+    raw = RawArray(np.ones((5, 15000)), info)
+    with warnings.catch_warnings(record=True):  # one outside range
+        raw.annotations = Annotations([2, 100, 105, 148], [2, 8, 5, 8], 'BAD')
+    data = raw.get_data([0, 1, 3, 4], 100, 11200, 'omit')
+    assert_array_equal(data.shape, (4, 9900))
+
+    # with orig_time and complete overlap
+    raw = read_raw_fif(fif_fname)
+    raw.annotations = Annotations([44, 47, 48], [1, 3, 1], 'BAD',
+                                  raw.info['meas_date'])
+    data, times = raw.get_data(range(10), 0, 6000, 'omit', True)
+    assert_array_equal(data.shape, (10, 4799))
+    assert_equal(times[-1], raw.times[5999])
+    assert_array_equal(data[:, -100:], raw[:10, 5900:6000][0])
+
+    data, times = raw.get_data(range(10), 0, 6000, 'NaN', True)
+    assert_array_equal(data.shape, (10, 6000))
+    assert_equal(times[-1], raw.times[5999])
+    assert_true(np.isnan(data[:, 313:613]).all())  # 1s -2s
+    assert_true(not np.isnan(data[:, 614].any()))
+    assert_array_equal(data[:, -100:], raw[:10, 5900:6000][0])
+    assert_array_equal(raw.get_data(), raw[:][0])
+
+    # Test _sync_onset
+    times = [10, -88, 190]
+    onsets = _sync_onset(raw, times)
+    assert_array_almost_equal(onsets, times - raw.first_samp /
+                              raw.info['sfreq'])
+    assert_array_almost_equal(times, _sync_onset(raw, onsets, True))
+
+
+def test_annotation_filtering():
+    """Test that annotations work properly with filtering."""
+    # Create data with just a DC component
+    data = np.ones((1, 1000))
+    info = create_info(1, 1000., 'eeg')
+    raws = [RawArray(data * (ii + 1), info) for ii in range(4)]
+    kwargs_pass = dict(l_freq=None, h_freq=50., fir_design='firwin')
+    kwargs_stop = dict(l_freq=50., h_freq=None, fir_design='firwin')
+    # lowpass filter, which should not modify the data
+    raws_pass = [raw.copy().filter(**kwargs_pass) for raw in raws]
+    # highpass filter, which should zero it out
+    raws_stop = [raw.copy().filter(**kwargs_stop) for raw in raws]
+    # concat the original and the filtered segments
+    raws_concat = concatenate_raws([raw.copy() for raw in raws])
+    raws_zero = raws_concat.copy().apply_function(lambda x: x * 0)
+    raws_pass_concat = concatenate_raws(raws_pass)
+    raws_stop_concat = concatenate_raws(raws_stop)
+    # make sure we did something reasonable with our individual-file filtering
+    assert_allclose(raws_concat[0][0], raws_pass_concat[0][0], atol=1e-14)
+    assert_allclose(raws_zero[0][0], raws_stop_concat[0][0], atol=1e-14)
+    # ensure that our Annotations cut up the filtering properly
+    raws_concat_pass = raws_concat.copy().filter(skip_by_annotation='edge',
+                                                 **kwargs_pass)
+    assert_allclose(raws_concat[0][0], raws_concat_pass[0][0], atol=1e-14)
+    raws_concat_stop = raws_concat.copy().filter(skip_by_annotation='edge',
+                                                 **kwargs_stop)
+    assert_allclose(raws_zero[0][0], raws_concat_stop[0][0], atol=1e-14)
+    # one last test: let's cut out a section entirely:
+    # here the 1-3 second window should be skipped
+    raw = raws_concat.copy()
+    raw.annotations.append(1., 2., 'foo')
+    raw.filter(l_freq=50., h_freq=None, fir_design='firwin',
+               skip_by_annotation='foo')
+    # our filter will zero out anything not skipped:
+    mask = np.concatenate((np.zeros(1000), np.ones(2000), np.zeros(1000)))
+    expected_data = raws_concat[0][0][0] * mask
+    assert_allclose(raw[0][0][0], expected_data, atol=1e-14)
+
+
+def test_annotation_omit():
+    """Test raw.get_data with annotations."""
+    data = np.concatenate([np.ones((1, 1000)), 2 * np.ones((1, 1000))], -1)
+    info = create_info(1, 1000., 'eeg')
+    raw = RawArray(data, info)
+    raw.annotations = Annotations([0.5], [1], ['bad'])
+    expected = raw[0][0]
+    assert_allclose(raw.get_data(reject_by_annotation=None), expected)
+    # nan
+    expected[0, 500:1500] = np.nan
+    assert_allclose(raw.get_data(reject_by_annotation='nan'), expected)
+    got = np.concatenate([raw.get_data(start=start, stop=stop,
+                                       reject_by_annotation='nan')
+                          for start, stop in ((0, 1000), (1000, 2000))], -1)
+    assert_allclose(got, expected)
+    # omit
+    expected = expected[:, np.isfinite(expected[0])]
+    assert_allclose(raw.get_data(reject_by_annotation='omit'), expected)
+    got = np.concatenate([raw.get_data(start=start, stop=stop,
+                                       reject_by_annotation='omit')
+                          for start, stop in ((0, 1000), (1000, 2000))], -1)
+    assert_allclose(got, expected)
+    assert_raises(ValueError, raw.get_data, reject_by_annotation='foo')
+
+
+def test_annotation_epoching():
+    """Test that annotations work properly with concatenated edges."""
+    # Create data with just a DC component
+    data = np.ones((1, 1000))
+    info = create_info(1, 1000., 'eeg')
+    raw = concatenate_raws([RawArray(data, info) for ii in range(3)])
+    events = np.array([[a, 0, 1] for a in [0, 500, 1000, 1500, 2000]])
+    epochs = Epochs(raw, events, tmin=0, tmax=0.999, baseline=None,
+                    preload=True)  # 1000 samples long
+    assert_equal(len(epochs.drop_log), len(events))
+    assert_equal(len(epochs), 3)
+    assert_equal([0, 2, 4], epochs.selection)
 
 
 run_tests_if_main()
diff --git a/mne/tests/test_bem.py b/mne/tests/test_bem.py
index dce68f0..cc5240f 100644
--- a/mne/tests/test_bem.py
+++ b/mne/tests/test_bem.py
@@ -10,6 +10,7 @@ import warnings
 
 import numpy as np
 from nose.tools import assert_raises, assert_true
+import pytest
 from numpy.testing import assert_equal, assert_allclose
 
 from mne import (make_bem_model, read_bem_surfaces, write_bem_surfaces,
@@ -19,8 +20,8 @@ from mne.preprocessing.maxfilter import fit_sphere_to_headshape
 from mne.io.constants import FIFF
 from mne.transforms import translation
 from mne.datasets import testing
-from mne.utils import (run_tests_if_main, _TempDir, slow_test, catch_logging,
-                       requires_freesurfer)
+from mne.utils import (run_tests_if_main, _TempDir, catch_logging,
+                       requires_freesurfer, requires_nibabel)
 from mne.bem import (_ico_downsample, _get_ico_map, _order_surfaces,
                      _assert_complete_surface, _assert_inside,
                      _check_surface_size, _bem_find_surface, make_flash_bem)
@@ -134,7 +135,7 @@ def test_bem_model():
                   conductivity=[0.3, 0.006], subjects_dir=subjects_dir)
 
 
- at slow_test
+ at pytest.mark.slowtest
 @testing.requires_testing_data
 def test_bem_solution():
     """Test making a BEM solution from Python with I/O"""
@@ -339,6 +340,7 @@ def test_fit_sphere_to_headshape():
     assert_raises(TypeError, fit_sphere_to_headshape, 1, units='m')
 
 
+ at requires_nibabel()
 @requires_freesurfer
 @testing.requires_testing_data
 def test_make_flash_bem():
diff --git a/mne/tests/test_chpi.py b/mne/tests/test_chpi.py
index a6ab8c5..5681047 100644
--- a/mne/tests/test_chpi.py
+++ b/mne/tests/test_chpi.py
@@ -3,20 +3,25 @@
 # License: BSD (3-clause)
 
 import os.path as op
+import warnings
+
 import numpy as np
 from numpy.testing import assert_allclose
 from nose.tools import assert_raises, assert_equal, assert_true
-import warnings
+import pytest
 
-from mne.io import read_raw_fif
+from mne import (pick_types, Dipole, make_sphere_model, make_forward_dipole,
+                 pick_info)
+from mne.io import read_raw_fif, read_raw_artemis123, read_info, RawArray
 from mne.io.constants import FIFF
-from mne.chpi import (_calculate_chpi_positions,
+from mne.chpi import (_calculate_chpi_positions, _calculate_chpi_coil_locs,
                       head_pos_to_trans_rot_t, read_head_pos,
-                      write_head_pos, filter_chpi, _get_hpi_info)
+                      write_head_pos, filter_chpi,
+                      _get_hpi_info, _get_hpi_initial_fit)
 from mne.fixes import assert_raises_regex
 from mne.transforms import rot_to_quat, _angle_between_quats
-from mne.utils import (run_tests_if_main, _TempDir, slow_test, catch_logging,
-                       requires_version)
+from mne.simulation import simulate_raw
+from mne.utils import run_tests_if_main, _TempDir, catch_logging
 from mne.datasets import testing
 from mne.tests.common import assert_meg_snr
 
@@ -25,12 +30,20 @@ test_fif_fname = op.join(base_dir, 'test_raw.fif')
 ctf_fname = op.join(base_dir, 'test_ctf_raw.fif')
 hp_fif_fname = op.join(base_dir, 'test_chpi_raw_sss.fif')
 hp_fname = op.join(base_dir, 'test_chpi_raw_hp.txt')
+raw_fname = op.join(base_dir, 'test_raw.fif')
 
 data_path = testing.data_path(download=False)
 chpi_fif_fname = op.join(data_path, 'SSS', 'test_move_anon_raw.fif')
 pos_fname = op.join(data_path, 'SSS', 'test_move_anon_raw.pos')
 sss_fif_fname = op.join(data_path, 'SSS', 'test_move_anon_raw_sss.fif')
 sss_hpisubt_fname = op.join(data_path, 'SSS', 'test_move_anon_hpisubt_raw.fif')
+chpi5_fif_fname = op.join(data_path, 'SSS', 'chpi5_raw.fif')
+chpi5_pos_fname = op.join(data_path, 'SSS', 'chpi5_raw_mc.pos')
+
+art_fname = op.join(data_path, 'ARTEMIS123', 'Artemis_Data_2017-04-04' +
+                    '-15h-44m-22s_Motion_Translation-z.bin')
+art_mc_fname = op.join(data_path, 'ARTEMIS123', 'Artemis_Data_2017-04-04' +
+                       '-15h-44m-22s_Motion_Translation-z_mc.pos')
 
 warnings.simplefilter('always')
 
@@ -38,11 +51,10 @@ warnings.simplefilter('always')
 @testing.requires_testing_data
 def test_chpi_adjust():
     """Test cHPI logging and adjustment."""
-    raw = read_raw_fif(chpi_fif_fname, allow_maxshield='yes',
-                       add_eeg_ref=False)
+    raw = read_raw_fif(chpi_fif_fname, allow_maxshield='yes')
     with catch_logging() as log:
-        _get_hpi_info(raw.info, adjust=True, verbose='debug')
-
+        _get_hpi_initial_fit(raw.info, adjust=True, verbose='debug')
+        _get_hpi_info(raw.info, verbose='debug')
     # Ran MaxFilter (with -list, -v, -movecomp, etc.), and got:
     msg = ['HPIFIT: 5 coils digitized in order 5 1 4 3 2',
            'HPIFIT: 3 coils accepted: 1 2 4',
@@ -69,7 +81,8 @@ def test_chpi_adjust():
         'Note: HPI coil 3 isotrak is adjusted by 5.3 mm!',
         'Note: HPI coil 5 isotrak is adjusted by 3.2 mm!'] + msg[-2:]
     with catch_logging() as log:
-        _get_hpi_info(raw.info, adjust=True, verbose='debug')
+        _get_hpi_initial_fit(raw.info, adjust=True, verbose='debug')
+        _get_hpi_info(raw.info, verbose='debug')
     log = log.getvalue().splitlines()
     assert_true(set(log) == set(msg), '\n' + '\n'.join(set(msg) - set(log)))
 
@@ -101,23 +114,25 @@ def test_hpi_info():
     tempdir = _TempDir()
     temp_name = op.join(tempdir, 'temp_raw.fif')
     for fname in (chpi_fif_fname, sss_fif_fname):
-        raw = read_raw_fif(fname, allow_maxshield='yes', add_eeg_ref=False)
+        raw = read_raw_fif(fname, allow_maxshield='yes').crop(0, 0.1)
         assert_true(len(raw.info['hpi_subsystem']) > 0)
         raw.save(temp_name, overwrite=True)
-        raw_2 = read_raw_fif(temp_name, allow_maxshield='yes',
-                             add_eeg_ref=False)
-        assert_equal(len(raw_2.info['hpi_subsystem']),
+        info = read_info(temp_name)
+        assert_equal(len(info['hpi_subsystem']),
                      len(raw.info['hpi_subsystem']))
 
 
-def _compare_positions(a, b, max_dist=0.003, max_angle=5.):
+def _assert_quats(actual, desired, dist_tol=0.003, angle_tol=5.):
     """Compare estimated cHPI positions."""
     from scipy.interpolate import interp1d
-    trans, rot, t = a
-    trans_est, rot_est, t_est = b
+    trans_est, rot_est, t_est = head_pos_to_trans_rot_t(actual)
+    trans, rot, t = head_pos_to_trans_rot_t(desired)
     quats_est = rot_to_quat(rot_est)
 
     # maxfilter produces some times that are implausibly large (weird)
+    if not np.isclose(t[0], t_est[0], atol=1e-1):  # within 100 ms
+        raise AssertionError('Start times not within 100 ms: %0.3f != %0.3f'
+                             % (t[0], t_est[0]))
     use_mask = (t >= t_est[0]) & (t <= t_est[-1])
     t = t[use_mask]
     trans = trans[use_mask]
@@ -129,88 +144,260 @@ def _compare_positions(a, b, max_dist=0.003, max_angle=5.):
         angles = _angle_between_quats(q, q)
         assert_allclose(angles, 0., atol=1e-5)
 
-    # < 3 mm translation difference between MF and our estimation
+    # limit translation difference between MF and our estimation
     trans_est_interp = interp1d(t_est, trans_est, axis=0)(t)
-    worst = np.sqrt(np.sum((trans - trans_est_interp) ** 2, axis=1)).max()
-    assert_true(worst <= max_dist, '%0.1f > %0.1f mm'
-                % (1000 * worst, 1000 * max_dist))
+    distances = np.sqrt(np.sum((trans - trans_est_interp) ** 2, axis=1))
+    arg_worst = np.argmax(distances)
+    assert_true(distances[arg_worst] <= dist_tol,
+                '@ %0.3f seconds: %0.3f > %0.3f mm'
+                % (t[arg_worst], 1000 * distances[arg_worst], 1000 * dist_tol))
 
-    # < 5 degrees rotation difference between MF and our estimation
+    # limit rotation difference between MF and our estimation
     # (note that the interpolation will make this slightly worse)
     quats_est_interp = interp1d(t_est, quats_est, axis=0)(t)
-    worst = 180 * _angle_between_quats(quats_est_interp, quats).max() / np.pi
-    assert_true(worst <= max_angle, '%0.1f > %0.1f deg' % (worst, max_angle,))
+    angles = 180 * _angle_between_quats(quats_est_interp, quats) / np.pi
+    arg_worst = np.argmax(angles)
+    assert_true(angles[arg_worst] <= angle_tol,
+                '@ %0.3f seconds: %0.3f > %0.3f deg'
+                % (t[arg_worst], angles[arg_worst], angle_tol))
 
 
- at slow_test
+def _decimate_chpi(raw, decim=4):
+    """Decimate raw data (with aliasing) in cHPI-fitting compatible way."""
+    raw_dec = RawArray(
+        raw._data[:, ::decim], raw.info, first_samp=raw.first_samp // decim)
+    raw_dec.info['sfreq'] /= decim
+    for coil in raw_dec.info['hpi_meas'][0]['hpi_coils']:
+        if coil['coil_freq'] > raw_dec.info['sfreq']:
+            coil['coil_freq'] = np.mod(coil['coil_freq'],
+                                       raw_dec.info['sfreq'])
+            if coil['coil_freq'] > raw_dec.info['sfreq'] / 2.:
+                coil['coil_freq'] = raw_dec.info['sfreq'] - coil['coil_freq']
+    return raw_dec
+
+
+ at pytest.mark.slowtest
 @testing.requires_testing_data
- at requires_version('scipy', '0.11')
- at requires_version('numpy', '1.7')
 def test_calculate_chpi_positions():
     """Test calculation of cHPI positions."""
-    trans, rot, t = head_pos_to_trans_rot_t(read_head_pos(pos_fname))
-    raw = read_raw_fif(chpi_fif_fname, allow_maxshield='yes', preload=True,
-                       add_eeg_ref=False)
-    t -= raw.first_samp / raw.info['sfreq']
-    quats = _calculate_chpi_positions(raw, verbose='debug')
-    trans_est, rot_est, t_est = head_pos_to_trans_rot_t(quats)
-    _compare_positions((trans, rot, t), (trans_est, rot_est, t_est), 0.003)
+    # Check to make sure our fits match MF decently
+    mf_quats = read_head_pos(pos_fname)
+    raw = read_raw_fif(chpi_fif_fname, allow_maxshield='yes', preload=True)
+    # This is a little hack (aliasing while decimating) to make it much faster
+    # for testing purposes only. We can relax this later if we find it breaks
+    # something.
+    raw_dec = _decimate_chpi(raw, 15)
+    with catch_logging() as log:
+        py_quats = _calculate_chpi_positions(raw_dec, verbose='debug')
+    assert_true(log.getvalue().startswith('HPIFIT'))
+    _assert_quats(py_quats, mf_quats, dist_tol=0.004, angle_tol=2.5)
 
     # degenerate conditions
-    raw_no_chpi = read_raw_fif(test_fif_fname, add_eeg_ref=False)
+    raw_no_chpi = read_raw_fif(test_fif_fname)
     assert_raises(RuntimeError, _calculate_chpi_positions, raw_no_chpi)
     raw_bad = raw.copy()
     for d in raw_bad.info['dig']:
         if d['kind'] == FIFF.FIFFV_POINT_HPI:
-            d['coord_frame'] = 999
+            d['coord_frame'] = FIFF.FIFFV_COORD_UNKNOWN
             break
     assert_raises(RuntimeError, _calculate_chpi_positions, raw_bad)
-    raw_bad = raw.copy()
     for d in raw_bad.info['dig']:
         if d['kind'] == FIFF.FIFFV_POINT_HPI:
+            d['coord_frame'] = FIFF.FIFFV_COORD_HEAD
             d['r'] = np.ones(3)
-    raw_bad.crop(0, 1., copy=False)
+    raw_bad.crop(0, 1.)
+    picks = np.concatenate([np.arange(306, len(raw_bad.ch_names)),
+                            pick_types(raw_bad.info, meg=True)[::16]])
+    raw_bad.pick_channels([raw_bad.ch_names[pick] for pick in picks])
     with warnings.catch_warnings(record=True):  # bad pos
         with catch_logging() as log_file:
-            _calculate_chpi_positions(raw_bad, verbose=True)
+            _calculate_chpi_positions(raw_bad, t_step_min=1., verbose=True)
     # ignore HPI info header and [done] footer
     assert_true('0/5 good' in log_file.getvalue().strip().split('\n')[-2])
 
     # half the rate cuts off cHPI coils
-    with warnings.catch_warnings(record=True):  # uint cast suggestion
-        raw.resample(300., npad='auto')
+    raw.info['lowpass'] /= 2.
     assert_raises_regex(RuntimeError, 'above the',
                         _calculate_chpi_positions, raw)
 
+    # test on 5k artemis data
+    raw = read_raw_artemis123(art_fname, preload=True)
+    mf_quats = read_head_pos(art_mc_fname)
+    with catch_logging() as log:
+        py_quats = _calculate_chpi_positions(raw, t_step_min=2.,
+                                             verbose='debug')
+    _assert_quats(py_quats, mf_quats, dist_tol=0.004, angle_tol=2.5)
+
+
+ at testing.requires_testing_data
+def test_calculate_chpi_positions_on_chpi5_in_one_second_steps():
+    """Comparing estimated cHPI positions with MF results (one second)."""
+    # Check to make sure our fits match MF decently
+    mf_quats = read_head_pos(chpi5_pos_fname)
+    raw = read_raw_fif(chpi5_fif_fname, allow_maxshield='yes')
+    # the last two seconds contain a maxfilter problem!
+    # fiff file timing: 26. to 43. seconds
+    # maxfilter estimates a wrong head position for interval 16: 41.-42. sec
+    raw = _decimate_chpi(raw.crop(0., 15.).load_data(), decim=8)
+    # needs no interpolation, because maxfilter pos files comes with 1 s steps
+    py_quats = _calculate_chpi_positions(raw, t_step_min=1.0, t_step_max=1.0,
+                                         t_window=1.0, verbose='debug')
+    _assert_quats(py_quats, mf_quats, dist_tol=0.0008, angle_tol=.5)
+
+
+ at pytest.mark.slowtest
+ at testing.requires_testing_data
+def test_calculate_chpi_positions_on_chpi5_in_shorter_steps():
+    """Comparing estimated cHPI positions with MF results (smaller steps)."""
+    # Check to make sure our fits match MF decently
+    mf_quats = read_head_pos(chpi5_pos_fname)
+    raw = read_raw_fif(chpi5_fif_fname, allow_maxshield='yes')
+    raw = _decimate_chpi(raw.crop(0., 15.).load_data(), decim=8)
+    py_quats = _calculate_chpi_positions(raw, t_step_min=0.1, t_step_max=0.1,
+                                         t_window=0.1, verbose='debug')
+    # needs interpolation, tolerance must be increased
+    _assert_quats(py_quats, mf_quats, dist_tol=0.001, angle_tol=0.6)
+
+
+def test_simulate_calculate_chpi_positions():
+    """Test calculation of cHPI positions with simulated data."""
+    # Read info dict from raw FIF file
+    info = read_info(raw_fname)
+    # Tune the info structure
+    chpi_channel = u'STI201'
+    ncoil = len(info['hpi_results'][0]['order'])
+    coil_freq = 10 + np.arange(ncoil) * 5
+    hpi_subsystem = {'event_channel': chpi_channel,
+                     'hpi_coils': [{'event_bits': np.array([256, 0, 256, 256],
+                                                           dtype=np.int32)},
+                                   {'event_bits': np.array([512, 0, 512, 512],
+                                                           dtype=np.int32)},
+                                   {'event_bits':
+                                       np.array([1024, 0, 1024, 1024],
+                                                dtype=np.int32)},
+                                   {'event_bits':
+                                       np.array([2048, 0, 2048, 2048],
+                                                dtype=np.int32)}],
+                     'ncoil': ncoil}
+
+    info['hpi_subsystem'] = hpi_subsystem
+    for l, freq in enumerate(coil_freq):
+            info['hpi_meas'][0]['hpi_coils'][l]['coil_freq'] = freq
+    picks = pick_types(info, meg=True, stim=True, eeg=False, exclude=[])
+    info['sfreq'] = 100.  # this will speed it up a lot
+    info = pick_info(info, picks)
+    info['chs'][info['ch_names'].index('STI 001')]['ch_name'] = 'STI201'
+    info._update_redundant()
+    info['projs'] = []
+
+    info_trans = info['dev_head_t']['trans'].copy()
+
+    dev_head_pos_ini = np.concatenate([rot_to_quat(info_trans[:3, :3]),
+                                      info_trans[:3, 3]])
+    ez = np.array([0, 0, 1])  # Unit vector in z-direction of head coordinates
+
+    # Define some constants
+    duration = 30  # Time / s
+
+    # Quotient of head position sampling frequency
+    # and raw sampling frequency
+    head_pos_sfreq_quotient = 0.1
+
+    # Round number of head positions to the next integer
+    S = int(duration / (info['sfreq'] * head_pos_sfreq_quotient))
+    dz = 0.001  # Shift in z-direction is 0.1mm for each step
+
+    dev_head_pos = np.zeros((S, 10))
+    dev_head_pos[:, 0] = np.arange(S) * info['sfreq'] * head_pos_sfreq_quotient
+    dev_head_pos[:, 1:4] = dev_head_pos_ini[:3]
+    dev_head_pos[:, 4:7] = dev_head_pos_ini[3:] + \
+        np.outer(np.arange(S) * dz, ez)
+    dev_head_pos[:, 7] = 1.0
+
+    # cm/s
+    dev_head_pos[:, 9] = 100 * dz / (info['sfreq'] * head_pos_sfreq_quotient)
+
+    # Round number of samples to the next integer
+    raw_data = np.zeros((len(picks), int(duration * info['sfreq'] + 0.5)))
+    raw = RawArray(raw_data, info)
+
+    dip = Dipole(np.array([0.0, 0.1, 0.2]),
+                 np.array([[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]]),
+                 np.array([1e-9, 1e-9, 1e-9]),
+                 np.array([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]),
+                 np.array([1.0, 1.0, 1.0]), 'dip')
+    sphere = make_sphere_model('auto', 'auto', info=info,
+                               relative_radii=(1.0, 0.9), sigmas=(0.33, 0.3))
+    fwd, stc = make_forward_dipole(dip, sphere, info)
+    stc.resample(info['sfreq'])
+    raw = simulate_raw(raw, stc, None, fwd['src'], sphere, cov=None,
+                       blink=False, ecg=False, chpi=True,
+                       head_pos=dev_head_pos, mindist=1.0, interp='zero',
+                       verbose=None, use_cps=True)
+
+    quats = _calculate_chpi_positions(
+        raw, t_step_min=raw.info['sfreq'] * head_pos_sfreq_quotient,
+        t_step_max=raw.info['sfreq'] * head_pos_sfreq_quotient, t_window=1.0)
+    _assert_quats(quats, dev_head_pos, dist_tol=0.001, angle_tol=1.)
+
+
+ at testing.requires_testing_data
+def test_calculate_chpi_coil_locs():
+    """Test computing just cHPI locations."""
+    raw = read_raw_fif(chpi_fif_fname, allow_maxshield='yes', preload=True)
+    # This is a little hack (aliasing while decimating) to make it much faster
+    # for testing purposes only. We can relax this later if we find it breaks
+    # something.
+    raw_dec = _decimate_chpi(raw, 15)
+    times, cHPI_digs = _calculate_chpi_coil_locs(raw_dec, verbose='debug')
+
+    # spot check
+    assert_allclose(times[9], 9.9, atol=1e-3)
+    assert_allclose(cHPI_digs[9][2]['r'],
+                    [-0.01937833, 0.00346804, 0.06331209], atol=1e-3)
+    assert_allclose(cHPI_digs[9][2]['gof'], 0.9957976, atol=1e-3)
+
+    assert_allclose(cHPI_digs[9][4]['r'],
+                    [0.05442122, 0.00997692, 0.03721696], atol=1e-3)
+    assert_allclose(cHPI_digs[9][4]['gof'], 0.075700080794629199, atol=1e-3)
+
+    # test on 5k artemis data
+    raw = read_raw_artemis123(art_fname, preload=True)
+    times, cHPI_digs = _calculate_chpi_coil_locs(raw, verbose='debug')
+
+    assert_allclose(times[2], 2.9, atol=1e-3)
+    assert_allclose(cHPI_digs[2][0]['gof'], 0.9980471794552791, atol=1e-3)
+    assert_allclose(cHPI_digs[2][0]['r'],
+                    [-0.0157762, 0.06655744, 0.00545172], atol=1e-3)
+
 
 @testing.requires_testing_data
 def test_chpi_subtraction():
     """Test subtraction of cHPI signals."""
-    raw = read_raw_fif(chpi_fif_fname, allow_maxshield='yes', preload=True,
-                       add_eeg_ref=False)
+    raw = read_raw_fif(chpi_fif_fname, allow_maxshield='yes', preload=True)
     raw.info['bads'] = ['MEG0111']
+    raw.del_proj()
     with catch_logging() as log:
         filter_chpi(raw, include_line=False, verbose=True)
+    assert_true('No average EEG' not in log.getvalue())
     assert_true('5 cHPI' in log.getvalue())
     # MaxFilter doesn't do quite as well as our algorithm with the last bit
-    raw.crop(0, 16, copy=False)
+    raw.crop(0, 16)
     # remove cHPI status chans
-    raw_c = read_raw_fif(sss_hpisubt_fname,
-                         add_eeg_ref=False).crop(0, 16, copy=False).load_data()
+    raw_c = read_raw_fif(sss_hpisubt_fname).crop(0, 16).load_data()
     raw_c.pick_types(
         meg=True, eeg=True, eog=True, ecg=True, stim=True, misc=True)
     assert_meg_snr(raw, raw_c, 143, 624)
 
     # Degenerate cases
-    raw_nohpi = read_raw_fif(test_fif_fname, preload=True, add_eeg_ref=False)
+    raw_nohpi = read_raw_fif(test_fif_fname, preload=True)
     assert_raises(RuntimeError, filter_chpi, raw_nohpi)
 
     # When MaxFliter downsamples, like::
     #     $ maxfilter -nosss -ds 2 -f test_move_anon_raw.fif \
     #           -o test_move_anon_ds2_raw.fif
     # it can strip out some values of info, which we emulate here:
-    raw = read_raw_fif(chpi_fif_fname, allow_maxshield='yes',
-                       add_eeg_ref=False)
+    raw = read_raw_fif(chpi_fif_fname, allow_maxshield='yes')
     with warnings.catch_warnings(record=True):  # uint cast suggestion
         raw = raw.crop(0, 1).load_data().resample(600., npad='auto')
     raw.info['buffer_size_sec'] = np.float64(2.)
@@ -220,6 +407,8 @@ def test_chpi_subtraction():
     del raw.info['hpi_subsystem']['event_channel']
     with catch_logging() as log:
         filter_chpi(raw, verbose=True)
+    assert_raises(ValueError, filter_chpi, raw, t_window=-1)
     assert_true('2 cHPI' in log.getvalue())
 
+
 run_tests_if_main()
diff --git a/mne/tests/test_coreg.py b/mne/tests/test_coreg.py
index ab4bf83..2adfe42 100644
--- a/mne/tests/test_coreg.py
+++ b/mne/tests/test_coreg.py
@@ -1,22 +1,50 @@
 from glob import glob
 import os
 
-from nose.tools import assert_raises, assert_true
+from nose.tools import assert_equal, assert_raises, assert_true
 import numpy as np
 from numpy.testing import assert_array_almost_equal, assert_array_less
 
 import mne
-from mne.transforms import apply_trans, rotation, translation, scaling
+from mne.transforms import (Transform, apply_trans, rotation, translation,
+                            scaling)
 from mne.coreg import (fit_matched_points, fit_point_cloud,
                        _point_cloud_error, _decimate_points,
                        create_default_subject, scale_mri,
-                       _is_mri_subject, scale_labels, scale_source_space)
-from mne.utils import (requires_mne, requires_freesurfer, _TempDir,
-                       run_tests_if_main, requires_version)
+                       _is_mri_subject, scale_labels, scale_source_space,
+                       coregister_fiducials)
+from mne.io.constants import FIFF
+from mne.utils import (requires_freesurfer, _TempDir, run_tests_if_main,
+                       requires_version)
+from mne.source_space import write_source_spaces
 from functools import reduce
 
 
- at requires_mne
+def test_coregister_fiducials():
+    """Test coreg.coregister_fiducials()"""
+    # prepare head and MRI fiducials
+    trans = Transform('head', 'mri',
+                      rotation(.4, .1, 0).dot(translation(.1, -.1, .1)))
+    coords_orig = np.array([[-0.08061612, -0.02908875, -0.04131077],
+                            [0.00146763, 0.08506715, -0.03483611],
+                            [0.08436285, -0.02850276, -0.04127743]])
+    coords_trans = apply_trans(trans, coords_orig)
+
+    def make_dig(coords, cf):
+        return ({'coord_frame': cf, 'ident': 1, 'kind': 1, 'r': coords[0]},
+                {'coord_frame': cf, 'ident': 2, 'kind': 1, 'r': coords[1]},
+                {'coord_frame': cf, 'ident': 3, 'kind': 1, 'r': coords[2]})
+
+    mri_fiducials = make_dig(coords_trans, FIFF.FIFFV_COORD_MRI)
+    info = {'dig': make_dig(coords_orig, FIFF.FIFFV_COORD_HEAD)}
+
+    # test coregister_fiducials()
+    trans_est = coregister_fiducials(info, mri_fiducials)
+    assert_equal(trans_est.from_str, trans.from_str)
+    assert_equal(trans_est.to_str, trans.to_str)
+    assert_array_almost_equal(trans_est['trans'], trans['trans'])
+
+
 @requires_freesurfer
 @requires_version('scipy', '0.11')
 def test_scale_mri():
@@ -41,8 +69,11 @@ def test_scale_mri():
 
     # create source space
     path = os.path.join(tempdir, 'fsaverage', 'bem', 'fsaverage-ico-0-src.fif')
-    mne.setup_source_space('fsaverage', path, 'ico0', overwrite=True,
-                           subjects_dir=tempdir, add_dist=False)
+    src = mne.setup_source_space('fsaverage', 'ico0', subjects_dir=tempdir,
+                                 add_dist=False)
+    src_path = os.path.join(tempdir, 'fsaverage', 'bem',
+                            'fsaverage-ico-0-src.fif')
+    write_source_spaces(src_path, src)
 
     # scale fsaverage
     os.environ['_MNE_FEW_SURFACES'] = 'true'
@@ -53,6 +84,7 @@ def test_scale_mri():
     assert_true(is_mri, "Scaling fsaverage failed")
     src_path = os.path.join(tempdir, 'flachkopf', 'bem',
                             'flachkopf-ico-0-src.fif')
+
     assert_true(os.path.exists(src_path), "Source space was not scaled")
     scale_labels('flachkopf', subjects_dir=tempdir)
 
@@ -64,11 +96,12 @@ def test_scale_mri():
     # add distances to source space
     src = mne.read_source_spaces(path)
     mne.add_source_space_distances(src)
-    src.save(path)
+    src.save(path, overwrite=True)
 
     # scale with distances
     os.remove(src_path)
     scale_source_space('flachkopf', 'ico-0', subjects_dir=tempdir)
+    assert_true(os.path.exists(src_path), "Source space was not scaled")
 
 
 def test_fit_matched_points():
diff --git a/mne/tests/test_cov.py b/mne/tests/test_cov.py
index 7eef394..b17f903 100644
--- a/mne/tests/test_cov.py
+++ b/mne/tests/test_cov.py
@@ -4,29 +4,29 @@
 # License: BSD (3-clause)
 
 import os.path as op
+import itertools as itt
+import warnings
 
 from nose.tools import assert_true
 from numpy.testing import (assert_array_almost_equal, assert_array_equal,
-                           assert_equal)
+                           assert_equal, assert_allclose)
 from nose.tools import assert_raises
+import pytest
 import numpy as np
 from scipy import linalg
-import warnings
-import itertools as itt
 
 from mne.cov import (regularize, whiten_evoked, _estimate_rank_meeg_cov,
                      _auto_low_rank_model, _apply_scaling_cov,
-                     _undo_scaling_cov, prepare_noise_cov)
+                     _undo_scaling_cov, prepare_noise_cov, compute_whitener,
+                     _apply_scaling_array, _undo_scaling_array)
 
 from mne import (read_cov, write_cov, Epochs, merge_events,
                  find_events, compute_raw_covariance,
                  compute_covariance, read_evokeds, compute_proj_raw,
-                 pick_channels_cov, pick_channels, pick_types, pick_info,
-                 make_ad_hoc_cov)
+                 pick_channels_cov, pick_types, pick_info, make_ad_hoc_cov)
 from mne.io import read_raw_fif, RawArray, read_info
 from mne.tests.common import assert_naming, assert_snr
-from mne.utils import (_TempDir, slow_test, requires_sklearn_0_15,
-                       run_tests_if_main)
+from mne.utils import _TempDir, requires_version, run_tests_if_main
 from mne.io.proc_history import _get_sss_rank
 from mne.io.pick import channel_type, _picks_by_type
 
@@ -44,12 +44,11 @@ hp_fif_fname = op.join(base_dir, 'test_chpi_raw_sss.fif')
 
 def test_cov_mismatch():
     """Test estimation with MEG<->Head mismatch."""
-    raw = read_raw_fif(raw_fname, add_eeg_ref=False).crop(0, 5).load_data()
+    raw = read_raw_fif(raw_fname).crop(0, 5).load_data()
     events = find_events(raw, stim_channel='STI 014')
     raw.pick_channels(raw.ch_names[:5])
     raw.add_proj([], remove_existing=True)
-    epochs = Epochs(raw, events, None, tmin=-0.2, tmax=0., preload=True,
-                    add_eeg_ref=False)
+    epochs = Epochs(raw, events, None, tmin=-0.2, tmax=0., preload=True)
     for kind in ('shift', 'None'):
         epochs_2 = epochs.copy()
         # This should be fine
@@ -85,6 +84,42 @@ def test_cov_order():
     cov = read_cov(cov_fname)
     # no avg ref present warning
     prepare_noise_cov(cov, info, ch_names, verbose='error')
+    # big reordering
+    cov_reorder = cov.copy()
+    order = np.random.RandomState(0).permutation(np.arange(len(cov.ch_names)))
+    cov_reorder['names'] = [cov['names'][ii] for ii in order]
+    cov_reorder['data'] = cov['data'][order][:, order]
+    # Make sure we did this properly
+    _assert_reorder(cov_reorder, cov, order)
+    # Now check some functions that should get the same result for both
+    # regularize
+    cov_reg = regularize(cov, info)
+    cov_reg_reorder = regularize(cov_reorder, info)
+    _assert_reorder(cov_reg_reorder, cov_reg, order)
+    # prepare_noise_cov
+    cov_prep = prepare_noise_cov(cov, info, ch_names)
+    cov_prep_reorder = prepare_noise_cov(cov, info, ch_names)
+    _assert_reorder(cov_prep, cov_prep_reorder,
+                    order=np.arange(len(cov_prep['names'])))
+    # compute_whitener
+    whitener, w_ch_names = compute_whitener(cov, info)
+    whitener_2, w_ch_names_2 = compute_whitener(cov_reorder, info)
+    assert_array_equal(w_ch_names_2, w_ch_names)
+    assert_allclose(whitener_2, whitener)
+    # whiten_evoked
+    evoked = read_evokeds(ave_fname)[0]
+    evoked_white = whiten_evoked(evoked, cov)
+    evoked_white_2 = whiten_evoked(evoked, cov_reorder)
+    assert_allclose(evoked_white_2.data, evoked_white.data)
+
+
+def _assert_reorder(cov_new, cov_orig, order):
+    """Check that we get the same result under reordering."""
+    inv_order = np.argsort(order)
+    assert_array_equal([cov_new['names'][ii] for ii in inv_order],
+                       cov_orig['names'])
+    assert_allclose(cov_new['data'][inv_order][:, inv_order],
+                    cov_orig['data'], atol=1e-20)
 
 
 def test_ad_hoc_cov():
@@ -142,7 +177,7 @@ def test_io_cov():
 def test_cov_estimation_on_raw():
     """Test estimation from raw (typically empty room)."""
     tempdir = _TempDir()
-    raw = read_raw_fif(raw_fname, preload=True, add_eeg_ref=False)
+    raw = read_raw_fif(raw_fname, preload=True)
     cov_mne = read_cov(erm_cov_fname)
 
     # The pure-string uses the more efficient numpy-based method, the
@@ -166,19 +201,15 @@ def test_cov_estimation_on_raw():
         assert_array_almost_equal(cov.data, cov_read.data)
 
         # test with a subset of channels
-        picks = pick_channels(raw.ch_names, include=raw.ch_names[:5])
-        raw_pick = raw.copy().pick_channels(
-            [raw.ch_names[pick] for pick in picks])
+        raw_pick = raw.copy().pick_channels(raw.ch_names[:5])
         raw_pick.info.normalize_proj()
-        cov = compute_raw_covariance(raw_pick, picks=picks, tstep=None,
-                                     method=method)
+        cov = compute_raw_covariance(raw_pick, tstep=None, method=method)
         assert_true(cov_mne.ch_names[:5] == cov.ch_names)
-        assert_snr(cov.data, cov_mne.data[picks][:, picks], 1e4)
-        cov = compute_raw_covariance(raw_pick, picks=picks, method=method)
-        assert_snr(cov.data, cov_mne.data[picks][:, picks], 90)  # cutoff samps
+        assert_snr(cov.data, cov_mne.data[:5, :5], 1e4)
+        cov = compute_raw_covariance(raw_pick, method=method)
+        assert_snr(cov.data, cov_mne.data[:5, :5], 90)  # cutoff samps
         # make sure we get a warning with too short a segment
-        raw_2 = read_raw_fif(raw_fname,
-                             add_eeg_ref=False).crop(0, 1, copy=False)
+        raw_2 = read_raw_fif(raw_fname).crop(0, 1)
         with warnings.catch_warnings(record=True) as w:
             warnings.simplefilter('always')
             cov = compute_raw_covariance(raw_2, method=method)
@@ -187,16 +218,16 @@ def test_cov_estimation_on_raw():
         assert_raises(ValueError, compute_raw_covariance, raw, tstep=None,
                       method='empirical', reject=dict(eog=200e-6))
         # but this should work
-        cov = compute_raw_covariance(raw.copy().crop(0, 10., copy=False),
+        cov = compute_raw_covariance(raw.copy().crop(0, 10.),
                                      tstep=None, method=method,
                                      reject=dict(eog=1000e-6))
 
 
- at slow_test
- at requires_sklearn_0_15
+ at pytest.mark.slowtest
+ at requires_version('sklearn', '0.15')
 def test_cov_estimation_on_raw_reg():
     """Test estimation from raw with regularization."""
-    raw = read_raw_fif(raw_fname, preload=True, add_eeg_ref=False)
+    raw = read_raw_fif(raw_fname, preload=True)
     raw.info['sfreq'] /= 10.
     raw = RawArray(raw._data[:, ::10].copy(), raw.info)  # decimate for speed
     cov_mne = read_cov(erm_cov_fname)
@@ -208,12 +239,21 @@ def test_cov_estimation_on_raw_reg():
     assert_snr(cov.data, cov_mne.data, 5)
 
 
- at slow_test
+def _assert_cov(cov, cov_desired, tol=0.005, nfree=True):
+    assert_equal(cov.ch_names, cov_desired.ch_names)
+    err = (linalg.norm(cov.data - cov_desired.data, ord='fro') /
+           linalg.norm(cov.data, ord='fro'))
+    assert_true(err < tol, msg='%s >= %s' % (err, tol))
+    if nfree:
+        assert_equal(cov.nfree, cov_desired.nfree)
+
+
+ at pytest.mark.slowtest
 def test_cov_estimation_with_triggers():
     """Test estimation from raw with triggers."""
     tempdir = _TempDir()
-    raw = read_raw_fif(raw_fname, preload=False, add_eeg_ref=False)
-    raw.set_eeg_reference()
+    raw = read_raw_fif(raw_fname)
+    raw.set_eeg_reference(projection=True).load_data()
     events = find_events(raw, stim_channel='STI 014')
     event_ids = [1, 2, 3, 4]
     reject = dict(grad=10000e-13, mag=4e-12, eeg=80e-6, eog=150e-6)
@@ -222,59 +262,46 @@ def test_cov_estimation_with_triggers():
     events_merged = merge_events(events, event_ids, 1234)
     epochs = Epochs(raw, events_merged, 1234, tmin=-0.2, tmax=0,
                     baseline=(-0.2, -0.1), proj=True,
-                    reject=reject, preload=True, add_eeg_ref=False)
+                    reject=reject, preload=True)
 
     cov = compute_covariance(epochs, keep_sample_mean=True)
-    cov_mne = read_cov(cov_km_fname)
-    assert_true(cov_mne.ch_names == cov.ch_names)
-    assert_true((linalg.norm(cov.data - cov_mne.data, ord='fro') /
-                linalg.norm(cov.data, ord='fro')) < 0.005)
+    _assert_cov(cov, read_cov(cov_km_fname))
 
     # Test with tmin and tmax (different but not too much)
     cov_tmin_tmax = compute_covariance(epochs, tmin=-0.19, tmax=-0.01)
     assert_true(np.all(cov.data != cov_tmin_tmax.data))
-    assert_true((linalg.norm(cov.data - cov_tmin_tmax.data, ord='fro') /
-                 linalg.norm(cov_tmin_tmax.data, ord='fro')) < 0.05)
+    err = (linalg.norm(cov.data - cov_tmin_tmax.data, ord='fro') /
+           linalg.norm(cov_tmin_tmax.data, ord='fro'))
+    assert_true(err < 0.05, msg=err)
 
     # cov using a list of epochs and keep_sample_mean=True
     epochs = [Epochs(raw, events, ev_id, tmin=-0.2, tmax=0,
-              baseline=(-0.2, -0.1), proj=True, reject=reject,
-              add_eeg_ref=False)
+              baseline=(-0.2, -0.1), proj=True, reject=reject)
               for ev_id in event_ids]
-
     cov2 = compute_covariance(epochs, keep_sample_mean=True)
     assert_array_almost_equal(cov.data, cov2.data)
     assert_true(cov.ch_names == cov2.ch_names)
 
     # cov with keep_sample_mean=False using a list of epochs
     cov = compute_covariance(epochs, keep_sample_mean=False)
-    cov_mne = read_cov(cov_fname)
-    assert_true(cov_mne.ch_names == cov.ch_names)
-    assert_true((linalg.norm(cov.data - cov_mne.data, ord='fro') /
-                 linalg.norm(cov.data, ord='fro')) < 0.005)
+    _assert_cov(cov, read_cov(cov_fname), nfree=False)
 
     method_params = {'empirical': {'assume_centered': False}}
     assert_raises(ValueError, compute_covariance, epochs,
                   keep_sample_mean=False, method_params=method_params)
-
     assert_raises(ValueError, compute_covariance, epochs,
                   keep_sample_mean=False, method='factor_analysis')
 
     # test IO when computation done in Python
     cov.save(op.join(tempdir, 'test-cov.fif'))  # test saving
     cov_read = read_cov(op.join(tempdir, 'test-cov.fif'))
-    assert_true(cov_read.ch_names == cov.ch_names)
-    assert_true(cov_read.nfree == cov.nfree)
-    assert_true((linalg.norm(cov.data - cov_read.data, ord='fro') /
-                 linalg.norm(cov.data, ord='fro')) < 1e-5)
+    _assert_cov(cov, cov_read, 1e-5)
 
     # cov with list of epochs with different projectors
-    epochs = [Epochs(raw, events[:4], event_ids[0], tmin=-0.2, tmax=0,
-                     baseline=(-0.2, -0.1), proj=True, reject=reject,
-                     add_eeg_ref=False),
-              Epochs(raw, events[:4], event_ids[0], tmin=-0.2, tmax=0,
-                     baseline=(-0.2, -0.1), proj=False, reject=reject,
-                     add_eeg_ref=False)]
+    epochs = [Epochs(raw, events[:1], None, tmin=-0.2, tmax=0,
+                     baseline=(-0.2, -0.1), proj=True),
+              Epochs(raw, events[:1], None, tmin=-0.2, tmax=0,
+                     baseline=(-0.2, -0.1), proj=False)]
     # these should fail
     assert_raises(ValueError, compute_covariance, epochs)
     assert_raises(ValueError, compute_covariance, epochs, projs=None)
@@ -283,13 +310,18 @@ def test_cov_estimation_with_triggers():
         warnings.simplefilter('always')
         cov = compute_covariance(epochs, projs=epochs[0].info['projs'])
         cov = compute_covariance(epochs, projs=[])
-    assert_true(len(w) == 2)
+    assert_equal(len(w), 2)
 
     # test new dict support
-    epochs = Epochs(raw, events, dict(a=1, b=2, c=3, d=4), tmin=-0.2, tmax=0,
-                    baseline=(-0.2, -0.1), proj=True, reject=reject,
-                    add_eeg_ref=False)
-    compute_covariance(epochs)
+    epochs = Epochs(raw, events, dict(a=1, b=2, c=3, d=4), tmin=-0.01, tmax=0,
+                    proj=True, reject=reject, preload=True)
+    with warnings.catch_warnings(record=True):  # samples
+        compute_covariance(epochs)
+
+        # projs checking
+        compute_covariance(epochs, projs=[])
+    assert_raises(TypeError, compute_covariance, epochs, projs='foo')
+    assert_raises(TypeError, compute_covariance, epochs, projs=['foo'])
 
 
 def test_arithmetic_cov():
@@ -308,7 +340,7 @@ def test_arithmetic_cov():
 
 def test_regularize_cov():
     """Test cov regularization."""
-    raw = read_raw_fif(raw_fname, preload=False, add_eeg_ref=False)
+    raw = read_raw_fif(raw_fname)
     raw.info['bads'].append(raw.ch_names[0])  # test with bad channels
     noise_cov = read_cov(cov_fname)
     # Regularize noise cov
@@ -345,7 +377,7 @@ def test_whiten_evoked():
     assert_raises(RuntimeError, whiten_evoked, evoked, cov_bad, picks)
 
 
- at slow_test
+ at pytest.mark.slowtest
 def test_rank():
     """Test cov rank estimation."""
     # Test that our rank estimation works properly on a simple case
@@ -359,9 +391,9 @@ def test_rank():
     assert_true((cov['eig'][1:] > 0).all())  # all else should be > 0
 
     # Now do some more comprehensive tests
-    raw_sample = read_raw_fif(raw_fname, add_eeg_ref=False)
+    raw_sample = read_raw_fif(raw_fname)
 
-    raw_sss = read_raw_fif(hp_fif_fname, add_eeg_ref=False)
+    raw_sss = read_raw_fif(hp_fif_fname)
     raw_sss.add_proj(compute_proj_raw(raw_sss))
 
     cov_sample = compute_raw_covariance(raw_sample)
@@ -420,7 +452,7 @@ def test_rank():
                 n_projs_eeg = 0
 
             # check sss
-            if 'proc_history' in this_very_info:
+            if len(this_very_info['proc_history']) > 0:
                 mf = this_very_info['proc_history'][0]['max_info']
                 n_free = _get_sss_rank(mf)
                 if 'mag' not in ch_types and 'grad' not in ch_types:
@@ -463,11 +495,16 @@ def test_cov_scaling():
     assert_array_equal(cov, cov2)
     assert_true(cov.max() < 1)
 
+    data = evoked.data.copy()
+    _apply_scaling_array(data, picks_list, scalings=scalings)
+    _undo_scaling_array(data, picks_list, scalings=scalings)
+    assert_allclose(data, evoked.data, atol=1e-20)
+
 
- at requires_sklearn_0_15
+ at requires_version('sklearn', '0.15')
 def test_auto_low_rank():
     """Test probabilistic low rank estimators."""
-    n_samples, n_features, rank = 400, 20, 10
+    n_samples, n_features, rank = 400, 10, 5
     sigma = 0.1
 
     def get_data(n_samples, n_features, rank, sigma):
@@ -483,7 +520,7 @@ def test_auto_low_rank():
 
     X = get_data(n_samples=n_samples, n_features=n_features, rank=rank,
                  sigma=sigma)
-    method_params = {'iter_n_components': [9, 10, 11]}
+    method_params = {'iter_n_components': [4, 5, 6]}
     cv = 3
     n_jobs = 1
     mode = 'factor_analysis'
@@ -511,11 +548,11 @@ def test_auto_low_rank():
                   n_jobs=n_jobs, method_params=method_params, cv=cv)
 
 
- at slow_test
- at requires_sklearn_0_15
+ at pytest.mark.slowtest
+ at requires_version('sklearn', '0.15')
 def test_compute_covariance_auto_reg():
     """Test automated regularization."""
-    raw = read_raw_fif(raw_fname, preload=True, add_eeg_ref=False)
+    raw = read_raw_fif(raw_fname, preload=True)
     raw.resample(100, npad='auto')  # much faster estimation
     events = find_events(raw, stim_channel='STI 014')
     event_ids = [1, 2, 3, 4]
@@ -529,8 +566,7 @@ def test_compute_covariance_auto_reg():
     raw.info.normalize_proj()
     epochs = Epochs(
         raw, events_merged, 1234, tmin=-0.2, tmax=0,
-        baseline=(-0.2, -0.1), proj=True, reject=reject, preload=True,
-        add_eeg_ref=False)
+        baseline=(-0.2, -0.1), proj=True, reject=reject, preload=True)
     epochs = epochs.crop(None, 0)[:10]
 
     method_params = dict(factor_analysis=dict(iter_n_components=[3]),
@@ -538,8 +574,33 @@ def test_compute_covariance_auto_reg():
 
     covs = compute_covariance(epochs, method='auto',
                               method_params=method_params,
-                              projs=True,
                               return_estimators=True)
+    # make sure regularization produces structured differencess
+    diag_mask = np.eye(len(epochs.ch_names)).astype(bool)
+    off_diag_mask = np.invert(diag_mask)
+    for cov_a, cov_b in itt.combinations(covs, 2):
+        if (cov_a['method'] == 'diagonal_fixed' and
+                # here we have diagnoal or no regularization.
+                cov_b['method'] == 'empirical'):
+
+            assert_true(not np.any(
+                        cov_a['data'][diag_mask] ==
+                        cov_b['data'][diag_mask]))
+
+            # but the rest is the same
+            assert_array_equal(
+                cov_a['data'][off_diag_mask],
+                cov_b['data'][off_diag_mask])
+
+        else:
+            # and here we have shrinkage everywhere.
+            assert_true(not np.any(
+                        cov_a['data'][diag_mask] ==
+                        cov_b['data'][diag_mask]))
+
+            assert_true(not np.any(
+                        cov_a['data'][diag_mask] ==
+                        cov_b['data'][diag_mask]))
 
     logliks = [c['loglik'] for c in covs]
     assert_true(np.diff(logliks).max() <= 0)  # descending order
@@ -562,4 +623,5 @@ def test_compute_covariance_auto_reg():
     assert_raises(ValueError, compute_covariance, epochs, method='shrunk',
                   scalings=dict(misc=123))
 
+
 run_tests_if_main()
diff --git a/mne/tests/test_dipole.py b/mne/tests/test_dipole.py
index 1db01a6..56b1654 100644
--- a/mne/tests/test_dipole.py
+++ b/mne/tests/test_dipole.py
@@ -1,11 +1,10 @@
-import os
 import os.path as op
-import sys
 import warnings
 
 import numpy as np
 from nose.tools import assert_true, assert_equal, assert_raises
-from numpy.testing import assert_allclose
+from numpy.testing import assert_allclose, assert_array_equal
+import pytest
 
 from mne import (read_dipole, read_forward_solution,
                  convert_forward_solution, read_evokeds, read_cov,
@@ -14,13 +13,14 @@ from mne import (read_dipole, read_forward_solution,
                  pick_info, EvokedArray, read_source_spaces, make_ad_hoc_cov,
                  make_forward_solution, Dipole, DipoleFixed, Epochs,
                  make_fixed_length_events)
+from mne.dipole import get_phantom_dipoles
 from mne.simulation import simulate_evoked
 from mne.datasets import testing
-from mne.utils import (run_tests_if_main, _TempDir, slow_test, requires_mne,
-                       run_subprocess)
+from mne.utils import run_tests_if_main, _TempDir, requires_mne, run_subprocess
 from mne.proj import make_eeg_average_ref_proj
 
 from mne.io import read_raw_fif, read_raw_ctf
+from mne.io.constants import FIFF
 
 from mne.surface import _compute_nearest
 from mne.bem import _bem_find_surface, read_bem_solution
@@ -28,18 +28,19 @@ from mne.transforms import apply_trans, _get_trans
 
 warnings.simplefilter('always')
 data_path = testing.data_path(download=False)
-fname_raw = op.join(data_path, 'MEG', 'sample', 'sample_audvis_trunc_raw.fif')
-fname_dip = op.join(data_path, 'MEG', 'sample', 'sample_audvis_trunc_set1.dip')
-fname_evo = op.join(data_path, 'MEG', 'sample', 'sample_audvis_trunc-ave.fif')
-fname_cov = op.join(data_path, 'MEG', 'sample', 'sample_audvis_trunc-cov.fif')
+meg_path = op.join(data_path, 'MEG', 'sample')
+fname_dip_xfit = op.join(meg_path, 'sample_audvis-ave_xfit.dip')
+fname_raw = op.join(meg_path, 'sample_audvis_trunc_raw.fif')
+fname_dip = op.join(meg_path, 'sample_audvis_trunc_set1.dip')
+fname_evo = op.join(meg_path, 'sample_audvis_trunc-ave.fif')
+fname_evo_full = op.join(meg_path, 'sample_audvis-ave.fif')
+fname_cov = op.join(meg_path, 'sample_audvis_trunc-cov.fif')
+fname_trans = op.join(meg_path, 'sample_audvis_trunc-trans.fif')
+fname_fwd = op.join(meg_path, 'sample_audvis_trunc-meg-eeg-oct-6-fwd.fif')
 fname_bem = op.join(data_path, 'subjects', 'sample', 'bem',
                     'sample-1280-1280-1280-bem-sol.fif')
 fname_src = op.join(data_path, 'subjects', 'sample', 'bem',
                     'sample-oct-2-src.fif')
-fname_trans = op.join(data_path, 'MEG', 'sample',
-                      'sample_audvis_trunc-trans.fif')
-fname_fwd = op.join(data_path, 'MEG', 'sample',
-                    'sample_audvis_trunc-meg-eeg-oct-6-fwd.fif')
 fname_xfit_dip = op.join(data_path, 'dip', 'fixed_auto.fif')
 fname_xfit_dip_txt = op.join(data_path, 'dip', 'fixed_auto.dip')
 fname_xfit_seq_txt = op.join(data_path, 'dip', 'sequential.dip')
@@ -81,10 +82,9 @@ def test_io_dipoles():
 @testing.requires_testing_data
 def test_dipole_fitting_ctf():
     """Test dipole fitting with CTF data."""
-    raw_ctf = read_raw_ctf(fname_ctf).set_eeg_reference()
+    raw_ctf = read_raw_ctf(fname_ctf).set_eeg_reference(projection=True)
     events = make_fixed_length_events(raw_ctf, 1)
-    evoked = Epochs(raw_ctf, events, 1, 0, 0, baseline=None,
-                    add_eeg_ref=False).average()
+    evoked = Epochs(raw_ctf, events, 1, 0, 0, baseline=None).average()
     cov = make_ad_hoc_cov(evoked.info)
     sphere = make_sphere_model((0., 0., 0.))
     # XXX Eventually we should do some better checks about accuracy, but
@@ -94,18 +94,19 @@ def test_dipole_fitting_ctf():
     fit_dipole(evoked, cov, sphere)
 
 
- at slow_test
+ at pytest.mark.slowtest
 @testing.requires_testing_data
 @requires_mne
 def test_dipole_fitting():
     """Test dipole fitting."""
-    amp = 10e-9
+    amp = 100e-9
     tempdir = _TempDir()
     rng = np.random.RandomState(0)
     fname_dtemp = op.join(tempdir, 'test.dip')
     fname_sim = op.join(tempdir, 'test-ave.fif')
     fwd = convert_forward_solution(read_forward_solution(fname_fwd),
-                                   surf_ori=False, force_fixed=True)
+                                   surf_ori=False, force_fixed=True,
+                                   use_cps=True)
     evoked = read_evokeds(fname_evo)[0]
     cov = read_cov(fname_cov)
     n_per_hemi = 5
@@ -113,7 +114,7 @@ def test_dipole_fitting():
                 for s in fwd['src']]
     nv = sum(len(v) for v in vertices)
     stc = SourceEstimate(amp * np.eye(nv), vertices, 0, 0.001)
-    evoked = simulate_evoked(fwd, stc, evoked.info, cov, snr=20,
+    evoked = simulate_evoked(fwd, stc, evoked.info, cov, nave=evoked.nave,
                              random_state=rng)
     # For speed, let's use a subset of channels (strange but works)
     picks = np.sort(np.concatenate([
@@ -133,33 +134,30 @@ def test_dipole_fitting():
 
     # Run mne-python version
     sphere = make_sphere_model(head_radius=0.1)
-    dip, residuals = fit_dipole(evoked, fname_cov, sphere, fname_fwd)
+    with warnings.catch_warnings(record=True):
+        dip, residuals = fit_dipole(evoked, cov, sphere, fname_fwd)
 
     # Sanity check: do our residuals have less power than orig data?
     data_rms = np.sqrt(np.sum(evoked.data ** 2, axis=0))
     resi_rms = np.sqrt(np.sum(residuals ** 2, axis=0))
-    factor = 1.
-    # XXX weird, inexplicable differenc for 3.5 build we'll assume is due to
-    # Anaconda bug for now...
-    if os.getenv('TRAVIS', 'false') == 'true' and \
-            sys.version[:3] in ('3.5', '2.7'):
-        factor = 0.8
-    assert_true((data_rms > factor * resi_rms).all(),
-                msg='%s (factor: %s)' % ((data_rms / resi_rms).min(), factor))
+    assert_true((data_rms > resi_rms * 0.95).all(),
+                msg='%s (factor: %s)' % ((data_rms / resi_rms).min(), 0.95))
 
     # Compare to original points
     transform_surface_to(fwd['src'][0], 'head', fwd['mri_head_t'])
     transform_surface_to(fwd['src'][1], 'head', fwd['mri_head_t'])
+    assert_equal(fwd['src'][0]['coord_frame'], FIFF.FIFFV_COORD_HEAD)
     src_rr = np.concatenate([s['rr'][v] for s, v in zip(fwd['src'], vertices)],
                             axis=0)
     src_nn = np.concatenate([s['nn'][v] for s, v in zip(fwd['src'], vertices)],
                             axis=0)
 
     # MNE-C skips the last "time" point :(
-    dip.crop(dip_c.times[0], dip_c.times[-1])
+    out = dip.crop(dip_c.times[0], dip_c.times[-1])
+    assert_true(dip is out)
     src_rr, src_nn = src_rr[:-1], src_nn[:-1]
 
-    # check that we did at least as well
+    # check that we did about as well
     corrs, dists, gc_dists, amp_errs, gofs = [], [], [], [], []
     for d in (dip_c, dip):
         new = d.pos
@@ -170,13 +168,15 @@ def test_dipole_fitting():
                                                      axis=1)))]
         amp_errs += [np.sqrt(np.mean((amp - d.amplitude) ** 2))]
         gofs += [np.mean(d.gof)]
-    assert_true(dists[0] >= dists[1] * factor, 'dists: %s' % dists)
-    assert_true(corrs[0] <= corrs[1] / factor, 'corrs: %s' % corrs)
-    assert_true(gc_dists[0] >= gc_dists[1] * factor,
+    factor = 0.8
+    assert_true(dists[0] / factor >= dists[1], 'dists: %s' % dists)
+    assert_true(corrs[0] * factor <= corrs[1], 'corrs: %s' % corrs)
+    assert_true(gc_dists[0] / factor >= gc_dists[1] * 0.8,
                 'gc-dists (ori): %s' % gc_dists)
-    assert_true(amp_errs[0] >= amp_errs[1] * factor,
+    assert_true(amp_errs[0] / factor >= amp_errs[1],
                 'amplitude errors: %s' % amp_errs)
-    assert_true(gofs[0] <= gofs[1] / factor, 'gof: %s' % gofs)
+    # This one is weird because our cov/sim/picking is weird
+    assert_true(gofs[0] * factor <= gofs[1] * 2, 'gof: %s' % gofs)
 
 
 @testing.requires_testing_data
@@ -216,7 +216,9 @@ def test_dipole_fitting_fixed():
     assert_allclose(resid, resid_fixed[:, [t_idx]])
     _check_roundtrip_fixed(dip_fixed)
     # Degenerate conditions
-    assert_raises(ValueError, fit_dipole, evoked, cov, sphere, pos=[0])
+    evoked_nan = evoked.copy().crop(0, 0)
+    evoked_nan.data[0, 0] = None
+    assert_raises(ValueError, fit_dipole, evoked_nan, cov, sphere)
     assert_raises(ValueError, fit_dipole, evoked, cov, sphere, ori=[1, 0, 0])
     assert_raises(ValueError, fit_dipole, evoked, cov, sphere, pos=[0, 0, 0],
                   ori=[2, 0, 0])
@@ -243,7 +245,7 @@ def test_len_index_dipoles():
 def test_min_distance_fit_dipole():
     """Test dipole min_dist to inner_skull."""
     subject = 'sample'
-    raw = read_raw_fif(fname_raw, preload=True, add_eeg_ref=False)
+    raw = read_raw_fif(fname_raw, preload=True)
 
     # select eeg data
     picks = pick_types(raw.info, meg=False, eeg=True, exclude='bads')
@@ -299,7 +301,7 @@ def test_accuracy():
         src = read_source_spaces(fname_src)
 
         fwd = make_forward_solution(evoked.info, None, src, bem)
-        fwd = convert_forward_solution(fwd, force_fixed=True)
+        fwd = convert_forward_solution(fwd, force_fixed=True, use_cps=True)
         vertices = [src[0]['vertno'], src[1]['vertno']]
         n_vertices = sum(len(v) for v in vertices)
         amp = 10e-9
@@ -307,7 +309,8 @@ def test_accuracy():
         data[-1, -1] = 1.
         data *= amp
         stc = SourceEstimate(data, vertices, 0., 1e-3, 'sample')
-        sim = simulate_evoked(fwd, stc, evoked.info, cov=None, snr=np.inf)
+        evoked.info.normalize_proj()
+        sim = simulate_evoked(fwd, stc, evoked.info, cov=None, nave=np.inf)
 
         cov = make_ad_hoc_cov(evoked.info)
         dip = fit_dipole(sim, cov, bem, min_dist=0.001)[0]
@@ -334,6 +337,9 @@ def test_accuracy():
 def test_dipole_fixed():
     """Test reading a fixed-position dipole (from Xfit)."""
     dip = read_dipole(fname_xfit_dip)
+    # print the representation of the objet DipoleFixed
+    print(dip)
+
     _check_roundtrip_fixed(dip)
     with warnings.catch_warnings(record=True) as w:  # unused fields
         dip_txt = read_dipole(fname_xfit_dip_txt)
@@ -360,4 +366,41 @@ def _check_roundtrip_fixed(dip):
                     'cal', 'coil_type', 'scanno', 'logno'):
             assert_allclose(ch_1[key], ch_2[key], err_msg=key)
 
+
+def test_get_phantom_dipoles():
+    """Test getting phantom dipole locations."""
+    assert_raises(ValueError, get_phantom_dipoles, 0)
+    assert_raises(ValueError, get_phantom_dipoles, 'foo')
+    for kind in ('vectorview', 'otaniemi'):
+        pos, ori = get_phantom_dipoles(kind)
+        assert_equal(pos.shape, (32, 3))
+        assert_equal(ori.shape, (32, 3))
+
+
+ at testing.requires_testing_data
+def test_confidence():
+    """Test confidence limits."""
+    tempdir = _TempDir()
+    evoked = read_evokeds(fname_evo_full, 'Left Auditory', baseline=(None, 0))
+    evoked.crop(0.08, 0.08).pick_types()  # MEG-only
+    cov = make_ad_hoc_cov(evoked.info)
+    sphere = make_sphere_model((0., 0., 0.04), 0.08)
+    dip_py = fit_dipole(evoked, cov, sphere)[0]
+    fname_test = op.join(tempdir, 'temp-dip.txt')
+    dip_py.save(fname_test)
+    dip_read = read_dipole(fname_test)
+    with warnings.catch_warnings(record=True) as w:
+        dip_xfit = read_dipole(fname_dip_xfit)
+    assert_equal(len(w), 1)
+    assert_true("['noise/ft/cm', 'prob']" in str(w[0].message))
+    for dip_check in (dip_py, dip_read):
+        assert_allclose(dip_check.pos, dip_xfit.pos, atol=5e-4)  # < 0.5 mm
+        assert_allclose(dip_check.gof, dip_xfit.gof, atol=5e-1)  # < 0.5%
+        assert_array_equal(dip_check.nfree, dip_xfit.nfree)  # exact match
+        assert_allclose(dip_check.khi2, dip_xfit.khi2, rtol=2e-2)  # 2% miss
+        assert_equal(set(dip_check.conf.keys()), set(dip_xfit.conf.keys()))
+        for key in sorted(dip_check.conf.keys()):
+            assert_allclose(dip_check.conf[key], dip_xfit.conf[key],
+                            rtol=1.5e-1, err_msg=key)
+
 run_tests_if_main(False)
diff --git a/mne/tests/test_docstring_parameters.py b/mne/tests/test_docstring_parameters.py
index d38fcdf..12935dd 100644
--- a/mne/tests/test_docstring_parameters.py
+++ b/mne/tests/test_docstring_parameters.py
@@ -1,8 +1,10 @@
 from __future__ import print_function
 
-from nose.tools import assert_true
-import sys
 import inspect
+import os.path as op
+import re
+import sys
+from unittest import SkipTest
 import warnings
 
 from pkgutil import walk_packages
@@ -17,14 +19,16 @@ public_modules = [
     # the list of modules users need to access for all functionality
     'mne',
     'mne.beamformer',
+    'mne.chpi',
     'mne.connectivity',
     'mne.datasets',
+    'mne.datasets.brainstorm',
+    'mne.datasets.hf_sef',
     'mne.datasets.megsim',
     'mne.datasets.sample',
-    'mne.datasets.spm_face',
     'mne.decoding',
+    'mne.dipole',
     'mne.filter',
-    'mne.gui',
     'mne.inverse_sparse',
     'mne.io',
     'mne.io.kit',
@@ -37,6 +41,7 @@ public_modules = [
     'mne.source_space',
     'mne.stats',
     'mne.time_frequency',
+    'mne.time_frequency.tfr',
     'mne.viz',
 ]
 
@@ -54,9 +59,22 @@ def get_name(func):
 
 # functions to ignore args / docstring of
 _docstring_ignores = [
+    'mne.io.Info',  # Parameters
     'mne.io.write',  # always ignore these
-    'mne.decoding.csp.CSP.fit',  # deprecated epochs_data
-    'mne.decoding.csp.CSP.transform'  # deprecated epochs_data
+    # Deprecations
+    'mne.connectivity.effective.phase_slope_index',
+    'mne.connectivity.spectral.spectral_connectivity',
+    'mne.decoding.time_frequency.*__init__',  # TimeFrequency
+    'mne.minimum_norm.time_frequency.source_induced_power',
+    'mne.time_frequency.csd.*__init__',  # CrossSpectralDensity
+    'mne.time_frequency.multitaper.tfr_array_multitaper',
+    'mne.time_frequency.tfr.tfr_array_morlet',
+    'mne.time_frequency.tfr.tfr_morlet',
+    'mne.decoding.csp.*plot_.*',  # CSP and SPoc but on Py3k unbound methods
+    'mne.decoding.csp.*plot_.*',  # are just functions so our naming is odd
+    r'mne.*\.plot_topomap',
+    r'mne.*\.to_data_frame',
+    'mne.viz.topomap.plot_evoked_topomap',
 ]
 
 _tab_ignores = [
@@ -79,7 +97,11 @@ def check_parameters_match(func, doc=None):
 
     if doc is None:
         with warnings.catch_warnings(record=True) as w:
-            doc = docscrape.FunctionDoc(func)
+            try:
+                doc = docscrape.FunctionDoc(func)
+            except Exception as exp:
+                incorrect += [name_ + ' parsing error: ' + str(exp)]
+                return incorrect
         if len(w):
             raise RuntimeError('Error for %s:\n%s' % (name_, w[0]))
     # check set
@@ -90,7 +112,7 @@ def check_parameters_match(func, doc=None):
     if len(param_names) != len(args):
         bad = str(sorted(list(set(param_names) - set(args)) +
                          list(set(args) - set(param_names))))
-        if not any(d in name_ for d in _docstring_ignores) and \
+        if not any(re.match(d, name_) for d in _docstring_ignores) and \
                 'deprecation_wrapped' not in func.__code__.co_name:
             incorrect += [name_ + ' arg mismatch: ' + bad]
     else:
@@ -102,11 +124,21 @@ def check_parameters_match(func, doc=None):
 
 @requires_numpydoc
 def test_docstring_parameters():
-    """Test module docsting formatting"""
+    """Test module docstring formatting."""
     from numpydoc import docscrape
+
+    # skip modules that require mayavi if mayavi is not installed
+    public_modules_ = public_modules[:]
+    try:
+        import mayavi  # noqa: F401 analysis:ignore
+        public_modules_.append('mne.gui')
+    except ImportError:
+        pass
+
     incorrect = []
-    for name in public_modules:
-        module = __import__(name, globals())
+    for name in public_modules_:
+        with warnings.catch_warnings(record=True):  # traits warnings
+            module = __import__(name, globals())
         for submod in name.split('.')[1:]:
             module = getattr(module, submod)
         classes = inspect.getmembers(module, inspect.isclass)
@@ -137,15 +169,150 @@ def test_docstring_parameters():
 
 def test_tabs():
     """Test that there are no tabs in our source files"""
+    # avoid importing modules that require mayavi if mayavi is not installed
+    ignore = _tab_ignores[:]
+    try:
+        import mayavi  # noqa: F401 analysis:ignore
+    except ImportError:
+        ignore.extend('mne.gui.' + name for name in
+                      ('_coreg_gui', '_fiducials_gui', '_file_traits', '_help',
+                       '_kit2fiff_gui', '_marker_gui', '_viewer'))
+
     for importer, modname, ispkg in walk_packages(mne.__path__, prefix='mne.'):
-        if not ispkg and modname not in _tab_ignores:
+        # because we don't import e.g. mne.tests w/mne
+        if not ispkg and modname not in ignore:
             # mod = importlib.import_module(modname)  # not py26 compatible!
-            __import__(modname)  # because we don't import e.g. mne.tests w/mne
+            try:
+                with warnings.catch_warnings(record=True):  # traits
+                    __import__(modname)
+            except Exception:  # can't import properly
+                continue
             mod = sys.modules[modname]
-            source = getsource(mod)
-            assert_true('\t' not in source,
-                        '"%s" has tabs, please remove them or add it to the'
-                        'ignore list' % modname)
+            try:
+                source = getsource(mod)
+            except IOError:  # user probably should have run "make clean"
+                continue
+            assert '\t' not in source, ('"%s" has tabs, please remove them '
+                                        'or add it to the ignore list'
+                                        % modname)
+
+
+documented_ignored_mods = (
+    'mne.cuda',
+    'mne.io.write',
+    'mne.utils',
+    'mne.viz.utils',
+)
+documented_ignored_names = """
+BaseEstimator
+ContainsMixin
+CrossSpectralDensity
+FilterMixin
+GeneralizationAcrossTime
+RawFIF
+TimeDecoding
+TimeMixin
+ToDataFrameMixin
+TransformerMixin
+UpdateChannelsMixin
+adjust_axes
+apply_lcmv
+apply_lcmv_epochs
+apply_lcmv_raw
+apply_maxfilter
+apply_trans
+channel_type
+check_n_jobs
+combine_kit_markers
+combine_tfr
+combine_transforms
+design_mne_c_filter
+detrend
+dir_tree_find
+fast_cross_3d
+fiff_open
+find_outliers
+find_source_space_hemi
+find_tag
+get_score_funcs
+get_sosfiltfilt
+get_version
+invert_transform
+is_power2
+iter_topography
+kit2fiff
+label_src_vertno_sel
+make_eeg_average_ref_proj
+make_lcmv
+make_projector
+mesh_dist
+mesh_edges
+minimum_phase
+next_fast_len
+parallel_func
+pick_channels_evoked
+plot_epochs_psd
+plot_epochs_psd_topomap
+plot_raw_psd_topo
+plot_source_spectrogram
+prepare_inverse_operator
+read_fiducials
+read_tag
+rescale
+simulate_noise_evoked
+source_estimate_quantification
+whiten_evoked
+write_fiducials
+write_info
+""".split('\n')
+
+
+def test_documented():
+    """Test that public functions and classes are documented."""
+    # skip modules that require mayavi if mayavi is not installed
+    public_modules_ = public_modules[:]
+    try:
+        import mayavi  # noqa: F401, analysis:ignore
+        public_modules_.append('mne.gui')
+    except ImportError:
+        pass
+
+    doc_file = op.abspath(op.join(op.dirname(__file__), '..', '..', 'doc',
+                                  'python_reference.rst'))
+    if not op.isfile(doc_file):
+        raise SkipTest('Documentation file not found: %s' % doc_file)
+    known_names = list()
+    with open(doc_file, 'rb') as fid:
+        for line in fid:
+            line = line.decode('utf-8')
+            if not line.startswith('  '):  # at least two spaces
+                continue
+            line = line.split()
+            if len(line) == 1 and line[0] != ':':
+                known_names.append(line[0].split('.')[-1])
+    known_names = set(known_names)
+
+    missing = []
+    for name in public_modules_:
+        with warnings.catch_warnings(record=True):  # traits warnings
+            module = __import__(name, globals())
+        for submod in name.split('.')[1:]:
+            module = getattr(module, submod)
+        classes = inspect.getmembers(module, inspect.isclass)
+        functions = inspect.getmembers(module, inspect.isfunction)
+        checks = list(classes) + list(functions)
+        for name, cf in checks:
+            if not name.startswith('_') and name not in known_names:
+                from_mod = inspect.getmodule(cf).__name__
+                if (from_mod.startswith('mne') and
+                        not from_mod.startswith('mne.externals') and
+                        from_mod not in documented_ignored_mods and
+                        name not in documented_ignored_names):
+                    missing.append('%s (%s.%s)' % (name, from_mod, name))
+    if len(missing) > 0:
+        raise AssertionError('\n\nFound new public members missing from '
+                             'doc/python_reference.rst:\n\n* ' +
+                             '\n* '.join(sorted(set(missing))))
 
 
 run_tests_if_main()
diff --git a/mne/tests/test_epochs.py b/mne/tests/test_epochs.py
index 47259fa..6396f61 100644
--- a/mne/tests/test_epochs.py
+++ b/mne/tests/test_epochs.py
@@ -8,13 +8,12 @@ from copy import deepcopy
 
 from nose.tools import (assert_true, assert_equal, assert_raises,
                         assert_not_equal)
-
+import pytest
 from numpy.testing import (assert_array_equal, assert_array_almost_equal,
                            assert_allclose)
 import numpy as np
 import copy as cp
 import warnings
-from scipy import fftpack
 import matplotlib
 
 from mne import (Epochs, Annotations, read_events, pick_events, read_epochs,
@@ -25,8 +24,8 @@ from mne.baseline import rescale
 from mne.preprocessing import maxwell_filter
 from mne.epochs import (
     bootstrap, equalize_epoch_counts, combine_event_ids, add_channels_epochs,
-    EpochsArray, concatenate_epochs, _BaseEpochs, average_movements)
-from mne.utils import (_TempDir, requires_pandas, slow_test,
+    EpochsArray, concatenate_epochs, BaseEpochs, average_movements)
+from mne.utils import (_TempDir, requires_pandas,
                        run_tests_if_main, requires_version)
 from mne.chpi import read_head_pos, head_pos_to_trans_rot_t
 
@@ -55,19 +54,20 @@ event_name = op.join(base_dir, 'test-eve.fif')
 evoked_nf_name = op.join(base_dir, 'test-nf-ave.fif')
 
 event_id, tmin, tmax = 1, -0.2, 0.5
-event_id_2 = 2
+event_id_2 = np.int64(2)  # to test non Python int types
 rng = np.random.RandomState(42)
 
 
 def _get_data(preload=False):
     """Get data."""
-    raw = read_raw_fif(raw_fname, preload=preload, add_eeg_ref=False)
+    raw = read_raw_fif(raw_fname, preload=preload)
     events = read_events(event_name)
     picks = pick_types(raw.info, meg=True, eeg=True, stim=True,
                        ecg=True, eog=True, include=['STI 014'],
                        exclude='bads')
     return raw, events, picks
 
+
 reject = dict(grad=1000e-12, mag=4e-12, eeg=80e-6, eog=150e-6)
 flat = dict(grad=1e-15, mag=1e-15)
 
@@ -76,7 +76,7 @@ def test_hierarchical():
     """Test hierarchical access."""
     raw, events, picks = _get_data()
     event_id = {'a/1': 1, 'a/2': 2, 'b/1': 3, 'b/2': 4}
-    epochs = Epochs(raw, events, event_id, add_eeg_ref=False, preload=True)
+    epochs = Epochs(raw, events, event_id, preload=True)
     epochs_a1 = epochs['a/1']
     epochs_a2 = epochs['a/2']
     epochs_b1 = epochs['b/1']
@@ -94,30 +94,29 @@ def test_hierarchical():
     assert_array_equal(epochs.get_data(), epochs_all.get_data())
 
 
- at slow_test
+ at pytest.mark.slowtest
 @testing.requires_testing_data
 def test_average_movements():
     """Test movement averaging algorithm."""
     # usable data
     crop = 0., 10.
     origin = (0., 0., 0.04)
-    raw = read_raw_fif(fname_raw_move, allow_maxshield='yes',
-                       add_eeg_ref=False)
+    raw = read_raw_fif(fname_raw_move, allow_maxshield='yes')
     raw.info['bads'] += ['MEG2443']  # mark some bad MEG channel
-    raw.crop(*crop, copy=False).load_data()
-    raw.filter(None, 20, method='iir')
+    raw.crop(*crop).load_data()
+    raw.filter(None, 20, fir_design='firwin')
     events = make_fixed_length_events(raw, event_id)
     picks = pick_types(raw.info, meg=True, eeg=True, stim=True,
                        ecg=True, eog=True, exclude=())
     epochs = Epochs(raw, events, event_id, tmin, tmax, picks=picks, proj=False,
-                    preload=True, add_eeg_ref=False)
+                    preload=True)
     epochs_proj = Epochs(raw, events[:1], event_id, tmin, tmax, picks=picks,
-                         proj=True, preload=True, add_eeg_ref=False)
+                         proj=True, preload=True)
     raw_sss_stat = maxwell_filter(raw, origin=origin, regularize=None,
                                   bad_condition='ignore')
     del raw
     epochs_sss_stat = Epochs(raw_sss_stat, events, event_id, tmin, tmax,
-                             picks=picks, proj=False, add_eeg_ref=False)
+                             picks=picks, proj=False)
     evoked_sss_stat = epochs_sss_stat.average()
     del raw_sss_stat, epochs_sss_stat
     head_pos = read_head_pos(fname_raw_move_pos)
@@ -147,14 +146,14 @@ def test_average_movements():
     assert_allclose(evoked_move_non.data[meg_picks],
                     evoked_move_all.data[meg_picks], atol=1e-20)
     # compare to averaged movecomp version (should be fairly similar)
-    raw_sss = read_raw_fif(fname_raw_movecomp_sss, add_eeg_ref=False)
-    raw_sss.crop(*crop, copy=False).load_data()
-    raw_sss.filter(None, 20, method='iir')
+    raw_sss = read_raw_fif(fname_raw_movecomp_sss)
+    raw_sss.crop(*crop).load_data()
+    raw_sss.filter(None, 20, fir_design='firwin')
     picks_sss = pick_types(raw_sss.info, meg=True, eeg=True, stim=True,
                            ecg=True, eog=True, exclude=())
     assert_array_equal(picks, picks_sss)
     epochs_sss = Epochs(raw_sss, events, event_id, tmin, tmax,
-                        picks=picks_sss, proj=False, add_eeg_ref=False)
+                        picks=picks_sss, proj=False)
     evoked_sss = epochs_sss.average()
     assert_equal(evoked_std.nave, evoked_sss.nave)
     # this should break the non-MEG channels
@@ -199,22 +198,19 @@ def test_reject():
     assert_raises(TypeError, pick_types, raw)
     picks_meg = pick_types(raw.info, meg=True, eeg=False)
     assert_raises(TypeError, Epochs, raw, events, event_id, tmin, tmax,
-                  picks=picks, preload=False, reject='foo',
-                  add_eeg_ref=False)
+                  picks=picks, preload=False, reject='foo')
     assert_raises(ValueError, Epochs, raw, events, event_id, tmin, tmax,
-                  picks=picks_meg, preload=False, reject=dict(eeg=1.),
-                  add_eeg_ref=False)
+                  picks=picks_meg, preload=False, reject=dict(eeg=1.))
     # this one is okay because it's not actually requesting rejection
     Epochs(raw, events, event_id, tmin, tmax, picks=picks_meg,
-           preload=False, reject=dict(eeg=np.inf), add_eeg_ref=False)
+           preload=False, reject=dict(eeg=np.inf))
     for val in (None, -1):  # protect against older MNE-C types
         for kwarg in ('reject', 'flat'):
             assert_raises(ValueError, Epochs, raw, events, event_id,
                           tmin, tmax, picks=picks_meg, preload=False,
-                          add_eeg_ref=False, **{kwarg: dict(grad=val)})
+                          **{kwarg: dict(grad=val)})
     assert_raises(KeyError, Epochs, raw, events, event_id, tmin, tmax,
-                  picks=picks, preload=False, reject=dict(foo=1.),
-                  add_eeg_ref=False)
+                  picks=picks, preload=False, reject=dict(foo=1.))
 
     data_7 = dict()
     keep_idx = [0, 1, 2]
@@ -222,7 +218,7 @@ def test_reject():
         for proj in (True, False, 'delayed'):
             # no rejection
             epochs = Epochs(raw, events, event_id, tmin, tmax, picks=picks,
-                            preload=preload, add_eeg_ref=False)
+                            preload=preload)
             assert_raises(ValueError, epochs.drop_bad, reject='foo')
             epochs.drop_bad()
             assert_equal(len(epochs), len(events))
@@ -234,7 +230,7 @@ def test_reject():
 
             # with rejection
             epochs = Epochs(raw, events, event_id, tmin, tmax, picks=picks,
-                            reject=reject, preload=preload, add_eeg_ref=False)
+                            reject=reject, preload=preload)
             epochs.drop_bad()
             assert_equal(len(epochs), len(events) - 4)
             assert_array_equal(epochs.selection, selection)
@@ -243,7 +239,7 @@ def test_reject():
 
             # rejection post-hoc
             epochs = Epochs(raw, events, event_id, tmin, tmax, picks=picks,
-                            preload=preload, add_eeg_ref=False)
+                            preload=preload)
             epochs.drop_bad()
             assert_equal(len(epochs), len(events))
             assert_array_equal(epochs.get_data(), data_7[proj])
@@ -257,8 +253,7 @@ def test_reject():
             # rejection twice
             reject_part = dict(grad=1100e-12, mag=4e-12, eeg=80e-6, eog=150e-6)
             epochs = Epochs(raw, events, event_id, tmin, tmax, picks=picks,
-                            reject=reject_part, preload=preload,
-                            add_eeg_ref=False)
+                            reject=reject_part, preload=preload)
             epochs.drop_bad()
             assert_equal(len(epochs), len(events) - 1)
             epochs.drop_bad(reject)
@@ -279,23 +274,31 @@ def test_reject():
             # rejection of subset of trials (ensure array ownership)
             reject_part = dict(grad=1100e-12, mag=4e-12, eeg=80e-6, eog=150e-6)
             epochs = Epochs(raw, events, event_id, tmin, tmax, picks=picks,
-                            reject=None, preload=preload,
-                            add_eeg_ref=False)
+                            reject=None, preload=preload)
             epochs = epochs[:-1]
             epochs.drop_bad(reject=reject)
             assert_equal(len(epochs), len(events) - 4)
             assert_array_equal(epochs.get_data(), data_7[proj][keep_idx])
 
-            # rejection on annotations
-            raw.annotations = Annotations([(events[0][0] - raw.first_samp) /
-                                           raw.info['sfreq']], [1], ['BAD'])
+        # rejection on annotations
+        sfreq = raw.info['sfreq']
+        onsets = [(event[0] - raw.first_samp) / sfreq for event in
+                  events[::2][:3]]
+        onsets[0] = onsets[0] + tmin - 0.499  # tmin < 0
+        onsets[1] = onsets[1] + tmax - 0.001
+        first_time = (raw.info['meas_date'][0] + raw.info['meas_date'][1] *
+                      0.000001 + raw.first_samp / sfreq)
+        for orig_time in [None, first_time]:
+            raw.annotations = Annotations(onsets, [0.5, 0.5, 0.5], 'BAD',
+                                          orig_time)
             epochs = Epochs(raw, events, event_id, tmin, tmax, picks=[0],
-                            reject=None, preload=preload,
-                            add_eeg_ref=False)
+                            reject=None, preload=preload)
             epochs.drop_bad()
-            assert_equal(len(events) - 1, len(epochs.events))
+            assert_equal(len(events) - 3, len(epochs.events))
             assert_equal(epochs.drop_log[0][0], 'BAD')
-            raw.annotations = None
+            assert_equal(epochs.drop_log[2][0], 'BAD')
+            assert_equal(epochs.drop_log[4][0], 'BAD')
+        raw.annotations = None
 
 
 def test_decim():
@@ -331,8 +334,7 @@ def test_decim():
     assert_raises(ValueError, epochs.decimate, 2, offset=2)
     for this_offset in range(decim):
         epochs = Epochs(raw, events, event_id,
-                        tmin=-this_offset / raw.info['sfreq'],
-                        tmax=tmax, preload=False, add_eeg_ref=False)
+                        tmin=-this_offset / raw.info['sfreq'], tmax=tmax)
         idx_offsets = np.arange(decim) + this_offset
         for offset, idx_offset in zip(np.arange(decim), idx_offsets):
             expected_times = epochs.times[idx_offset::decim]
@@ -346,14 +348,13 @@ def test_decim():
             assert_equal(ep_decim.info['sfreq'], sfreq_new)
 
     # More complex cases
-    epochs = Epochs(raw, events, event_id, tmin, tmax, preload=False,
-                    add_eeg_ref=False)
+    epochs = Epochs(raw, events, event_id, tmin, tmax)
     expected_data = epochs.get_data()[:, :, ::decim]
     expected_times = epochs.times[::decim]
     for preload in (True, False):
         # at init
         epochs = Epochs(raw, events, event_id, tmin, tmax, decim=decim,
-                        preload=preload, add_eeg_ref=False)
+                        preload=preload)
         assert_allclose(epochs.get_data(), expected_data)
         assert_allclose(epochs.get_data(), expected_data)
         assert_equal(epochs.info['sfreq'], sfreq_new)
@@ -361,13 +362,13 @@ def test_decim():
 
         # split between init and afterward
         epochs = Epochs(raw, events, event_id, tmin, tmax, decim=dec_1,
-                        preload=preload, add_eeg_ref=False).decimate(dec_2)
+                        preload=preload).decimate(dec_2)
         assert_allclose(epochs.get_data(), expected_data)
         assert_allclose(epochs.get_data(), expected_data)
         assert_equal(epochs.info['sfreq'], sfreq_new)
         assert_array_equal(epochs.times, expected_times)
         epochs = Epochs(raw, events, event_id, tmin, tmax, decim=dec_2,
-                        preload=preload, add_eeg_ref=False).decimate(dec_1)
+                        preload=preload).decimate(dec_1)
         assert_allclose(epochs.get_data(), expected_data)
         assert_allclose(epochs.get_data(), expected_data)
         assert_equal(epochs.info['sfreq'], sfreq_new)
@@ -375,7 +376,7 @@ def test_decim():
 
         # split between init and afterward, with preload in between
         epochs = Epochs(raw, events, event_id, tmin, tmax, decim=dec_1,
-                        preload=preload, add_eeg_ref=False)
+                        preload=preload)
         epochs.load_data()
         epochs = epochs.decimate(dec_2)
         assert_allclose(epochs.get_data(), expected_data)
@@ -383,7 +384,7 @@ def test_decim():
         assert_equal(epochs.info['sfreq'], sfreq_new)
         assert_array_equal(epochs.times, expected_times)
         epochs = Epochs(raw, events, event_id, tmin, tmax, decim=dec_2,
-                        preload=preload, add_eeg_ref=False)
+                        preload=preload)
         epochs.load_data()
         epochs = epochs.decimate(dec_1)
         assert_allclose(epochs.get_data(), expected_data)
@@ -393,15 +394,14 @@ def test_decim():
 
         # decimate afterward
         epochs = Epochs(raw, events, event_id, tmin, tmax,
-                        preload=preload, add_eeg_ref=False).decimate(decim)
+                        preload=preload).decimate(decim)
         assert_allclose(epochs.get_data(), expected_data)
         assert_allclose(epochs.get_data(), expected_data)
         assert_equal(epochs.info['sfreq'], sfreq_new)
         assert_array_equal(epochs.times, expected_times)
 
         # decimate afterward, with preload in between
-        epochs = Epochs(raw, events, event_id, tmin, tmax,
-                        preload=preload, add_eeg_ref=False)
+        epochs = Epochs(raw, events, event_id, tmin, tmax, preload=preload)
         epochs.load_data()
         epochs.decimate(decim)
         assert_allclose(epochs.get_data(), expected_data)
@@ -413,51 +413,73 @@ def test_decim():
 def test_base_epochs():
     """Test base epochs class."""
     raw = _get_data()[0]
-    epochs = _BaseEpochs(raw.info, None, np.ones((1, 3), int),
-                         event_id, tmin, tmax)
+    epochs = BaseEpochs(raw.info, None, np.ones((1, 3), int),
+                        event_id, tmin, tmax)
     assert_raises(NotImplementedError, epochs.get_data)
     # events with non integers
-    assert_raises(ValueError, _BaseEpochs, raw.info, None,
+    assert_raises(ValueError, BaseEpochs, raw.info, None,
                   np.ones((1, 3), float), event_id, tmin, tmax)
-    assert_raises(ValueError, _BaseEpochs, raw.info, None,
+    assert_raises(ValueError, BaseEpochs, raw.info, None,
                   np.ones((1, 3, 2), int), event_id, tmin, tmax)
 
 
 @requires_version('scipy', '0.14')
 def test_savgol_filter():
     """Test savgol filtering."""
-    h_freq = 10.
+    h_freq = 20.
     raw, events = _get_data()[:2]
-    epochs = Epochs(raw, events, event_id, tmin, tmax, add_eeg_ref=False)
+    epochs = Epochs(raw, events, event_id, tmin, tmax)
     assert_raises(RuntimeError, epochs.savgol_filter, 10.)
-    epochs = Epochs(raw, events, event_id, tmin, tmax, preload=True,
-                    add_eeg_ref=False)
-    freqs = fftpack.fftfreq(len(epochs.times), 1. / epochs.info['sfreq'])
-    data = np.abs(fftpack.fft(epochs.get_data()))
-    match_mask = np.logical_and(freqs >= 0, freqs <= h_freq / 2.)
-    mismatch_mask = np.logical_and(freqs >= h_freq * 2, freqs < 50.)
+    epochs = Epochs(raw, events, event_id, tmin, tmax, preload=True)
+    epochs.pick_types(meg='grad')
+    freqs = np.fft.rfftfreq(len(epochs.times), 1. / epochs.info['sfreq'])
+    data = np.abs(np.fft.rfft(epochs.get_data()))
+    pass_mask = (freqs <= h_freq / 2. - 5.)
+    stop_mask = (freqs >= h_freq * 2 + 5.)
     epochs.savgol_filter(h_freq)
-    data_filt = np.abs(fftpack.fft(epochs.get_data()))
+    data_filt = np.abs(np.fft.rfft(epochs.get_data()))
     # decent in pass-band
-    assert_allclose(np.mean(data[:, :, match_mask], 0),
-                    np.mean(data_filt[:, :, match_mask], 0),
-                    rtol=1e-4, atol=1e-2)
+    assert_allclose(np.mean(data[:, :, pass_mask], 0),
+                    np.mean(data_filt[:, :, pass_mask], 0),
+                    rtol=1e-2, atol=1e-18)
     # suppression in stop-band
-    assert_true(np.mean(data[:, :, mismatch_mask]) >
-                np.mean(data_filt[:, :, mismatch_mask]) * 5)
+    assert_true(np.mean(data[:, :, stop_mask]) >
+                np.mean(data_filt[:, :, stop_mask]) * 5)
+
+
+def test_filter():
+    """Test filtering."""
+    h_freq = 40.
+    raw, events = _get_data()[:2]
+    epochs = Epochs(raw, events, event_id, tmin, tmax)
+    assert round(epochs.info['lowpass']) == 172
+    assert_raises(RuntimeError, epochs.savgol_filter, 10.)
+    epochs = Epochs(raw, events, event_id, tmin, tmax, preload=True)
+    epochs.pick_types(meg='grad')
+    freqs = np.fft.rfftfreq(len(epochs.times), 1. / epochs.info['sfreq'])
+    data = np.abs(np.fft.rfft(epochs.get_data()))
+    pass_mask = (freqs <= h_freq / 2. - 5.)
+    stop_mask = (freqs >= h_freq * 2 + 5.)
+    epochs.filter(None, h_freq, fir_design='firwin')
+    assert epochs.info['lowpass'] == h_freq
+    data_filt = np.abs(np.fft.rfft(epochs.get_data()))
+    # decent in pass-band
+    assert_allclose(np.mean(data_filt[:, :, pass_mask], 0),
+                    np.mean(data[:, :, pass_mask], 0),
+                    rtol=5e-2, atol=1e-16)
+    # suppression in stop-band
+    assert_true(np.mean(data[:, :, stop_mask]) >
+                np.mean(data_filt[:, :, stop_mask]) * 10)
 
 
 def test_epochs_hash():
     """Test epoch hashing."""
     raw, events = _get_data()[:2]
-    epochs = Epochs(raw, events, event_id, tmin, tmax,
-                    add_eeg_ref=False)
+    epochs = Epochs(raw, events, event_id, tmin, tmax)
     assert_raises(RuntimeError, epochs.__hash__)
-    epochs = Epochs(raw, events, event_id, tmin, tmax, preload=True,
-                    add_eeg_ref=False)
+    epochs = Epochs(raw, events, event_id, tmin, tmax, preload=True)
     assert_equal(hash(epochs), hash(epochs))
-    epochs_2 = Epochs(raw, events, event_id, tmin, tmax, preload=True,
-                      add_eeg_ref=False)
+    epochs_2 = Epochs(raw, events, event_id, tmin, tmax, preload=True)
     assert_equal(hash(epochs), hash(epochs_2))
     # do NOT use assert_equal here, failing output is terrible
     assert_true(pickle.dumps(epochs) == pickle.dumps(epochs_2))
@@ -467,7 +489,7 @@ def test_epochs_hash():
 
 
 def test_event_ordering():
-    """Test event order"""
+    """Test event order."""
     raw, events = _get_data()[:2]
     events2 = events.copy()
     rng.shuffle(events2)
@@ -475,28 +497,28 @@ def test_event_ordering():
         with warnings.catch_warnings(record=True) as w:
             warnings.simplefilter('always')
             Epochs(raw, eve, event_id, tmin, tmax,
-                   baseline=(None, 0), reject=reject, flat=flat,
-                   add_eeg_ref=False)
+                   reject=reject, flat=flat)
             assert_equal(len(w), ii)
             if ii > 0:
                 assert_true('chronologically' in '%s' % w[-1].message)
+    # Duplicate events should be an error...
+    events2 = events[[0, 0]]
+    events2[:, 2] = [1, 2]
+    assert_raises(RuntimeError, Epochs, raw, events2, event_id=None)
+    # But only if duplicates are actually used by event_id
+    assert_equal(len(Epochs(raw, events2, event_id=dict(a=1), preload=True)),
+                 1)
 
 
 def test_epochs_bad_baseline():
     """Test Epochs initialization with bad baseline parameters."""
     raw, events = _get_data()[:2]
-    assert_raises(ValueError, Epochs, raw, events, None, -0.1, 0.3, (-0.2, 0),
-                  add_eeg_ref=False)
-    assert_raises(ValueError, Epochs, raw, events, None, -0.1, 0.3, (0, 0.4),
-                  add_eeg_ref=False)
-    assert_raises(ValueError, Epochs, raw, events, None, -0.1, 0.3, (0.1, 0),
-                  add_eeg_ref=False)
-    assert_raises(ValueError, Epochs, raw, events, None, 0.1, 0.3, (None, 0),
-                  add_eeg_ref=False)
-    assert_raises(ValueError, Epochs, raw, events, None, -0.3, -0.1, (0, None),
-                  add_eeg_ref=False)
-    epochs = Epochs(raw, events, None, 0.1, 0.3, baseline=None,
-                    add_eeg_ref=False)
+    assert_raises(ValueError, Epochs, raw, events, None, -0.1, 0.3, (-0.2, 0))
+    assert_raises(ValueError, Epochs, raw, events, None, -0.1, 0.3, (0, 0.4))
+    assert_raises(ValueError, Epochs, raw, events, None, -0.1, 0.3, (0.1, 0))
+    assert_raises(ValueError, Epochs, raw, events, None, 0.1, 0.3, (None, 0))
+    assert_raises(ValueError, Epochs, raw, events, None, -0.3, -0.1, (0, None))
+    epochs = Epochs(raw, events, None, 0.1, 0.3, baseline=None)
     assert_raises(RuntimeError, epochs.apply_baseline, (0.1, 0.2))
     epochs.load_data()
     assert_raises(ValueError, epochs.apply_baseline, (None, 0))
@@ -514,10 +536,10 @@ def test_epoch_combine_ids():
     raw, events, picks = _get_data()
     epochs = Epochs(raw, events, {'a': 1, 'b': 2, 'c': 3,
                                   'd': 4, 'e': 5, 'f': 32},
-                    tmin, tmax, picks=picks, preload=False, add_eeg_ref=False)
+                    tmin, tmax, picks=picks, preload=False)
     events_new = merge_events(events, [1, 2], 12)
     epochs_new = combine_event_ids(epochs, ['a', 'b'], {'ab': 12})
-    assert_equal(epochs_new['ab'].name, 'ab')
+    assert_equal(epochs_new['ab']._name, 'ab')
     assert_array_equal(events_new, epochs_new.events)
     # should probably add test + functionality for non-replacement XXX
 
@@ -527,7 +549,7 @@ def test_epoch_multi_ids():
     raw, events, picks = _get_data()
     epochs = Epochs(raw, events, {'a/b/a': 1, 'a/b/b': 2, 'a/c': 3,
                                   'b/d': 4, 'a_b': 5},
-                    tmin, tmax, picks=picks, preload=False, add_eeg_ref=False)
+                    tmin, tmax, picks=picks, preload=False)
     epochs_regular = epochs['a/b']
     epochs_reverse = epochs['b/a']
     epochs_multi = epochs[['a/b/a', 'a/b/b']]
@@ -542,14 +564,12 @@ def test_read_epochs_bad_events():
     raw, events, picks = _get_data()
     # Event at the beginning
     epochs = Epochs(raw, np.array([[raw.first_samp, 0, event_id]]),
-                    event_id, tmin, tmax, picks=picks, baseline=(None, 0),
-                    add_eeg_ref=False)
+                    event_id, tmin, tmax, picks=picks)
     with warnings.catch_warnings(record=True):
         evoked = epochs.average()
 
     epochs = Epochs(raw, np.array([[raw.first_samp, 0, event_id]]),
-                    event_id, tmin, tmax, picks=picks, baseline=(None, 0),
-                    add_eeg_ref=False)
+                    event_id, tmin, tmax, picks=picks)
     assert_true(repr(epochs))  # test repr
     epochs.drop_bad()
     assert_true(repr(epochs))
@@ -558,8 +578,7 @@ def test_read_epochs_bad_events():
 
     # Event at the end
     epochs = Epochs(raw, np.array([[raw.last_samp, 0, event_id]]),
-                    event_id, tmin, tmax, picks=picks, baseline=(None, 0),
-                    add_eeg_ref=False)
+                    event_id, tmin, tmax, picks=picks)
 
     with warnings.catch_warnings(record=True):
         evoked = epochs.average()
@@ -567,7 +586,7 @@ def test_read_epochs_bad_events():
     warnings.resetwarnings()
 
 
- at slow_test
+ at pytest.mark.slowtest
 def test_read_write_epochs():
     """Test epochs from raw files with IO as fif file."""
     raw, events, picks = _get_data(preload=True)
@@ -576,21 +595,20 @@ def test_read_write_epochs():
     temp_fname_no_bl = op.join(tempdir, 'test_no_bl-epo.fif')
     baseline = (None, 0)
     epochs = Epochs(raw, events, event_id, tmin, tmax, picks=picks,
-                    baseline=baseline, preload=True, add_eeg_ref=False)
+                    baseline=baseline, preload=True)
     epochs_orig = epochs.copy()
     epochs_no_bl = Epochs(raw, events, event_id, tmin, tmax, picks=picks,
-                          baseline=None, preload=True, add_eeg_ref=False)
+                          baseline=None, preload=True)
     assert_true(epochs_no_bl.baseline is None)
     evoked = epochs.average()
     data = epochs.get_data()
 
     # Bad tmin/tmax parameters
     assert_raises(ValueError, Epochs, raw, events, event_id, tmax, tmin,
-                  baseline=None, add_eeg_ref=False)
+                  baseline=None)
 
     epochs_no_id = Epochs(raw, pick_events(events, include=event_id),
-                          None, tmin, tmax, picks=picks,
-                          baseline=(None, 0), add_eeg_ref=False)
+                          None, tmin, tmax, picks=picks)
     assert_array_equal(data, epochs_no_id.get_data())
 
     eog_picks = pick_types(raw.info, meg=False, eeg=False, stim=False,
@@ -607,7 +625,7 @@ def test_read_write_epochs():
         # decim with lowpass
         warnings.simplefilter('always')
         epochs_dec = Epochs(raw, events, event_id, tmin, tmax, picks=picks,
-                            baseline=(None, 0), decim=2, add_eeg_ref=False)
+                            decim=2)
         assert_equal(len(w), 1)
 
         # decim without lowpass
@@ -641,8 +659,7 @@ def test_read_write_epochs():
     event_ids = dict(a=1, b=2)
     for proj in (True, 'delayed', False):
         epochs = Epochs(raw, events, event_ids, tmin, tmax, picks=picks,
-                        baseline=(None, 0), proj=proj, reject=reject,
-                        add_eeg_ref=False)
+                        proj=proj, reject=reject)
         assert_equal(epochs.proj, proj if proj != 'delayed' else False)
         data1 = epochs.get_data()
         epochs2 = epochs.copy().apply_proj()
@@ -677,7 +694,7 @@ def test_read_write_epochs():
         epochs_no_bl_read = read_epochs(temp_fname_no_bl)
         assert_raises(ValueError, epochs.apply_baseline, baseline=[1, 2, 3])
         epochs_with_bl = epochs_no_bl_read.copy().apply_baseline(baseline)
-        assert_true(isinstance(epochs_with_bl, _BaseEpochs))
+        assert_true(isinstance(epochs_with_bl, BaseEpochs))
         assert_true(epochs_with_bl.baseline == baseline)
         assert_true(epochs_no_bl_read.baseline != baseline)
         assert_true(str(epochs_read).startswith('<Epochs'))
@@ -707,11 +724,11 @@ def test_read_write_epochs():
         epochs_read2 = read_epochs(op.join(tempdir, 'foo-epo.fif'),
                                    preload=preload)
         assert_equal(epochs_read2.event_id, epochs.event_id)
+        assert_equal(epochs_read2['a:a'].average().comment, 'a:a')
 
         # add reject here so some of the epochs get dropped
         epochs = Epochs(raw, events, event_id, tmin, tmax, picks=picks,
-                        baseline=(None, 0), reject=reject,
-                        add_eeg_ref=False)
+                        reject=reject)
         epochs.save(temp_fname)
         # ensure bad events are not saved
         epochs_read3 = read_epochs(temp_fname, preload=preload)
@@ -723,7 +740,7 @@ def test_read_write_epochs():
         epochs_read4 = epochs_read3.copy()
         assert_array_almost_equal(epochs_read4.get_data(), data)
         # test equalizing loaded one (drop_log property)
-        epochs_read4.equalize_event_counts(epochs.event_id, copy=False)
+        epochs_read4.equalize_event_counts(epochs.event_id)
 
         epochs.drop([1, 2], reason='can we recover orig ID?')
         epochs.save(temp_fname)
@@ -746,8 +763,7 @@ def test_read_write_epochs():
 
         # test loading epochs with missing events
         epochs = Epochs(raw, events, dict(foo=1, bar=999), tmin, tmax,
-                        picks=picks, on_missing='ignore',
-                        add_eeg_ref=False)
+                        picks=picks, on_missing='ignore')
         epochs.save(temp_fname)
         epochs_read = read_epochs(temp_fname, preload=preload)
         assert_allclose(epochs.get_data(), epochs_read.get_data(), **tols)
@@ -781,16 +797,15 @@ def test_epochs_proj():
     this_picks = pick_types(raw.info, meg=True, eeg=False, stim=True,
                             eog=True, exclude=exclude)
     epochs = Epochs(raw, events[:4], event_id, tmin, tmax, picks=this_picks,
-                    baseline=(None, 0), proj=True, add_eeg_ref=False)
+                    proj=True)
     assert_true(all(p['active'] is True for p in epochs.info['projs']))
     evoked = epochs.average()
     assert_true(all(p['active'] is True for p in evoked.info['projs']))
     data = epochs.get_data()
 
-    raw_proj = read_raw_fif(raw_fname, add_eeg_ref=False).apply_proj()
+    raw_proj = read_raw_fif(raw_fname).apply_proj()
     epochs_no_proj = Epochs(raw_proj, events[:4], event_id, tmin, tmax,
-                            picks=this_picks, baseline=(None, 0), proj=False,
-                            add_eeg_ref=False)
+                            picks=this_picks, proj=False)
 
     data_no_proj = epochs_no_proj.get_data()
     assert_true(all(p['active'] is True for p in epochs_no_proj.info['projs']))
@@ -804,24 +819,24 @@ def test_epochs_proj():
     this_picks = pick_types(raw.info, meg=True, eeg=True, stim=True,
                             eog=True, exclude=exclude)
     epochs = Epochs(raw, events[:4], event_id, tmin, tmax, picks=this_picks,
-                    baseline=(None, 0), proj=True, add_eeg_ref=False)
-    epochs.set_eeg_reference().apply_proj()
+                    proj=True)
+    epochs.set_eeg_reference(projection=True).apply_proj()
     assert_true(_has_eeg_average_ref_proj(epochs.info['projs']))
     epochs = Epochs(raw, events[:4], event_id, tmin, tmax, picks=this_picks,
-                    baseline=(None, 0), proj=True, add_eeg_ref=False)
+                    proj=True)
     assert_true(not _has_eeg_average_ref_proj(epochs.info['projs']))
 
     # make sure we don't add avg ref when a custom ref has been applied
     raw.info['custom_ref_applied'] = True
     epochs = Epochs(raw, events[:4], event_id, tmin, tmax, picks=this_picks,
-                    baseline=(None, 0), proj=True, add_eeg_ref=False)
+                    proj=True)
     assert_true(not _has_eeg_average_ref_proj(epochs.info['projs']))
 
     # From GH#2200:
     # This has no problem
     proj = raw.info['projs']
     epochs = Epochs(raw, events[:4], event_id, tmin, tmax, picks=this_picks,
-                    baseline=(None, 0), proj=False, add_eeg_ref=False)
+                    proj=False)
     epochs.info['projs'] = []
     data = epochs.copy().add_proj(proj).apply_proj().get_data()
     # save and reload data
@@ -834,23 +849,23 @@ def test_epochs_proj():
     assert_allclose(data, data_2, atol=1e-15, rtol=1e-3)
 
     # adding EEG ref (GH #2727)
-    raw = read_raw_fif(raw_fname, add_eeg_ref=False)
+    raw = read_raw_fif(raw_fname)
     raw.add_proj([], remove_existing=True)
     raw.info['bads'] = ['MEG 2443', 'EEG 053']
     picks = pick_types(raw.info, meg=False, eeg=True, stim=True, eog=False,
                        exclude='bads')
     epochs = Epochs(raw, events, event_id, tmin, tmax, proj=True, picks=picks,
-                    baseline=(None, 0), preload=True, add_eeg_ref=False)
+                    preload=True)
     epochs.pick_channels(['EEG 001', 'EEG 002'])
     assert_equal(len(epochs), 7)  # sufficient for testing
     temp_fname = op.join(tempdir, 'test-epo.fif')
     epochs.save(temp_fname)
     for preload in (True, False):
         epochs = read_epochs(temp_fname, proj=False, preload=preload)
-        epochs.set_eeg_reference().apply_proj()
+        epochs.set_eeg_reference(projection=True).apply_proj()
         assert_allclose(epochs.get_data().mean(axis=1), 0, atol=1e-15)
         epochs = read_epochs(temp_fname, proj=False, preload=preload)
-        epochs.set_eeg_reference()
+        epochs.set_eeg_reference(projection=True)
         assert_raises(AssertionError, assert_allclose,
                       epochs.get_data().mean(axis=1), 0., atol=1e-15)
         epochs.apply_proj()
@@ -860,14 +875,11 @@ def test_epochs_proj():
 def test_evoked_arithmetic():
     """Test arithmetic of evoked data."""
     raw, events, picks = _get_data()
-    epochs1 = Epochs(raw, events[:4], event_id, tmin, tmax, picks=picks,
-                     baseline=(None, 0), add_eeg_ref=False)
+    epochs1 = Epochs(raw, events[:4], event_id, tmin, tmax, picks=picks)
     evoked1 = epochs1.average()
-    epochs2 = Epochs(raw, events[4:8], event_id, tmin, tmax, picks=picks,
-                     baseline=(None, 0), add_eeg_ref=False)
+    epochs2 = Epochs(raw, events[4:8], event_id, tmin, tmax, picks=picks)
     evoked2 = epochs2.average()
-    epochs = Epochs(raw, events[:8], event_id, tmin, tmax, picks=picks,
-                    baseline=(None, 0), add_eeg_ref=False)
+    epochs = Epochs(raw, events[:8], event_id, tmin, tmax, picks=picks)
     evoked = epochs.average()
     evoked_sum = combine_evoked([evoked1, evoked2], weights='nave')
     assert_array_equal(evoked.data, evoked_sum.data)
@@ -885,8 +897,7 @@ def test_evoked_io_from_epochs():
     with warnings.catch_warnings(record=True) as w:
         warnings.simplefilter('always')
         epochs = Epochs(raw, events[:4], event_id, tmin + 0.011, tmax,
-                        picks=picks, baseline=(None, 0), decim=5,
-                        add_eeg_ref=False)
+                        picks=picks, decim=5)
     assert_true(len(w) == 1)
     evoked = epochs.average()
     evoked.info['proj_name'] = ''  # Test that empty string shortcuts to None.
@@ -901,8 +912,7 @@ def test_evoked_io_from_epochs():
     with warnings.catch_warnings(record=True) as w:
         warnings.simplefilter('always')
         epochs = Epochs(raw, events[:4], event_id, 0.1, tmax,
-                        picks=picks, baseline=(0.1, 0.2), decim=5,
-                        add_eeg_ref=False)
+                        picks=picks, baseline=(0.1, 0.2), decim=5)
     evoked = epochs.average()
     evoked.save(op.join(tempdir, 'evoked-ave.fif'))
     evoked2 = read_evokeds(op.join(tempdir, 'evoked-ave.fif'))[0]
@@ -913,8 +923,7 @@ def test_evoked_io_from_epochs():
     with warnings.catch_warnings(record=True) as w:
         warnings.simplefilter('always')
         epochs = Epochs(raw, events[:4], event_id, -0.2, tmax,
-                        picks=picks, baseline=(0.1, 0.2), decim=5,
-                        add_eeg_ref=False)
+                        picks=picks, baseline=(0.1, 0.2), decim=5)
     evoked = epochs.average()
     evoked.crop(0.099, None)
     assert_allclose(evoked.data, evoked2.data, rtol=1e-4, atol=1e-20)
@@ -925,13 +934,12 @@ def test_evoked_standard_error():
     """Test calculation and read/write of standard error."""
     raw, events, picks = _get_data()
     tempdir = _TempDir()
-    epochs = Epochs(raw, events[:4], event_id, tmin, tmax, picks=picks,
-                    baseline=(None, 0), add_eeg_ref=False)
+    epochs = Epochs(raw, events[:4], event_id, tmin, tmax, picks=picks)
     evoked = [epochs.average(), epochs.standard_error()]
     write_evokeds(op.join(tempdir, 'evoked-ave.fif'), evoked)
     evoked2 = read_evokeds(op.join(tempdir, 'evoked-ave.fif'), [0, 1])
-    evoked3 = [read_evokeds(op.join(tempdir, 'evoked-ave.fif'), 'Unknown'),
-               read_evokeds(op.join(tempdir, 'evoked-ave.fif'), 'Unknown',
+    evoked3 = [read_evokeds(op.join(tempdir, 'evoked-ave.fif'), '1'),
+               read_evokeds(op.join(tempdir, 'evoked-ave.fif'), '1',
                             kind='standard_error')]
     for evoked_new in [evoked2, evoked3]:
         assert_true(evoked_new[0]._aspect_kind ==
@@ -954,8 +962,8 @@ def test_reject_epochs():
     """Test of epochs rejection."""
     raw, events, picks = _get_data()
     events1 = events[events[:, 2] == event_id]
-    epochs = Epochs(raw, events1, event_id, tmin, tmax, baseline=(None, 0),
-                    reject=reject, flat=flat, add_eeg_ref=False)
+    epochs = Epochs(raw, events1, event_id, tmin, tmax,
+                    reject=reject, flat=flat)
     assert_raises(RuntimeError, len, epochs)
     n_events = len(epochs.events)
     data = epochs.get_data()
@@ -972,8 +980,8 @@ def test_reject_epochs():
     raw_2 = raw.copy()
     raw_2.info['bads'] = ['MEG 2443']
     reject_crazy = dict(grad=1000e-15, mag=4e-15, eeg=80e-9, eog=150e-9)
-    epochs = Epochs(raw_2, events1, event_id, tmin, tmax, baseline=(None, 0),
-                    reject=reject_crazy, flat=flat, add_eeg_ref=False)
+    epochs = Epochs(raw_2, events1, event_id, tmin, tmax,
+                    reject=reject_crazy, flat=flat)
     epochs.drop_bad()
 
     assert_true(all('MEG 2442' in e for e in epochs.drop_log))
@@ -981,15 +989,14 @@ def test_reject_epochs():
 
     # Invalid reject_tmin/reject_tmax/detrend
     assert_raises(ValueError, Epochs, raw, events1, event_id, tmin, tmax,
-                  reject_tmin=1., reject_tmax=0, add_eeg_ref=False)
+                  reject_tmin=1., reject_tmax=0)
     assert_raises(ValueError, Epochs, raw, events1, event_id, tmin, tmax,
-                  reject_tmin=tmin - 1, reject_tmax=1., add_eeg_ref=False)
+                  reject_tmin=tmin - 1, reject_tmax=1.)
     assert_raises(ValueError, Epochs, raw, events1, event_id, tmin, tmax,
-                  reject_tmin=0., reject_tmax=tmax + 1, add_eeg_ref=False)
+                  reject_tmin=0., reject_tmax=tmax + 1)
 
     epochs = Epochs(raw, events1, event_id, tmin, tmax, picks=picks,
-                    baseline=(None, 0), reject=reject, flat=flat,
-                    reject_tmin=0., reject_tmax=.1, add_eeg_ref=False)
+                    reject=reject, flat=flat, reject_tmin=0., reject_tmax=.1)
     data = epochs.get_data()
     n_clean_epochs = len(data)
     assert_true(n_clean_epochs == 7)
@@ -998,8 +1005,7 @@ def test_reject_epochs():
     assert_true(epochs.times[epochs._reject_time][-1] <= 0.1)
 
     # Invalid data for _is_good_epoch function
-    epochs = Epochs(raw, events1, event_id, tmin, tmax, reject=None, flat=None,
-                    add_eeg_ref=False)
+    epochs = Epochs(raw, events1, event_id, tmin, tmax)
     assert_equal(epochs._is_good_epoch(None), (False, ['NO_DATA']))
     assert_equal(epochs._is_good_epoch(np.zeros((1, 1))),
                  (False, ['TOO_SHORT']))
@@ -1011,13 +1017,12 @@ def test_preload_epochs():
     """Test preload of epochs."""
     raw, events, picks = _get_data()
     epochs_preload = Epochs(raw, events[:16], event_id, tmin, tmax,
-                            picks=picks, baseline=(None, 0), preload=True,
-                            reject=reject, flat=flat, add_eeg_ref=False)
+                            picks=picks, preload=True,
+                            reject=reject, flat=flat)
     data_preload = epochs_preload.get_data()
 
     epochs = Epochs(raw, events[:16], event_id, tmin, tmax, picks=picks,
-                    baseline=(None, 0), preload=False,
-                    reject=reject, flat=flat, add_eeg_ref=False)
+                    preload=False, reject=reject, flat=flat)
     data = epochs.get_data()
     assert_array_equal(data_preload, data)
     assert_array_almost_equal(epochs_preload.average().data,
@@ -1028,8 +1033,7 @@ def test_indexing_slicing():
     """Test of indexing and slicing operations."""
     raw, events, picks = _get_data()
     epochs = Epochs(raw, events[:20], event_id, tmin, tmax, picks=picks,
-                    baseline=(None, 0), preload=False,
-                    reject=reject, flat=flat, add_eeg_ref=False)
+                    reject=reject, flat=flat)
 
     data_normal = epochs.get_data()
 
@@ -1042,9 +1046,8 @@ def test_indexing_slicing():
     assert((end_index - start_index) > 0)
 
     for preload in [True, False]:
-        epochs2 = Epochs(raw, events[:20], event_id, tmin, tmax,
-                         picks=picks, baseline=(None, 0), preload=preload,
-                         reject=reject, flat=flat, add_eeg_ref=False)
+        epochs2 = Epochs(raw, events[:20], event_id, tmin, tmax, picks=picks,
+                         preload=preload, reject=reject, flat=flat)
 
         if not preload:
             epochs2.drop_bad()
@@ -1086,9 +1089,8 @@ def test_comparision_with_c():
     raw, events = _get_data()[:2]
     c_evoked = read_evokeds(evoked_nf_name, condition=0)
     epochs = Epochs(raw, events, event_id, tmin, tmax, baseline=None,
-                    preload=True, reject=None, flat=None, add_eeg_ref=False,
-                    proj=False)
-    evoked = epochs.set_eeg_reference().apply_proj().average()
+                    preload=True, proj=False)
+    evoked = epochs.set_eeg_reference(projection=True).apply_proj().average()
     sel = pick_channels(c_evoked.ch_names, evoked.ch_names)
     evoked_data = evoked.data
     c_evoked_data = c_evoked.data[sel]
@@ -1102,14 +1104,12 @@ def test_crop():
     """Test of crop of epochs."""
     raw, events, picks = _get_data()
     epochs = Epochs(raw, events[:5], event_id, tmin, tmax, picks=picks,
-                    baseline=(None, 0), preload=False,
-                    reject=reject, flat=flat, add_eeg_ref=False)
+                    preload=False, reject=reject, flat=flat)
     assert_raises(RuntimeError, epochs.crop, None, 0.2)  # not preloaded
     data_normal = epochs.get_data()
 
     epochs2 = Epochs(raw, events[:5], event_id, tmin, tmax,
-                     picks=picks, baseline=(None, 0), preload=True,
-                     reject=reject, flat=flat, add_eeg_ref=False)
+                     picks=picks, preload=True, reject=reject, flat=flat)
     with warnings.catch_warnings(record=True) as w:
         epochs2.crop(-20, 200)
     assert_true(len(w) == 2)
@@ -1142,8 +1142,7 @@ def test_crop():
     assert_allclose(last_time, epochs.times[-1])
 
     epochs = Epochs(raw, events[:5], event_id, -1, 1,
-                    picks=picks, baseline=(None, 0), preload=True,
-                    reject=reject, flat=flat, add_eeg_ref=False)
+                    picks=picks, preload=True, reject=reject, flat=flat)
     # We include nearest sample, so actually a bit beyound our bounds here
     assert_allclose(epochs.tmin, -1.0006410259015925, rtol=1e-12)
     assert_allclose(epochs.tmax, 1.0006410259015925, rtol=1e-12)
@@ -1156,17 +1155,14 @@ def test_crop():
 
 
 def test_resample():
-    """Test of resample of epochs
-    """
+    """Test of resample of epochs."""
     raw, events, picks = _get_data()
     epochs = Epochs(raw, events[:10], event_id, tmin, tmax, picks=picks,
-                    baseline=(None, 0), preload=False,
-                    reject=reject, flat=flat, add_eeg_ref=False)
+                    preload=False, reject=reject, flat=flat)
     assert_raises(RuntimeError, epochs.resample, 100)
 
     epochs_o = Epochs(raw, events[:10], event_id, tmin, tmax, picks=picks,
-                      baseline=(None, 0), preload=True,
-                      reject=reject, flat=flat, add_eeg_ref=False)
+                      preload=True, reject=reject, flat=flat)
     epochs = epochs_o.copy()
 
     data_normal = cp.deepcopy(epochs.get_data())
@@ -1239,9 +1235,9 @@ def test_detrend():
 
     # test first-order
     epochs_1 = Epochs(raw, events[:4], event_id, tmin, tmax, picks=picks,
-                      baseline=None, detrend=1, add_eeg_ref=False)
+                      baseline=None, detrend=1)
     epochs_2 = Epochs(raw, events[:4], event_id, tmin, tmax, picks=picks,
-                      baseline=None, detrend=None, add_eeg_ref=False)
+                      baseline=None, detrend=None)
     data_picks = pick_types(epochs_1.info, meg=True, eeg=True,
                             exclude='bads')
     evoked_1 = epochs_1.average()
@@ -1254,11 +1250,9 @@ def test_detrend():
     # test zeroth-order case
     for preload in [True, False]:
         epochs_1 = Epochs(raw, events[:4], event_id, tmin, tmax, picks=picks,
-                          baseline=(None, None), preload=preload,
-                          add_eeg_ref=False)
+                          baseline=(None, None), preload=preload)
         epochs_2 = Epochs(raw, events[:4], event_id, tmin, tmax, picks=picks,
-                          baseline=None, preload=preload, detrend=0,
-                          add_eeg_ref=False)
+                          baseline=None, preload=preload, detrend=0)
         a = epochs_1.get_data()
         b = epochs_2.get_data()
         # All data channels should be almost equal
@@ -1269,15 +1263,14 @@ def test_detrend():
 
     for value in ['foo', 2, False, True]:
         assert_raises(ValueError, Epochs, raw, events[:4], event_id,
-                      tmin, tmax, detrend=value, add_eeg_ref=False)
+                      tmin, tmax, detrend=value)
 
 
 def test_bootstrap():
     """Test of bootstrapping of epochs."""
     raw, events, picks = _get_data()
     epochs = Epochs(raw, events[:5], event_id, tmin, tmax, picks=picks,
-                    baseline=(None, 0), preload=True,
-                    reject=reject, flat=flat, add_eeg_ref=False)
+                    preload=True, reject=reject, flat=flat)
     epochs2 = bootstrap(epochs, random_state=0)
     assert_true(len(epochs2.events) == len(epochs.events))
     assert_true(epochs._data.shape == epochs2._data.shape)
@@ -1287,14 +1280,12 @@ def test_epochs_copy():
     """Test copy epochs."""
     raw, events, picks = _get_data()
     epochs = Epochs(raw, events[:5], event_id, tmin, tmax, picks=picks,
-                    baseline=(None, 0), preload=True,
-                    reject=reject, flat=flat, add_eeg_ref=False)
+                    preload=True, reject=reject, flat=flat)
     copied = epochs.copy()
     assert_array_equal(epochs._data, copied._data)
 
     epochs = Epochs(raw, events[:5], event_id, tmin, tmax, picks=picks,
-                    baseline=(None, 0), preload=False,
-                    reject=reject, flat=flat, add_eeg_ref=False)
+                    preload=False, reject=reject, flat=flat)
     copied = epochs.copy()
     data = epochs.get_data()
     copied_data = copied.get_data()
@@ -1304,8 +1295,7 @@ def test_epochs_copy():
 def test_iter_evoked():
     """Test the iterator for epochs -> evoked."""
     raw, events, picks = _get_data()
-    epochs = Epochs(raw, events[:5], event_id, tmin, tmax, picks=picks,
-                    baseline=(None, 0), add_eeg_ref=False)
+    epochs = Epochs(raw, events[:5], event_id, tmin, tmax, picks=picks)
 
     for ii, ev in enumerate(epochs.iter_evoked()):
         x = ev.data
@@ -1316,8 +1306,7 @@ def test_iter_evoked():
 def test_subtract_evoked():
     """Test subtraction of Evoked from Epochs."""
     raw, events, picks = _get_data()
-    epochs = Epochs(raw, events[:10], event_id, tmin, tmax, picks=picks,
-                    baseline=(None, 0), add_eeg_ref=False)
+    epochs = Epochs(raw, events[:10], event_id, tmin, tmax, picks=picks)
 
     # make sure subraction fails if data channels are missing
     assert_raises(ValueError, epochs.subtract_evoked,
@@ -1331,8 +1320,7 @@ def test_subtract_evoked():
 
     # use preloading and SSP from the start
     epochs2 = Epochs(raw, events[:10], event_id, tmin, tmax, picks=picks,
-                     baseline=(None, 0), preload=True, proj=True,
-                     add_eeg_ref=False)
+                     preload=True)
 
     evoked = epochs2.average()
     epochs2.subtract_evoked(evoked)
@@ -1350,15 +1338,13 @@ def test_epoch_eq():
     """Test epoch count equalization and condition combining."""
     raw, events, picks = _get_data()
     # equalizing epochs objects
-    epochs_1 = Epochs(raw, events, event_id, tmin, tmax, picks=picks,
-                      add_eeg_ref=False)
-    epochs_2 = Epochs(raw, events, event_id_2, tmin, tmax, picks=picks,
-                      add_eeg_ref=False)
+    epochs_1 = Epochs(raw, events, event_id, tmin, tmax, picks=picks)
+    epochs_2 = Epochs(raw, events, event_id_2, tmin, tmax, picks=picks)
     epochs_1.drop_bad()  # make sure drops are logged
     assert_true(len([l for l in epochs_1.drop_log if not l]) ==
                 len(epochs_1.events))
     drop_log1 = epochs_1.drop_log = [[] for _ in range(len(epochs_1.events))]
-    drop_log2 = [[] if l == ['EQUALIZED_COUNT'] else l for l in
+    drop_log2 = [[] if log == ['EQUALIZED_COUNT'] else log for log in
                  epochs_1.drop_log]
     assert_true(drop_log1 == drop_log2)
     assert_true(len([l for l in epochs_1.drop_log if not l]) ==
@@ -1366,25 +1352,23 @@ def test_epoch_eq():
     assert_true(epochs_1.events.shape[0] != epochs_2.events.shape[0])
     equalize_epoch_counts([epochs_1, epochs_2], method='mintime')
     assert_true(epochs_1.events.shape[0] == epochs_2.events.shape[0])
-    epochs_3 = Epochs(raw, events, event_id, tmin, tmax, picks=picks,
-                      add_eeg_ref=False)
-    epochs_4 = Epochs(raw, events, event_id_2, tmin, tmax, picks=picks,
-                      add_eeg_ref=False)
+    epochs_3 = Epochs(raw, events, event_id, tmin, tmax, picks=picks)
+    epochs_4 = Epochs(raw, events, event_id_2, tmin, tmax, picks=picks)
     equalize_epoch_counts([epochs_3, epochs_4], method='truncate')
     assert_true(epochs_1.events.shape[0] == epochs_3.events.shape[0])
     assert_true(epochs_3.events.shape[0] == epochs_4.events.shape[0])
 
     # equalizing conditions
     epochs = Epochs(raw, events, {'a': 1, 'b': 2, 'c': 3, 'd': 4},
-                    tmin, tmax, picks=picks, reject=reject, add_eeg_ref=False)
+                    tmin, tmax, picks=picks, reject=reject)
     epochs.drop_bad()  # make sure drops are logged
     assert_true(len([l for l in epochs.drop_log if not l]) ==
                 len(epochs.events))
     drop_log1 = deepcopy(epochs.drop_log)
     old_shapes = [epochs[key].events.shape[0] for key in ['a', 'b', 'c', 'd']]
-    epochs.equalize_event_counts(['a', 'b'], copy=False)
+    epochs.equalize_event_counts(['a', 'b'])
     # undo the eq logging
-    drop_log2 = [[] if l == ['EQUALIZED_COUNT'] else l for l in
+    drop_log2 = [[] if log == ['EQUALIZED_COUNT'] else log for log in
                  epochs.drop_log]
     assert_true(drop_log1 == drop_log2)
 
@@ -1396,20 +1380,19 @@ def test_epoch_eq():
     assert_true(new_shapes[3] == new_shapes[3])
     # now with two conditions collapsed
     old_shapes = new_shapes
-    epochs.equalize_event_counts([['a', 'b'], 'c'], copy=False)
+    epochs.equalize_event_counts([['a', 'b'], 'c'])
     new_shapes = [epochs[key].events.shape[0] for key in ['a', 'b', 'c', 'd']]
     assert_true(new_shapes[0] + new_shapes[1] == new_shapes[2])
     assert_true(new_shapes[3] == old_shapes[3])
-    assert_raises(KeyError, epochs.equalize_event_counts, [1, 'a'], copy=False)
+    assert_raises(KeyError, epochs.equalize_event_counts, [1, 'a'])
 
     # now let's combine conditions
     old_shapes = new_shapes
-    epochs.equalize_event_counts([['a', 'b'], ['c', 'd']], copy=False)
+    epochs.equalize_event_counts([['a', 'b'], ['c', 'd']])
     new_shapes = [epochs[key].events.shape[0] for key in ['a', 'b', 'c', 'd']]
     assert_true(old_shapes[0] + old_shapes[1] == new_shapes[0] + new_shapes[1])
     assert_true(new_shapes[0] + new_shapes[1] == new_shapes[2] + new_shapes[3])
-    assert_raises(ValueError, combine_event_ids, epochs, ['a', 'b'],
-                  {'ab': 1}, copy=False)
+    assert_raises(ValueError, combine_event_ids, epochs, ['a', 'b'], {'ab': 1})
 
     combine_event_ids(epochs, ['a', 'b'], {'ab': 12}, copy=False)
     caught = 0
@@ -1429,21 +1412,21 @@ def test_epoch_eq():
 
     # equalizing with hierarchical tags
     epochs = Epochs(raw, events, {'a/x': 1, 'b/x': 2, 'a/y': 3, 'b/y': 4},
-                    tmin, tmax, picks=picks, reject=reject, add_eeg_ref=False)
+                    tmin, tmax, picks=picks, reject=reject)
     cond1, cond2 = ['a', ['b/x', 'b/y']], [['a/x', 'a/y'], 'b']
-    es = [epochs.copy().equalize_event_counts(c, copy=False)[0]
+    es = [epochs.copy().equalize_event_counts(c)[0]
           for c in (cond1, cond2)]
     assert_array_equal(es[0].events[:, 0], es[1].events[:, 0])
     cond1, cond2 = ['a', ['b', 'b/y']], [['a/x', 'a/y'], 'x']
     for c in (cond1, cond2):  # error b/c tag and id mix/non-orthogonal tags
-        assert_raises(ValueError, epochs.equalize_event_counts, c, copy=False)
+        assert_raises(ValueError, epochs.equalize_event_counts, c)
     assert_raises(KeyError, epochs.equalize_event_counts,
-                  ["a/no_match", "b"], copy=False)
+                  ["a/no_match", "b"])
     # test equalization with no events of one type
     epochs.drop(np.arange(10))
     assert_equal(len(epochs['a/x']), 0)
     assert_true(len(epochs['a/y']) > 0)
-    epochs.equalize_event_counts(['a/x', 'a/y'], copy=False)
+    epochs.equalize_event_counts(['a/x', 'a/y'])
     assert_equal(len(epochs['a/x']), 0)
     assert_equal(len(epochs['a/y']), 0)
 
@@ -1454,42 +1437,38 @@ def test_access_by_name():
     raw, events, picks = _get_data()
 
     # Test various invalid inputs
-    assert_raises(ValueError, Epochs, raw, events, {1: 42, 2: 42}, tmin,
-                  tmax, picks=picks, add_eeg_ref=False)
-    assert_raises(ValueError, Epochs, raw, events, {'a': 'spam', 2: 'eggs'},
-                  tmin, tmax, picks=picks, add_eeg_ref=False)
-    assert_raises(ValueError, Epochs, raw, events, {'a': 'spam', 2: 'eggs'},
-                  tmin, tmax, picks=picks, add_eeg_ref=False)
-    assert_raises(ValueError, Epochs, raw, events, 'foo', tmin, tmax,
-                  picks=picks, add_eeg_ref=False)
-    assert_raises(ValueError, Epochs, raw, events, ['foo'], tmin, tmax,
-                  picks=picks, add_eeg_ref=False)
+    assert_raises(TypeError, Epochs, raw, events, {1: 42, 2: 42}, tmin,
+                  tmax, picks=picks)
+    assert_raises(TypeError, Epochs, raw, events, {'a': 'spam', 2: 'eggs'},
+                  tmin, tmax, picks=picks)
+    assert_raises(TypeError, Epochs, raw, events, {'a': 'spam', 2: 'eggs'},
+                  tmin, tmax, picks=picks)
+    assert_raises(TypeError, Epochs, raw, events, 'foo', tmin, tmax,
+                  picks=picks)
+    assert_raises(TypeError, Epochs, raw, events, ['foo'], tmin, tmax,
+                  picks=picks)
 
     # Test accessing non-existent events (assumes 12345678 does not exist)
     event_id_illegal = dict(aud_l=1, does_not_exist=12345678)
     assert_raises(ValueError, Epochs, raw, events, event_id_illegal,
-                  tmin, tmax, add_eeg_ref=False)
+                  tmin, tmax)
     # Test on_missing
     assert_raises(ValueError, Epochs, raw, events, 1, tmin, tmax,
-                  on_missing='foo', add_eeg_ref=False)
+                  on_missing='foo')
     with warnings.catch_warnings(record=True) as w:
         warnings.simplefilter('always')
-        Epochs(raw, events, event_id_illegal, tmin, tmax, on_missing='warning',
-               add_eeg_ref=False)
+        Epochs(raw, events, event_id_illegal, tmin, tmax, on_missing='warning')
         nw = len(w)
         assert_true(1 <= nw <= 2)
-        Epochs(raw, events, event_id_illegal, tmin, tmax, on_missing='ignore',
-               add_eeg_ref=False)
+        Epochs(raw, events, event_id_illegal, tmin, tmax, on_missing='ignore')
         assert_equal(len(w), nw)
 
     # Test constructing epochs with a list of ints as events
-    epochs = Epochs(raw, events, [1, 2], tmin, tmax, picks=picks,
-                    add_eeg_ref=False)
+    epochs = Epochs(raw, events, [1, 2], tmin, tmax, picks=picks)
     for k, v in epochs.event_id.items():
         assert_equal(int(k), v)
 
-    epochs = Epochs(raw, events, {'a': 1, 'b': 2}, tmin, tmax, picks=picks,
-                    add_eeg_ref=False)
+    epochs = Epochs(raw, events, {'a': 1, 'b': 2}, tmin, tmax, picks=picks)
     assert_raises(KeyError, epochs.__getitem__, 'bar')
 
     data = epochs['a'].get_data()
@@ -1497,7 +1476,7 @@ def test_access_by_name():
     assert_true(len(data) == len(event_a))
 
     epochs = Epochs(raw, events, {'a': 1, 'b': 2}, tmin, tmax, picks=picks,
-                    preload=True, add_eeg_ref=False)
+                    preload=True)
     assert_raises(KeyError, epochs.__getitem__, 'bar')
     temp_fname = op.join(tempdir, 'test-epo.fif')
     epochs.save(temp_fname)
@@ -1511,7 +1490,7 @@ def test_access_by_name():
     assert_array_equal(epochs2['a'].events, epochs['a'].events)
 
     epochs3 = Epochs(raw, events, {'a': 1, 'b': 2, 'c': 3, 'd': 4},
-                     tmin, tmax, picks=picks, preload=True, add_eeg_ref=False)
+                     tmin, tmax, picks=picks, preload=True)
     assert_equal(list(sorted(epochs3[('a', 'b')].event_id.values())),
                  [1, 2])
     epochs4 = epochs['a']
@@ -1526,16 +1505,15 @@ def test_access_by_name():
     assert_array_almost_equal(epochs.get_data(), epochs6.get_data(), 20)
 
     # Make sure we preserve names
-    assert_equal(epochs['a'].name, 'a')
-    assert_equal(epochs[['a', 'b']]['a'].name, 'a')
+    assert_equal(epochs['a']._name, 'a')
+    assert_equal(epochs[['a', 'b']]['a']._name, 'a')
 
 
 @requires_pandas
 def test_to_data_frame():
     """Test epochs Pandas exporter."""
     raw, events, picks = _get_data()
-    epochs = Epochs(raw, events, {'a': 1, 'b': 2}, tmin, tmax, picks=picks,
-                    add_eeg_ref=False)
+    epochs = Epochs(raw, events, {'a': 1, 'b': 2}, tmin, tmax, picks=picks)
     assert_raises(ValueError, epochs.to_data_frame, index=['foo', 'bar'])
     assert_raises(ValueError, epochs.to_data_frame, index='qux')
     assert_raises(ValueError, epochs.to_data_frame, np.arange(400))
@@ -1553,7 +1531,7 @@ def test_to_data_frame():
     assert_array_equal(df.values[:, 0], data[0] * 1e13)
     assert_array_equal(df.values[:, 2], data[2] * 1e15)
     for ind in ['time', ['condition', 'time'], ['condition', 'time', 'epoch']]:
-        df = epochs.to_data_frame(index=ind)
+        df = epochs.to_data_frame(picks=[11, 12, 14], index=ind)
         assert_true(df.index.names == ind if isinstance(ind, list) else [ind])
         # test that non-indexed data were present as categorial variables
         assert_array_equal(sorted(df.reset_index().columns[:3]),
@@ -1565,7 +1543,7 @@ def test_epochs_proj_mixin():
     raw, events, picks = _get_data()
     for proj in [True, False]:
         epochs = Epochs(raw, events[:4], event_id, tmin, tmax, picks=picks,
-                        baseline=(None, 0), proj=proj, add_eeg_ref=False)
+                        proj=proj)
 
         assert_true(all(p['active'] == proj for p in epochs.info['projs']))
 
@@ -1591,22 +1569,21 @@ def test_epochs_proj_mixin():
     # catch no-gos.
     # wrong proj argument
     assert_raises(ValueError, Epochs, raw, events[:4], event_id, tmin, tmax,
-                  picks=picks, baseline=(None, 0), proj='crazy',
-                  add_eeg_ref=False)
+                  picks=picks, proj='crazy')
 
     for preload in [True, False]:
         epochs = Epochs(raw, events[:4], event_id, tmin, tmax, picks=picks,
-                        baseline=(None, 0), proj='delayed', preload=preload,
-                        reject=reject, add_eeg_ref=False).set_eeg_reference()
+                        proj='delayed', preload=preload,
+                        reject=reject).set_eeg_reference(projection=True)
         epochs_proj = Epochs(
             raw, events[:4], event_id, tmin, tmax, picks=picks,
-            baseline=(None, 0), proj=True, preload=preload, add_eeg_ref=False,
-            reject=reject).set_eeg_reference().apply_proj()
+            proj=True, preload=preload,
+            reject=reject).set_eeg_reference(projection=True).apply_proj()
 
-        epochs_noproj = Epochs(
-            raw, events[:4], event_id, tmin, tmax, picks=picks,
-            baseline=(None, 0), proj=False, preload=preload, add_eeg_ref=False,
-            reject=reject).set_eeg_reference()
+        epochs_noproj = Epochs(raw, events[:4], event_id, tmin, tmax,
+                               picks=picks, proj=False, preload=preload,
+                               reject=reject)
+        epochs_noproj.set_eeg_reference(projection=True)
 
         assert_allclose(epochs.copy().apply_proj().get_data(),
                         epochs_proj.get_data(), rtol=1e-10, atol=1e-25)
@@ -1631,8 +1608,8 @@ def test_epochs_proj_mixin():
 
     # test mixin against manual application
     epochs = Epochs(raw, events[:4], event_id, tmin, tmax, picks=picks,
-                    baseline=None, proj=False,
-                    add_eeg_ref=False).set_eeg_reference()
+                    baseline=None,
+                    proj=False).set_eeg_reference(projection=True)
     data = epochs.get_data().copy()
     epochs.apply_proj()
     assert_allclose(np.dot(epochs._projector, data[0]), epochs._data[0])
@@ -1653,12 +1630,11 @@ def test_delayed_epochs():
     raw.info['lowpass'] = 40.  # fake the LP info so no warnings
     for decim in (1, 3):
         proj_data = Epochs(raw, events, event_id, tmin, tmax, proj=True,
-                           reject=reject, decim=decim, add_eeg_ref=False)
+                           reject=reject, decim=decim)
         use_tmin = proj_data.tmin
         proj_data = proj_data.get_data()
         noproj_data = Epochs(raw, events, event_id, tmin, tmax, proj=False,
-                             reject=reject, decim=decim,
-                             add_eeg_ref=False).get_data()
+                             reject=reject, decim=decim).get_data()
         assert_equal(proj_data.shape, noproj_data.shape)
         assert_equal(proj_data.shape[0], n_epochs)
         for preload in (True, False):
@@ -1669,8 +1645,7 @@ def test_delayed_epochs():
                     if ii in (0, 1):
                         epochs = Epochs(raw, events, event_id, tmin, tmax,
                                         proj=proj, reject=reject,
-                                        preload=preload, decim=decim,
-                                        add_eeg_ref=False)
+                                        preload=preload, decim=decim)
                     else:
                         fake_events = np.zeros((len(comp), 3), int)
                         fake_events[:, 0] = np.arange(len(comp))
@@ -1705,8 +1680,7 @@ def test_delayed_epochs():
 def test_drop_epochs():
     """Test dropping of epochs."""
     raw, events, picks = _get_data()
-    epochs = Epochs(raw, events, event_id, tmin, tmax, picks=picks,
-                    baseline=(None, 0), add_eeg_ref=False)
+    epochs = Epochs(raw, events, event_id, tmin, tmax, picks=picks)
     events1 = events[events[:, 2] == event_id]
 
     # Bound checks
@@ -1739,10 +1713,10 @@ def test_drop_epochs_mult():
     for preload in [True, False]:
         epochs1 = Epochs(raw, events, {'a': 1, 'b': 2},
                          tmin, tmax, picks=picks, reject=reject,
-                         preload=preload, add_eeg_ref=False)['a']
+                         preload=preload)['a']
         epochs2 = Epochs(raw, events, {'a': 1},
                          tmin, tmax, picks=picks, reject=reject,
-                         preload=preload, add_eeg_ref=False)
+                         preload=preload)
 
         if preload:
             # In the preload case you cannot know the bads if already ignored
@@ -1770,7 +1744,7 @@ def test_contains():
     seeg = RawArray(np.zeros((1, len(raw.times))),
                     create_info(['SEEG 001'], raw.info['sfreq'], 'seeg'))
     for key in ('dev_head_t', 'buffer_size_sec', 'highpass', 'lowpass',
-                'filename', 'dig', 'description', 'acq_pars', 'experimenter',
+                'dig', 'description', 'acq_pars', 'experimenter',
                 'proj_name'):
         seeg.info[key] = raw.info[key]
     raw.add_channels([seeg])
@@ -1782,8 +1756,7 @@ def test_contains():
     for (meg, eeg, seeg), others in tests:
         picks_contains = pick_types(raw.info, meg=meg, eeg=eeg, seeg=seeg)
         epochs = Epochs(raw, events, {'a': 1, 'b': 2}, tmin, tmax,
-                        picks=picks_contains, reject=None,
-                        preload=False, add_eeg_ref=False)
+                        picks=picks_contains)
         if eeg:
             test = 'eeg'
         elif seeg:
@@ -1801,8 +1774,7 @@ def test_drop_channels_mixin():
     """Test channels-dropping functionality."""
     raw, events = _get_data()[:2]
     # here without picks to get additional coverage
-    epochs = Epochs(raw, events, event_id, tmin, tmax, picks=None,
-                    baseline=(None, 0), preload=True, add_eeg_ref=False)
+    epochs = Epochs(raw, events, event_id, tmin, tmax, preload=True)
     drop_ch = epochs.ch_names[:3]
     ch_names = epochs.ch_names[3:]
 
@@ -1821,7 +1793,7 @@ def test_pick_channels_mixin():
     """Test channel-picking functionality."""
     raw, events, picks = _get_data()
     epochs = Epochs(raw, events, event_id, tmin, tmax, picks=picks,
-                    baseline=(None, 0), preload=True, add_eeg_ref=False)
+                    preload=True)
     ch_names = epochs.ch_names[:3]
     epochs.preload = False
     assert_raises(RuntimeError, epochs.drop_channels, [ch_names[0]])
@@ -1838,15 +1810,14 @@ def test_pick_channels_mixin():
 
     # Invalid picks
     assert_raises(ValueError, Epochs, raw, events, event_id, tmin, tmax,
-                  picks=[], add_eeg_ref=False)
+                  picks=[])
 
 
 def test_equalize_channels():
     """Test equalization of channels."""
     raw, events, picks = _get_data()
     epochs1 = Epochs(raw, events, event_id, tmin, tmax, picks=picks,
-                     baseline=(None, 0), proj=False, preload=True,
-                     add_eeg_ref=False)
+                     proj=False, preload=True)
     epochs2 = epochs1.copy()
     ch_names = epochs1.ch_names[2:]
     epochs1.drop_channels(epochs1.ch_names[:1])
@@ -1863,8 +1834,7 @@ def test_illegal_event_id():
     event_id_illegal = dict(aud_l=1, does_not_exist=12345678)
 
     assert_raises(ValueError, Epochs, raw, events, event_id_illegal, tmin,
-                  tmax, picks=picks, baseline=(None, 0), proj=False,
-                  add_eeg_ref=False)
+                  tmax, picks=picks, proj=False)
 
 
 def test_add_channels_epochs():
@@ -1872,9 +1842,8 @@ def test_add_channels_epochs():
     raw, events, picks = _get_data()
 
     def make_epochs(picks, proj):
-        return Epochs(raw, events, event_id, tmin, tmax, baseline=(None, 0),
-                      reject=None, preload=True, proj=proj, picks=picks,
-                      add_eeg_ref=False)
+        return Epochs(raw, events, event_id, tmin, tmax, preload=True,
+                      proj=proj, picks=picks)
 
     picks = pick_types(raw.info, meg=True, eeg=True, exclude='bads')
     picks_meg = pick_types(raw.info, meg=True, eeg=False, exclude='bads')
@@ -1888,8 +1857,7 @@ def test_add_channels_epochs():
         epochs_meg.info._check_consistency()
         epochs_eeg.info._check_consistency()
 
-        epochs2 = add_channels_epochs([epochs_meg, epochs_eeg],
-                                      add_eeg_ref=False)
+        epochs2 = add_channels_epochs([epochs_meg, epochs_eeg])
 
         assert_equal(len(epochs.info['projs']), len(epochs2.info['projs']))
         assert_equal(len(epochs.info.keys()), len(epochs_meg.info.keys()))
@@ -1906,81 +1874,74 @@ def test_add_channels_epochs():
 
     epochs_meg2 = epochs_meg.copy()
     epochs_meg2.info['meas_date'] += 10
-    add_channels_epochs([epochs_meg2, epochs_eeg], add_eeg_ref=False)
+    add_channels_epochs([epochs_meg2, epochs_eeg])
 
     epochs_meg2 = epochs_meg.copy()
-    epochs2.info['filename'] = epochs2.info['filename'].upper()
-    epochs2 = add_channels_epochs([epochs_meg, epochs_eeg],
-                                  add_eeg_ref=False)
+    epochs2 = add_channels_epochs([epochs_meg, epochs_eeg])
 
     epochs_meg2 = epochs_meg.copy()
     epochs_meg2.events[3, 2] -= 1
-    assert_raises(ValueError, add_channels_epochs,
-                  [epochs_meg2, epochs_eeg], add_eeg_ref=False)
+    assert_raises(ValueError, add_channels_epochs, [epochs_meg2, epochs_eeg])
 
     assert_raises(ValueError, add_channels_epochs,
-                  [epochs_meg, epochs_eeg[:2]], add_eeg_ref=False)
+                  [epochs_meg, epochs_eeg[:2]])
 
     epochs_meg.info['chs'].pop(0)
     epochs_meg.info._update_redundant()
-    assert_raises(RuntimeError, add_channels_epochs,
-                  [epochs_meg, epochs_eeg], add_eeg_ref=False)
+    assert_raises(RuntimeError, add_channels_epochs, [epochs_meg, epochs_eeg])
 
     epochs_meg2 = epochs_meg.copy()
     epochs_meg2.info['sfreq'] = None
-    assert_raises(RuntimeError, add_channels_epochs,
-                  [epochs_meg2, epochs_eeg], add_eeg_ref=False)
+    assert_raises(RuntimeError, add_channels_epochs, [epochs_meg2, epochs_eeg])
 
     epochs_meg2 = epochs_meg.copy()
     epochs_meg2.info['sfreq'] += 10
-    assert_raises(RuntimeError, add_channels_epochs,
-                  [epochs_meg2, epochs_eeg], add_eeg_ref=False)
+    assert_raises(RuntimeError, add_channels_epochs, [epochs_meg2, epochs_eeg])
 
     epochs_meg2 = epochs_meg.copy()
     epochs_meg2.info['chs'][1]['ch_name'] = epochs_meg2.info['ch_names'][0]
     epochs_meg2.info._update_redundant()
-    assert_raises(RuntimeError, add_channels_epochs,
-                  [epochs_meg2, epochs_eeg], add_eeg_ref=False)
+    with warnings.catch_warnings(record=True) as w:
+        warnings.simplefilter('always')
+        assert_raises(RuntimeError, add_channels_epochs,
+                      [epochs_meg2, epochs_eeg])
+        assert_equal(len(w), 1)
 
     epochs_meg2 = epochs_meg.copy()
     epochs_meg2.info['dev_head_t']['to'] += 1
-    assert_raises(ValueError, add_channels_epochs,
-                  [epochs_meg2, epochs_eeg], add_eeg_ref=False)
+    assert_raises(ValueError, add_channels_epochs, [epochs_meg2, epochs_eeg])
 
     epochs_meg2 = epochs_meg.copy()
     epochs_meg2.info['dev_head_t']['to'] += 1
-    assert_raises(ValueError, add_channels_epochs,
-                  [epochs_meg2, epochs_eeg], add_eeg_ref=False)
+    assert_raises(ValueError, add_channels_epochs, [epochs_meg2, epochs_eeg])
 
     epochs_meg2 = epochs_meg.copy()
     epochs_meg2.info['expimenter'] = 'foo'
-    assert_raises(RuntimeError, add_channels_epochs,
-                  [epochs_meg2, epochs_eeg], add_eeg_ref=False)
+    assert_raises(RuntimeError, add_channels_epochs, [epochs_meg2, epochs_eeg])
 
     epochs_meg2 = epochs_meg.copy()
     epochs_meg2.preload = False
-    assert_raises(ValueError, add_channels_epochs,
-                  [epochs_meg2, epochs_eeg], add_eeg_ref=False)
+    assert_raises(ValueError, add_channels_epochs, [epochs_meg2, epochs_eeg])
 
     epochs_meg2 = epochs_meg.copy()
     epochs_meg2.times += 0.4
     assert_raises(NotImplementedError, add_channels_epochs,
-                  [epochs_meg2, epochs_eeg], add_eeg_ref=False)
+                  [epochs_meg2, epochs_eeg])
 
     epochs_meg2 = epochs_meg.copy()
     epochs_meg2.times += 0.5
     assert_raises(NotImplementedError, add_channels_epochs,
-                  [epochs_meg2, epochs_eeg], add_eeg_ref=False)
+                  [epochs_meg2, epochs_eeg])
 
     epochs_meg2 = epochs_meg.copy()
     epochs_meg2.baseline = None
     assert_raises(NotImplementedError, add_channels_epochs,
-                  [epochs_meg2, epochs_eeg], add_eeg_ref=False)
+                  [epochs_meg2, epochs_eeg])
 
     epochs_meg2 = epochs_meg.copy()
     epochs_meg2.event_id['b'] = 2
     assert_raises(NotImplementedError, add_channels_epochs,
-                  [epochs_meg2, epochs_eeg], add_eeg_ref=False)
+                  [epochs_meg2, epochs_eeg])
 
 
 def test_array_epochs():
@@ -2037,14 +1998,14 @@ def test_array_epochs():
 
     # baseline
     data = np.ones((10, 20, 300))
-    epochs = EpochsArray(data, info, events=events, event_id=event_id,
-                         tmin=-.2, baseline=(None, 0))
+    epochs = EpochsArray(data, info, events, event_id=event_id, tmin=-.2,
+                         baseline=(None, 0))
     ep_data = epochs.get_data()
-    assert_array_equal(np.zeros_like(ep_data), ep_data)
+    assert_array_equal(ep_data, np.zeros_like(ep_data))
 
     # one time point
     epochs = EpochsArray(data[:, :, :1], info, events=events,
-                         event_id=event_id, tmin=0., baseline=None)
+                         event_id=event_id, tmin=0.)
     assert_allclose(epochs.times, [0.])
     assert_allclose(epochs.get_data(), data[:, :, :1])
     epochs.save(temp_fname)
@@ -2056,8 +2017,7 @@ def test_array_epochs():
     mask = (events[:, 2] == 1)
     data_1 = data[mask]
     events_1 = events[mask]
-    epochs = EpochsArray(data_1, info, events=events_1, event_id=1,
-                         tmin=-0.2, baseline=(None, 0))
+    epochs = EpochsArray(data_1, info, events=events_1, event_id=1, tmin=-0.2)
 
     # default events
     epochs = EpochsArray(data_1, info)
@@ -2070,7 +2030,7 @@ def test_concatenate_epochs():
     """Test concatenate epochs."""
     raw, events, picks = _get_data()
     epochs = Epochs(raw=raw, events=events, event_id=event_id, tmin=tmin,
-                    tmax=tmax, picks=picks, add_eeg_ref=False)
+                    tmax=tmax, picks=picks)
     epochs2 = epochs.copy()
     epochs_list = [epochs, epochs2]
     epochs_conc = concatenate_epochs(epochs_list)
@@ -2114,16 +2074,31 @@ def test_concatenate_epochs():
     epochs.info['dev_head_t'] = None
     concatenate_epochs([epochs, epochs2])  # should work
 
+    # check that different event_id does not work:
+    epochs1 = epochs.copy()
+    epochs2 = epochs.copy()
+    epochs1.event_id = dict(a=1)
+    epochs2.event_id = dict(a=2)
+    assert_raises(ValueError, concatenate_epochs, [epochs1, epochs2])
+
+    # check events are shifted, but relative position are equal
+    epochs_list = [epochs.copy() for ii in range(3)]
+    epochs_cat = concatenate_epochs(epochs_list)
+    for ii in range(3):
+        evs = epochs_cat.events[ii * len(epochs):(ii + 1) * len(epochs)]
+        rel_pos = epochs_list[ii].events[:, 0] - evs[:, 0]
+        assert_true(sum(rel_pos - rel_pos[0]) == 0)
+
 
 def test_add_channels():
     """Test epoch splitting / re-appending channel types."""
     raw, events, picks = _get_data()
     epoch_nopre = Epochs(
         raw=raw, events=events, event_id=event_id, tmin=tmin, tmax=tmax,
-        picks=picks, add_eeg_ref=False)
+        picks=picks)
     epoch = Epochs(
         raw=raw, events=events, event_id=event_id, tmin=tmin, tmax=tmax,
-        picks=picks, preload=True, add_eeg_ref=False)
+        picks=picks, preload=True)
     epoch_eeg = epoch.copy().pick_types(meg=False, eeg=True)
     epoch_meg = epoch.copy().pick_types(meg=True)
     epoch_stim = epoch.copy().pick_types(meg=False, stim=True)
@@ -2168,9 +2143,8 @@ def test_seeg_ecog():
 def test_default_values():
     """Test default event_id, tmax tmin values are working correctly"""
     raw, events = _get_data()[:2]
-    epoch_1 = Epochs(raw, events[:1], preload=True, add_eeg_ref=False)
-    epoch_2 = Epochs(raw, events[:1], event_id=None, tmin=-0.2, tmax=0.5,
-                     preload=True, add_eeg_ref=False)
+    epoch_1 = Epochs(raw, events[:1], preload=True)
+    epoch_2 = Epochs(raw, events[:1], tmin=-0.2, tmax=0.5, preload=True)
     assert_equal(hash(epoch_1), hash(epoch_2))
 
 
diff --git a/mne/tests/test_event.py b/mne/tests/test_event.py
index e1dd4d6..f6e8fc1 100644
--- a/mne/tests/test_event.py
+++ b/mne/tests/test_event.py
@@ -38,7 +38,7 @@ raw_fname = op.join(base_dir, 'test_raw.fif')
 
 def test_fix_stim():
     """Test fixing stim STI016 for Neuromag."""
-    raw = read_raw_fif(raw_fname, preload=True, add_eeg_ref=False)
+    raw = read_raw_fif(raw_fname, preload=True)
     # 32768 (016) + 3 (002+001) bits gets incorrectly coded during acquisition
     raw._data[raw.ch_names.index('STI 014'), :3] = [0, -32765, 0]
     with warnings.catch_warnings(record=True) as w:
@@ -53,10 +53,10 @@ def test_fix_stim():
 def test_add_events():
     """Test adding events to a Raw file."""
     # need preload
-    raw = read_raw_fif(raw_fname, preload=False, add_eeg_ref=False)
+    raw = read_raw_fif(raw_fname)
     events = np.array([[raw.first_samp, 0, 1]])
     assert_raises(RuntimeError, raw.add_events, events, 'STI 014')
-    raw = read_raw_fif(raw_fname, preload=True, add_eeg_ref=False)
+    raw = read_raw_fif(raw_fname, preload=True)
     orig_events = find_events(raw, 'STI 014')
     # add some events
     events = np.array([raw.first_samp, 0, 1])
@@ -179,7 +179,7 @@ def test_io_events():
 def test_find_events():
     """Test find events in raw file."""
     events = read_events(fname)
-    raw = read_raw_fif(raw_fname, preload=True, add_eeg_ref=False)
+    raw = read_raw_fif(raw_fname, preload=True)
     # let's test the defaulting behavior while we're at it
     extra_ends = ['', '_1']
     orig_envs = [os.getenv('MNE_STIM_CHANNEL%s' % s) for s in extra_ends]
@@ -192,7 +192,7 @@ def test_find_events():
     events11 = find_events(raw, mask=3, mask_type='not_and')
     with warnings.catch_warnings(record=True) as w:
         warnings.simplefilter('always')
-        events22 = read_events(fname, mask=3)
+        events22 = read_events(fname, mask=3, mask_type='not_and')
         assert_true(sum('events masked' in str(ww.message) for ww in w) == 1)
     assert_array_equal(events11, events22)
 
@@ -210,7 +210,7 @@ def test_find_events():
     raw._data[stim_channel_idx, 5:] = 0
     # 1 == '0b1', 2 == '0b10', 3 == '0b11', 4 == '0b100'
 
-    assert_raises(TypeError, find_events, raw, mask="0")
+    assert_raises(TypeError, find_events, raw, mask="0", mask_type='and')
     assert_raises(ValueError, find_events, raw, mask=0, mask_type='blah')
     # testing mask_type. default = 'not_and'
     assert_array_equal(find_events(raw, shortest_event=1, mask=1,
@@ -343,6 +343,22 @@ def test_find_events():
         if o is not None:
             os.environ['MNE_STIM_CHANNEL%s' % s] = o
 
+    # Test with list of stim channels
+    raw._data[stim_channel_idx, 1:101] = np.zeros(100)
+    raw._data[stim_channel_idx, 10:11] = 1
+    raw._data[stim_channel_idx, 30:31] = 3
+    stim_channel2 = 'STI 015'
+    stim_channel2_idx = pick_channels(raw.info['ch_names'],
+                                      include=[stim_channel2])
+    raw._data[stim_channel2_idx, :] = 0
+    raw._data[stim_channel2_idx, :100] = raw._data[stim_channel_idx, 5:105]
+    events1 = find_events(raw, stim_channel='STI 014')
+    events2 = events1.copy()
+    events2[:, 0] -= 5
+    events = find_events(raw, stim_channel=['STI 014', stim_channel2])
+    assert_array_equal(events[::2], events2)
+    assert_array_equal(events[1::2], events1)
+
 
 def test_pick_events():
     """Test pick events in a events ndarray."""
@@ -366,7 +382,7 @@ def test_pick_events():
 
 def test_make_fixed_length_events():
     """Test making events of a fixed length."""
-    raw = read_raw_fif(raw_fname, add_eeg_ref=False)
+    raw = read_raw_fif(raw_fname)
     events = make_fixed_length_events(raw, id=1)
     assert_true(events.shape[1], 3)
     events_zero = make_fixed_length_events(raw, 1, first_samp=False)
@@ -390,7 +406,7 @@ def test_make_fixed_length_events():
 def test_define_events():
     """Test defining response events."""
     events = read_events(fname)
-    raw = read_raw_fif(raw_fname, add_eeg_ref=False)
+    raw = read_raw_fif(raw_fname)
     events_, _ = define_target_events(events, 5, 32, raw.info['sfreq'],
                                       .2, 0.7, 42, 99)
     n_target = events[events[:, 2] == 5].shape[0]
@@ -442,7 +458,8 @@ def test_acqparser():
     assert_true(acqp['Surprise visual'])
     # test TRIUX file
     raw = read_raw_fif(fname_raw_elekta, preload=False)
-    acqp = AcqParserFIF(raw.info)
+    acqp = raw.acqparser
+    assert_true(acqp is raw.acqparser)  # same one, not regenerated
     # test __repr__()
     assert_true(repr(acqp))
     # this file should not be in compatibility mode
diff --git a/mne/tests/test_evoked.py b/mne/tests/test_evoked.py
index 45794f9..1a74ed7 100644
--- a/mne/tests/test_evoked.py
+++ b/mne/tests/test_evoked.py
@@ -14,6 +14,7 @@ from scipy import fftpack
 from numpy.testing import (assert_array_almost_equal, assert_equal,
                            assert_array_equal, assert_allclose)
 from nose.tools import assert_true, assert_raises, assert_not_equal
+import pytest
 
 from mne import (equalize_channels, pick_types, read_evokeds, write_evokeds,
                  grand_average, combine_evoked, create_info, read_events,
@@ -21,7 +22,7 @@ from mne import (equalize_channels, pick_types, read_evokeds, write_evokeds,
 from mne.evoked import _get_peak, Evoked, EvokedArray
 from mne.io import read_raw_fif
 from mne.tests.common import assert_naming
-from mne.utils import (_TempDir, requires_pandas, slow_test, requires_version,
+from mne.utils import (_TempDir, requires_pandas, requires_version,
                        run_tests_if_main)
 from mne.externals.six.moves import cPickle as pickle
 
@@ -55,13 +56,12 @@ def test_decim():
     assert_array_equal(data_epochs, data_epochs_3)
 
     # Now let's do it with some real data
-    raw = read_raw_fif(raw_fname, add_eeg_ref=False)
+    raw = read_raw_fif(raw_fname)
     events = read_events(event_name)
     sfreq_new = raw.info['sfreq'] / decim
     raw.info['lowpass'] = sfreq_new / 4.  # suppress aliasing warnings
     picks = pick_types(raw.info, meg=True, eeg=True, exclude=())
-    epochs = Epochs(raw, events, 1, -0.2, 0.5, picks=picks, preload=True,
-                    add_eeg_ref=False)
+    epochs = Epochs(raw, events, 1, -0.2, 0.5, picks=picks, preload=True)
     for offset in (0, 1):
         ev_ep_decim = epochs.copy().decimate(decim, offset).average()
         ev_decim = epochs.average().decimate(decim, offset)
@@ -110,7 +110,7 @@ def test_hash_evoked():
     assert_not_equal(hash(ave), hash(ave_2))
 
 
- at slow_test
+ at pytest.mark.slowtest
 def test_io_evoked():
     """Test IO for evoked data (fif + gz) with integer and str args."""
     tempdir = _TempDir()
@@ -226,7 +226,7 @@ def test_shift_time_evoked():
 
 
 def test_evoked_resample():
-    """Test for resampling of evoked data."""
+    """Test resampling evoked data."""
     tempdir = _TempDir()
     # upsample, write it out, read it in
     ave = read_evokeds(fname, 0)
@@ -256,6 +256,17 @@ def test_evoked_resample():
     assert_true(ave_up.data.shape[1] == 2 * ave_normal.data.shape[1])
 
 
+def test_evoked_filter():
+    """Test filtering evoked data."""
+    # this is mostly a smoke test as the Epochs and raw tests are more complete
+    ave = read_evokeds(fname, 0).pick_types('grad')
+    ave.data[:] = 1.
+    assert round(ave.info['lowpass']) == 172
+    ave_filt = ave.copy().filter(None, 40., fir_design='firwin')
+    assert ave_filt.info['lowpass'] == 40.
+    assert_allclose(ave.data, 1., atol=1e-6)
+
+
 def test_evoked_detrend():
     """Test for detrending evoked data."""
     ave = read_evokeds(fname, 0)
@@ -274,7 +285,7 @@ def test_to_data_frame():
     assert_raises(ValueError, ave.to_data_frame, picks=np.arange(400))
     df = ave.to_data_frame()
     assert_true((df.columns == ave.ch_names).all())
-    df = ave.to_data_frame(index=None).reset_index('time')
+    df = ave.to_data_frame(index=None).reset_index()
     assert_true('time' in df.columns)
     assert_array_equal(df.values[:, 1], ave.data[0] * 1e13)
     assert_array_equal(df.values[:, 3], ave.data[2] * 1e15)
@@ -328,6 +339,10 @@ def test_get_peak():
     assert_true(time_idx < len(evoked.times))
     assert_equal(ch_name, 'MEG 1421')
 
+    assert_raises(ValueError, evoked.get_peak, ch_type='mag', merge_grads=True)
+    ch_name, time_idx = evoked.get_peak(ch_type='grad', merge_grads=True)
+    assert_equal(ch_name, 'MEG 244X')
+
     data = np.array([[0., 1.,  2.],
                      [0., -3.,  0]])
 
@@ -417,8 +432,7 @@ def test_arithmetic():
     # combine_evoked([ev1, ev2]) should be the same as ev1 + ev2:
     # data should be added according to their `nave` weights
     # nave = ev1.nave + ev2.nave
-    with warnings.catch_warnings(record=True):  # deprecation no weights
-        ev = combine_evoked([ev1, ev2])
+    ev = combine_evoked([ev1, ev2], weights='nave')
     assert_equal(ev.nave, ev1.nave + ev2.nave)
     assert_allclose(ev.data, 1. / 3. * np.ones_like(ev.data))
 
@@ -516,7 +530,7 @@ def test_array_epochs():
 
     # test kind check
     assert_raises(TypeError, EvokedArray, data1, info, tmin=0, kind=1)
-    assert_raises(ValueError, EvokedArray, data1, info, tmin=0, kind='mean')
+    assert_raises(ValueError, EvokedArray, data1, info, kind='mean')
 
     # test match between channels info and data
     ch_names = ['EEG %03d' % (i + 1) for i in range(19)]
diff --git a/mne/tests/test_filter.py b/mne/tests/test_filter.py
index 88ee708..c8d6b0d 100644
--- a/mne/tests/test_filter.py
+++ b/mne/tests/test_filter.py
@@ -5,17 +5,18 @@ import numpy as np
 from numpy.testing import (assert_array_almost_equal, assert_almost_equal,
                            assert_array_equal, assert_allclose)
 from nose.tools import assert_equal, assert_true, assert_raises
+import pytest
 from scipy.signal import resample as sp_resample, butter
+from scipy.fftpack import fft, fftfreq
 
 from mne import create_info
 from mne.io import RawArray, read_raw_fif
-from mne.filter import (band_pass_filter, high_pass_filter, low_pass_filter,
-                        band_stop_filter, resample, _resample_stim_channels,
+from mne.filter import (filter_data, resample, _resample_stim_channels,
                         construct_iir_filter, notch_filter, detrend,
                         _overlap_add_filter, _smart_pad, design_mne_c_filter,
-                        estimate_ringing_samples, filter_data)
+                        estimate_ringing_samples, create_filter, _Interp2)
 
-from mne.utils import (sum_squared, run_tests_if_main, slow_test,
+from mne.utils import (sum_squared, run_tests_if_main,
                        catch_logging, requires_version, _TempDir,
                        requires_mne, run_subprocess)
 
@@ -25,7 +26,7 @@ rng = np.random.RandomState(0)
 
 @requires_mne
 def test_mne_c_design():
-    """Test MNE-C filter design"""
+    """Test MNE-C filter design."""
     tempdir = _TempDir()
     temp_fname = op.join(tempdir, 'test_raw.fif')
     out_fname = op.join(tempdir, 'test_c_raw.fif')
@@ -40,23 +41,23 @@ def test_mne_c_design():
            '--save', out_fname)
     run_subprocess(cmd)
     h = design_mne_c_filter(sfreq, None, 40)
-    h_c = read_raw_fif(out_fname, add_eeg_ref=False)[0][0][0][time_sl]
+    h_c = read_raw_fif(out_fname)[0][0][0][time_sl]
     assert_allclose(h, h_c, **tols)
 
     run_subprocess(cmd + ('--highpass', '5', '--highpassw', '2.5'))
     h = design_mne_c_filter(sfreq, 5, 40, 2.5)
-    h_c = read_raw_fif(out_fname, add_eeg_ref=False)[0][0][0][time_sl]
+    h_c = read_raw_fif(out_fname)[0][0][0][time_sl]
     assert_allclose(h, h_c, **tols)
 
     run_subprocess(cmd + ('--lowpass', '1000', '--highpass', '10'))
     h = design_mne_c_filter(sfreq, 10, None, verbose=True)
-    h_c = read_raw_fif(out_fname, add_eeg_ref=False)[0][0][0][time_sl]
+    h_c = read_raw_fif(out_fname)[0][0][0][time_sl]
     assert_allclose(h, h_c, **tols)
 
 
 @requires_version('scipy', '0.16')
 def test_estimate_ringing():
-    """Test our ringing estimation function"""
+    """Test our ringing estimation function."""
     # Actual values might differ based on system, so let's be approximate
     for kind in ('ba', 'sos'):
         for thresh, lims in ((0.1, (30, 60)),  # 47
@@ -73,7 +74,7 @@ def test_estimate_ringing():
 
 
 def test_1d_filter():
-    """Test our private overlap-add filtering function"""
+    """Test our private overlap-add filtering function."""
     # make some random signals and filters
     for n_signal in (1, 2, 3, 5, 10, 20, 40):
         x = rng.randn(n_signal)
@@ -85,7 +86,7 @@ def test_1d_filter():
                     h = np.concatenate([[1.], np.zeros(n_filter - 1)])
                 # ensure we pad the signal the same way for both filters
                 n_pad = n_filter - 1
-                x_pad = _smart_pad(x, np.array([n_pad, n_pad]))
+                x_pad = _smart_pad(x, (n_pad, n_pad))
                 for phase in ('zero', 'linear', 'zero-double'):
                     # compute our expected result the slow way
                     if phase == 'zero':
@@ -140,64 +141,63 @@ def test_1d_filter():
 
 @requires_version('scipy', '0.16')
 def test_iir_stability():
-    """Test IIR filter stability check"""
+    """Test IIR filter stability check."""
     sig = np.empty(1000)
     sfreq = 1000
     # This will make an unstable filter, should throw RuntimeError
-    assert_raises(RuntimeError, high_pass_filter, sig, sfreq, 0.6,
+    assert_raises(RuntimeError, filter_data, sig, sfreq, 0.6, None,
                   method='iir', iir_params=dict(ftype='butter', order=8,
                                                 output='ba'))
     # This one should work just fine
-    high_pass_filter(sig, sfreq, 0.6, method='iir',
-                     iir_params=dict(ftype='butter', order=8, output='sos'))
+    filter_data(sig, sfreq, 0.6, None, method='iir',
+                iir_params=dict(ftype='butter', order=8, output='sos'))
     # bad system type
-    assert_raises(ValueError, high_pass_filter, sig, sfreq, 0.6, method='iir',
+    assert_raises(ValueError, filter_data, sig, sfreq, 0.6, None, method='iir',
                   iir_params=dict(ftype='butter', order=8, output='foo'))
     # missing ftype
-    assert_raises(RuntimeError, high_pass_filter, sig, sfreq, 0.6,
+    assert_raises(RuntimeError, filter_data, sig, sfreq, 0.6, None,
                   method='iir', iir_params=dict(order=8, output='sos'))
     # bad ftype
-    assert_raises(RuntimeError, high_pass_filter, sig, sfreq, 0.6,
+    assert_raises(RuntimeError, filter_data, sig, sfreq, 0.6, None,
                   method='iir',
                   iir_params=dict(order=8, ftype='foo', output='sos'))
     # missing gstop
-    assert_raises(RuntimeError, high_pass_filter, sig, sfreq, 0.6,
+    assert_raises(RuntimeError, filter_data, sig, sfreq, 0.6, None,
                   method='iir', iir_params=dict(gpass=0.5, output='sos'))
     # can't pass iir_params if method='fft'
-    assert_raises(ValueError, high_pass_filter, sig, sfreq, 0.1,
+    assert_raises(ValueError, filter_data, sig, sfreq, 0.1, None,
                   method='fft', iir_params=dict(ftype='butter', order=2,
                                                 output='sos'))
     # method must be string
-    assert_raises(TypeError, high_pass_filter, sig, sfreq, 0.1,
+    assert_raises(TypeError, filter_data, sig, sfreq, 0.1, None,
                   method=1)
     # unknown method
-    assert_raises(ValueError, high_pass_filter, sig, sfreq, 0.1,
+    assert_raises(ValueError, filter_data, sig, sfreq, 0.1, None,
                   method='blah')
     # bad iir_params
-    assert_raises(TypeError, high_pass_filter, sig, sfreq, 0.1,
+    assert_raises(TypeError, filter_data, sig, sfreq, 0.1, None,
                   method='iir', iir_params='blah')
-    assert_raises(ValueError, high_pass_filter, sig, sfreq, 0.1,
+    assert_raises(ValueError, filter_data, sig, sfreq, 0.1, None,
                   method='fft', iir_params=dict())
 
     # should pass because dafault trans_bandwidth is not relevant
     iir_params = dict(ftype='butter', order=2, output='sos')
-    x_sos = high_pass_filter(sig, 250, 0.5, method='iir',
-                             iir_params=iir_params)
+    x_sos = filter_data(sig, 250, 0.5, None, method='iir',
+                        iir_params=iir_params)
     iir_params_sos = construct_iir_filter(iir_params, f_pass=0.5, sfreq=250,
                                           btype='highpass')
-    x_sos_2 = high_pass_filter(sig, 250, 0.5, method='iir',
-                               iir_params=iir_params_sos)
+    x_sos_2 = filter_data(sig, 250, 0.5, None, method='iir',
+                          iir_params=iir_params_sos)
     assert_allclose(x_sos[100:-100], x_sos_2[100:-100])
-    x_ba = high_pass_filter(sig, 250, 0.5, method='iir',
-                            iir_params=dict(ftype='butter', order=2,
-                                            output='ba'))
+    x_ba = filter_data(sig, 250, 0.5, None, method='iir',
+                       iir_params=dict(ftype='butter', order=2, output='ba'))
     # Note that this will fail for higher orders (e.g., 6) showing the
     # hopefully decreased numerical error of SOS
     assert_allclose(x_sos[100:-100], x_ba[100:-100])
 
 
 def test_notch_filters():
-    """Test notch filters"""
+    """Test notch filters."""
     # let's use an ugly, prime sfreq for fun
     sfreq = 487.0
     sig_len_secs = 20
@@ -214,15 +214,14 @@ def test_notch_filters():
     assert_raises(ValueError, notch_filter, a, sfreq, None, 'fft')
     assert_raises(ValueError, notch_filter, a, sfreq, None, 'iir')
     methods = ['spectrum_fit', 'spectrum_fit', 'fft', 'fft', 'iir']
-    filter_lengths = [None, None, None, 8192, None]
+    filter_lengths = ['auto', 'auto', 'auto', 8192, 'auto']
     line_freqs = [None, freqs, freqs, freqs, freqs]
     tols = [2, 1, 1, 1]
     for meth, lf, fl, tol in zip(methods, line_freqs, filter_lengths, tols):
         with catch_logging() as log_file:
-            with warnings.catch_warnings(record=True):  # filter_length=None
-                b = notch_filter(a, sfreq, lf, filter_length=fl, method=meth,
-                                 phase='zero', verbose=True)
-
+            with warnings.catch_warnings(record=True):
+                b = notch_filter(a, sfreq, lf, fl, method=meth,
+                                 fir_design='firwin', verbose=True)
         if lf is None:
             out = log_file.getvalue().split('\n')[:-1]
             if len(out) != 2 and len(out) != 3:  # force_serial: len(out) == 3
@@ -234,7 +233,7 @@ def test_notch_filters():
 
 
 def test_resample():
-    """Test resampling"""
+    """Test resampling."""
     x = rng.normal(0, 1, (10, 10, 10))
     x_rs = resample(x, 1, 2, 10)
     assert_equal(x.shape, (10, 10, 10))
@@ -253,7 +252,7 @@ def test_resample():
 
 
 def test_resample_stim_channel():
-    """Test resampling of stim channels"""
+    """Test resampling of stim channels."""
 
     # Downsampling
     assert_array_equal(
@@ -281,9 +280,9 @@ def test_resample_stim_channel():
 
 
 @requires_version('scipy', '0.16')
- at slow_test
+ at pytest.mark.slowtest
 def test_filters():
-    """Test low-, band-, high-pass, and band-stop filters plus resampling"""
+    """Test low-, band-, high-pass, and band-stop filters plus resampling."""
     sfreq = 100
     sig_len_secs = 15
 
@@ -291,40 +290,62 @@ def test_filters():
 
     # let's test our catchers
     for fl in ['blah', [0, 1], 1000.5, '10ss', '10']:
-        assert_raises(ValueError, band_pass_filter, a, sfreq, 4, 8, fl,
-                      1.0, 1.0, phase='zero')
+        assert_raises(ValueError, filter_data, a, sfreq, 4, 8, None, fl,
+                      1.0, 1.0, fir_design='firwin')
     for nj in ['blah', 0.5]:
-        assert_raises(ValueError, band_pass_filter, a, sfreq, 4, 8, 100,
-                      1.0, 1.0, n_jobs=nj, phase='zero', fir_window='hann')
-    assert_raises(ValueError, band_pass_filter, a, sfreq, 4, 8, 100,
-                  1.0, 1.0, phase='zero', fir_window='foo')
+        assert_raises(ValueError, filter_data, a, sfreq, 4, 8, None, 1000,
+                      1.0, 1.0, n_jobs=nj, phase='zero', fir_design='firwin')
+    assert_raises(ValueError, filter_data, a, sfreq, 4, 8, None, 100,
+                  1., 1., fir_window='foo')
+    assert_raises(ValueError, filter_data, a, sfreq, 4, 8, None, 10,
+                  1., 1., fir_design='firwin')  # too short
     # > Nyq/2
-    assert_raises(ValueError, band_pass_filter, a, sfreq, 4, sfreq / 2.,
-                  100, 1.0, 1.0, phase='zero', fir_window='hann')
-    assert_raises(ValueError, low_pass_filter, a, sfreq, sfreq / 2.,
-                  100, 1.0, phase='zero', fir_window='hann')
+    assert_raises(ValueError, filter_data, a, sfreq, 4, sfreq / 2., None,
+                  100, 1.0, 1.0, fir_design='firwin')
+    assert_raises(ValueError, filter_data, a, sfreq, -1, None, None,
+                  100, 1.0, 1.0, fir_design='firwin')
+    # these should work
+    create_filter(a, sfreq, None, None, fir_design='firwin')
+    create_filter(a, sfreq, None, None, method='iir')
+
     # check our short-filter warning:
     with warnings.catch_warnings(record=True) as w:
         # Warning for low attenuation
-        band_pass_filter(a, sfreq, 1, 8, filter_length=256, phase='zero')
+        filter_data(a, sfreq, 1, 8, filter_length=256, fir_design='firwin2')
     assert_true(any('attenuation' in str(ww.message) for ww in w))
     with warnings.catch_warnings(record=True) as w:
         # Warning for too short a filter
-        band_pass_filter(a, sfreq, 1, 8, filter_length='0.5s', phase='zero')
+        filter_data(a, sfreq, 1, 8, filter_length='0.5s', fir_design='firwin2')
     assert_true(any('Increase filter_length' in str(ww.message) for ww in w))
 
     # try new default and old default
-    for fl in ['auto', '10s', '5000ms', 1024]:
-        bp = band_pass_filter(a, sfreq, 4, 8, fl, 1.0, 1.0, phase='zero',
-                              fir_window='hamming')
-        bs = band_stop_filter(a, sfreq, 4 - 1.0, 8 + 1.0, fl, 1.0, 1.0,
-                              phase='zero', fir_window='hamming')
-        lp = low_pass_filter(a, sfreq, 8, fl, 1.0, n_jobs=2, phase='zero',
-                             fir_window='hamming')
-        hp = high_pass_filter(lp, sfreq, 4, fl, 1.0, phase='zero',
-                              fir_window='hamming')
-        assert_array_almost_equal(hp, bp, 4)
-        assert_array_almost_equal(bp + bs, a, 4)
+    freqs = fftfreq(a.shape[-1], 1. / sfreq)
+    A = np.abs(fft(a))
+    kwargs = dict(fir_design='firwin')
+    for fl in ['auto', '10s', '5000ms', 1024, 1023]:
+        bp = filter_data(a, sfreq, 4, 8, None, fl, 1.0, 1.0, **kwargs)
+        bs = filter_data(a, sfreq, 8 + 1.0, 4 - 1.0, None, fl, 1.0, 1.0,
+                         **kwargs)
+        lp = filter_data(a, sfreq, None, 8, None, fl, 10, 1.0, n_jobs=2,
+                         **kwargs)
+        hp = filter_data(lp, sfreq, 4, None, None, fl, 1.0, 10, **kwargs)
+        assert_allclose(hp, bp, rtol=1e-3, atol=1e-3)
+        assert_allclose(bp + bs, a, rtol=1e-3, atol=1e-3)
+        # Sanity check ttenuation
+        mask = (freqs > 5.5) & (freqs < 6.5)
+        assert_allclose(np.mean(np.abs(fft(bp)[:, mask]) / A[:, mask]),
+                        1., atol=0.02)
+        assert_allclose(np.mean(np.abs(fft(bs)[:, mask]) / A[:, mask]),
+                        0., atol=0.2)
+        # now the minimum-phase versions
+        bp = filter_data(a, sfreq, 4, 8, None, fl, 1.0, 1.0,
+                         phase='minimum', **kwargs)
+        bs = filter_data(a, sfreq, 8 + 1.0, 4 - 1.0, None, fl, 1.0, 1.0,
+                         phase='minimum', **kwargs)
+        assert_allclose(np.mean(np.abs(fft(bp)[:, mask]) / A[:, mask]),
+                        1., atol=0.11)
+        assert_allclose(np.mean(np.abs(fft(bs)[:, mask]) / A[:, mask]),
+                        0., atol=0.3)
 
     # and since these are low-passed, downsampling/upsampling should be close
     n_resamp_ignore = 10
@@ -373,18 +394,25 @@ def test_filters():
     a = rng.randn(5 * sfreq, 5 * sfreq)
     b = a[:, None, :]
 
-    a_filt = band_pass_filter(a, sfreq, 4, 8, 400, 2.0, 2.0, phase='zero',
-                              fir_window='hamming')
-    b_filt = band_pass_filter(b, sfreq, 4, 8, 400, 2.0, 2.0, picks=[0],
-                              phase='zero', fir_window='hamming')
+    a_filt = filter_data(a, sfreq, 4, 8, None, 400, 2.0, 2.0,
+                         fir_design='firwin')
+    b_filt = filter_data(b, sfreq, 4, 8, [0], 400, 2.0, 2.0,
+                         fir_design='firwin')
 
     assert_array_equal(a_filt[:, None, :], b_filt)
 
     # check for n-dimensional case
     a = rng.randn(2, 2, 2, 2)
     with warnings.catch_warnings(record=True):  # filter too long
-        assert_raises(ValueError, band_pass_filter, a, sfreq, 4, 8, 100,
-                      1.0, 1.0, picks=np.array([0, 1]), phase='zero')
+        assert_raises(ValueError, filter_data, a, sfreq, 4, 8,
+                      np.array([0, 1]), 100, 1.0, 1.0)
+
+    # check corner case (#4693)
+    h = create_filter(
+        np.empty(10000), 1000., l_freq=None, h_freq=55.,
+        h_trans_bandwidth=0.5, method='fir', phase='zero-double',
+        fir_design='firwin', verbose=True)
+    assert len(h) == 6601
 
 
 def test_filter_auto():
@@ -399,28 +427,30 @@ def test_filter_auto():
     t = np.arange(N) / sfreq
     x += np.sin(2 * np.pi * sine_freq * t)
     x_orig = x.copy()
-    x_filt = low_pass_filter(x, sfreq, lp, 'auto', 'auto', phase='zero',
-                             fir_window='hamming')
-    assert_array_equal(x, x_orig)
-    # the firwin2 function gets us this close
-    assert_allclose(x, x_filt, rtol=1e-4, atol=1e-4)
-    assert_array_equal(x_filt, low_pass_filter(
-        x, sfreq, lp, 'auto', 'auto', phase='zero', fir_window='hamming'))
-    assert_array_equal(x, x_orig)
-    assert_array_equal(x_filt, filter_data(
-        x, sfreq, None, lp, h_trans_bandwidth='auto', phase='zero',
-        fir_window='hamming', filter_length='auto'))
-    assert_array_equal(x, x_orig)
-    assert_array_equal(x_filt, filter_data(
-        x, sfreq, None, lp, h_trans_bandwidth='auto', phase='zero',
-        fir_window='hamming', filter_length='auto', copy=False))
-    assert_array_equal(x, x_filt)
+    for pad in ('reflect_limited', 'reflect', 'edge'):
+        for fir_design in ('firwin2', 'firwin'):
+            kwargs = dict(fir_design=fir_design, pad=pad)
+            x = x_orig.copy()
+            x_filt = filter_data(x, sfreq, None, lp, **kwargs)
+            assert_array_equal(x, x_orig)
+            n_edge = 10
+            assert_allclose(x[n_edge:-n_edge], x_filt[n_edge:-n_edge],
+                            atol=1e-2)
+            assert_array_equal(x_filt, filter_data(x, sfreq, None, lp, None,
+                                                   **kwargs))
+            assert_array_equal(x, x_orig)
+            assert_array_equal(x_filt, filter_data(x, sfreq, None, lp,
+                                                   **kwargs))
+            assert_array_equal(x, x_orig)
+            assert_array_equal(x_filt, filter_data(x, sfreq, None, lp,
+                                                   copy=False, **kwargs))
+            assert_array_equal(x, x_filt)
 
     # degenerate conditions
     assert_raises(ValueError, filter_data, x, -sfreq, 1, 10)
     assert_raises(ValueError, filter_data, x, sfreq, 1, sfreq * 0.75)
     assert_raises(TypeError, filter_data, x.astype(np.float32), sfreq, None,
-                  10, filter_length='auto', h_trans_bandwidth='auto')
+                  10, filter_length='auto', h_trans_bandwidth='auto', **kwargs)
 
 
 def test_cuda():
@@ -432,34 +462,28 @@ def test_cuda():
     sfreq = 500
     sig_len_secs = 20
     a = rng.randn(sig_len_secs * sfreq)
+    kwargs = dict(fir_design='firwin')
 
     with catch_logging() as log_file:
         for fl in ['auto', '10s', 2048]:
-            bp = band_pass_filter(a, sfreq, 4, 8, fl, 1.0, 1.0, n_jobs=1,
-                                  phase='zero', fir_window='hann')
-            bs = band_stop_filter(a, sfreq, 4 - 1.0, 8 + 1.0, fl, 1.0, 1.0,
-                                  n_jobs=1, phase='zero', fir_window='hann')
-            lp = low_pass_filter(a, sfreq, 8, fl, 1.0, n_jobs=1, phase='zero',
-                                 fir_window='hann')
-            hp = high_pass_filter(lp, sfreq, 4, fl, 1.0, n_jobs=1,
-                                  phase='zero', fir_window='hann')
-
-            bp_c = band_pass_filter(a, sfreq, 4, 8, fl, 1.0, 1.0,
-                                    n_jobs='cuda', verbose='INFO',
-                                    phase='zero', fir_window='hann')
-            bs_c = band_stop_filter(a, sfreq, 4 - 1.0, 8 + 1.0, fl, 1.0, 1.0,
-                                    n_jobs='cuda', verbose='INFO',
-                                    phase='zero', fir_window='hann')
-            lp_c = low_pass_filter(a, sfreq, 8, fl, 1.0,
-                                   n_jobs='cuda', verbose='INFO',
-                                   phase='zero', fir_window='hann')
-            hp_c = high_pass_filter(lp, sfreq, 4, fl, 1.0,
-                                    n_jobs='cuda', verbose='INFO',
-                                    phase='zero', fir_window='hann')
-
+            args = [a, sfreq, 4, 8, None, fl, 1.0, 1.0]
+            bp = filter_data(*args, **kwargs)
+            bp_c = filter_data(*args, n_jobs='cuda', verbose='info', **kwargs)
             assert_array_almost_equal(bp, bp_c, 12)
+
+            args = [a, sfreq, 8 + 1.0, 4 - 1.0, None, fl, 1.0, 1.0]
+            bs = filter_data(*args, **kwargs)
+            bs_c = filter_data(*args, n_jobs='cuda', verbose='info', **kwargs)
             assert_array_almost_equal(bs, bs_c, 12)
+
+            args = [a, sfreq, None, 8, None, fl, 1.0]
+            lp = filter_data(*args, **kwargs)
+            lp_c = filter_data(*args, n_jobs='cuda', verbose='info', **kwargs)
             assert_array_almost_equal(lp, lp_c, 12)
+
+            args = [lp, sfreq, 4, None, None, fl, 1.0]
+            hp = filter_data(*args, **kwargs)
+            hp_c = filter_data(*args, n_jobs='cuda', verbose='info', **kwargs)
             assert_array_almost_equal(hp, hp_c, 12)
 
     # check to make sure we actually used CUDA
@@ -487,11 +511,30 @@ def test_cuda():
 
 
 def test_detrend():
-    """Test zeroth and first order detrending"""
+    """Test zeroth and first order detrending."""
     x = np.arange(10)
     assert_array_almost_equal(detrend(x, 1), np.zeros_like(x))
     x = np.ones(10)
     assert_array_almost_equal(detrend(x, 0), np.zeros_like(x))
 
 
+def test_interp2():
+    """Test our two-point interpolator."""
+    interp = _Interp2('zero')
+    x = np.ones((1, 100))
+    interp['y'] = np.array([[10.]])
+    interp['y'] = np.array([[-10]])
+    interp.n_samp = 100
+    out = np.zeros_like(x)
+    interp.interpolate('y', x, out)
+    expected = 10 * x
+    assert_allclose(out, expected, atol=1e-7)
+    # Linear
+    interp.interp = 'linear'
+    out.fill(0.)
+    interp.interpolate('y', x, out)
+    expected = np.linspace(10, -10, 100, endpoint=False)[np.newaxis]
+    assert_allclose(out, expected, atol=1e-7)
+
+
 run_tests_if_main()
diff --git a/mne/tests/test_fixes.py b/mne/tests/test_fixes.py
index 65e971b..bbe3781 100644
--- a/mne/tests/test_fixes.py
+++ b/mne/tests/test_fixes.py
@@ -4,12 +4,14 @@
 # License: BSD
 
 import numpy as np
+from numpy.testing import assert_allclose
 from scipy.signal import filtfilt
+from scipy.special import sph_harm
 
 from numpy.testing import assert_array_equal
 
-from mne.utils import run_tests_if_main
-from mne.fixes import _sosfiltfilt as mne_sosfiltfilt
+from mne.utils import run_tests_if_main, requires_version
+from mne.fixes import _sosfiltfilt as mne_sosfiltfilt, _sph_harm
 
 
 def test_filtfilt():
@@ -21,4 +23,17 @@ def test_filtfilt():
     y = mne_sosfiltfilt(np.array([[1., 0., 0., 1, 0., 0.]]), x, padlen=0)
     assert_array_equal(x, y)
 
+
+ at requires_version('scipy', '0.17.1')
+def test_spherical_harmonics():
+    """Test spherical harmonic functions."""
+    az, pol = np.meshgrid(np.linspace(0, 2 * np.pi, 30),
+                          np.linspace(0, np.pi, 20))
+    # Test our basic spherical harmonics
+    for degree in range(1, 10):
+        for order in range(0, degree + 1):
+            sph = _sph_harm(order, degree, az, pol)
+            sph_scipy = sph_harm(order, degree, az, pol)
+            assert_allclose(sph, sph_scipy, atol=1e-7)
+
 run_tests_if_main()
diff --git a/mne/tests/test_import_nesting.py b/mne/tests/test_import_nesting.py
index cc4c264..b6f7e67 100644
--- a/mne/tests/test_import_nesting.py
+++ b/mne/tests/test_import_nesting.py
@@ -18,7 +18,8 @@ ok_scipy_submodules = set(['scipy', 'numpy',  # these appear in old scipy
                            'misc', 'sparse', 'version'])
 scipy_submodules = set(x.split('.')[1] for x in sys.modules.keys()
                        if x.startswith('scipy.') and '__' not in x and
-                       not x.split('.')[1].startswith('_'))
+                       not x.split('.')[1].startswith('_')
+                       and sys.modules[x] is not None)
 bad = scipy_submodules - ok_scipy_submodules
 if len(bad) > 0:
     out.append('Found un-nested scipy submodules: %s' % list(bad))
@@ -53,4 +54,5 @@ def test_module_nesting():
     if proc.returncode:
         raise AssertionError(stdout)
 
+
 run_tests_if_main()
diff --git a/mne/tests/test_label.py b/mne/tests/test_label.py
index c41acff..297d291 100644
--- a/mne/tests/test_label.py
+++ b/mne/tests/test_label.py
@@ -9,6 +9,7 @@ from scipy import sparse
 
 from numpy.testing import assert_array_equal, assert_array_almost_equal
 from nose.tools import assert_equal, assert_true, assert_false, assert_raises
+import pytest
 
 from mne.datasets import testing
 from mne import (read_label, stc_to_label, read_source_estimate,
@@ -17,7 +18,7 @@ from mne import (read_label, stc_to_label, read_source_estimate,
                  read_surface)
 from mne.label import Label, _blend_colors, label_sign_flip
 from mne.utils import (_TempDir, requires_sklearn, get_subjects_dir,
-                       run_tests_if_main, slow_test)
+                       run_tests_if_main)
 from mne.fixes import assert_is, assert_is_not
 from mne.label import _n_colors
 from mne.source_space import SourceSpaces
@@ -166,7 +167,7 @@ def assert_labels_equal(l0, l1, decimal=5, comment=True, color=True):
 
 
 def test_copy():
-    """Test label copying"""
+    """Test label copying."""
     label = read_label(label_fname)
     label_2 = label.copy()
     label_2.pos += 1
@@ -174,8 +175,7 @@ def test_copy():
 
 
 def test_label_subject():
-    """Test label subject name extraction
-    """
+    """Test label subject name extraction."""
     label = read_label(label_fname)
     assert_is(label.subject, None)
     assert_true('unknown' in repr(label))
@@ -185,8 +185,7 @@ def test_label_subject():
 
 
 def test_label_addition():
-    """Test label addition
-    """
+    """Test label addition."""
     pos = np.random.RandomState(0).rand(10, 3)
     values = np.arange(10.) / 10
     idx0 = list(range(7))
@@ -228,7 +227,6 @@ def test_label_addition():
 
     # adding lh and rh
     l2.hemi = 'rh'
-    # this now has deprecated behavior
     bhl = l0 + l2
     assert_equal(bhl.hemi, 'both')
     assert_equal(len(bhl), len(l0) + len(l2))
@@ -257,7 +255,7 @@ def test_label_addition():
 
 @testing.requires_testing_data
 def test_label_in_src():
-    """Test label in src"""
+    """Test label in src."""
     src = read_source_spaces(src_fname)
     label = read_label(v1_label_fname)
 
@@ -284,11 +282,15 @@ def test_label_in_src():
     vertices = np.append([-1], vert_in_src)
     assert_raises(ValueError, Label(vertices, hemi='lh').fill, src)
 
+    # test filling empty label
+    label = Label([], hemi='lh')
+    label.fill(src)
+    assert_array_equal(label.vertices, np.array([], int))
+
 
 @testing.requires_testing_data
 def test_label_io_and_time_course_estimates():
-    """Test IO for label + stc files
-    """
+    """Test IO for label + stc files."""
     stc = read_source_estimate(stc_fname)
     label = read_label(real_label_fname)
     stc_label = stc.in_label(label)
@@ -299,8 +301,7 @@ def test_label_io_and_time_course_estimates():
 
 @testing.requires_testing_data
 def test_label_io():
-    """Test IO of label files
-    """
+    """Test IO of label files."""
     tempdir = _TempDir()
     label = read_label(label_fname)
 
@@ -335,7 +336,7 @@ def _assert_labels_equal(labels_a, labels_b, ignore_pos=False):
 
 @testing.requires_testing_data
 def test_annot_io():
-    """Test I/O from and to *.annot files"""
+    """Test I/O from and to *.annot files."""
     # copy necessary files from fsaverage to tempdir
     tempdir = _TempDir()
     subject = 'fsaverage'
@@ -382,8 +383,7 @@ def test_annot_io():
 
 @testing.requires_testing_data
 def test_read_labels_from_annot():
-    """Test reading labels from FreeSurfer parcellation
-    """
+    """Test reading labels from FreeSurfer parcellation."""
     # test some invalid inputs
     assert_raises(ValueError, read_labels_from_annot, 'sample', hemi='bla',
                   subjects_dir=subjects_dir)
@@ -440,8 +440,7 @@ def test_read_labels_from_annot():
 
 @testing.requires_testing_data
 def test_read_labels_from_annot_annot2labels():
-    """Test reading labels from parc. by comparing with mne_annot2labels
-    """
+    """Test reading labels from parc. by comparing with mne_annot2labels."""
     label_fnames = glob.glob(label_dir + '/*.label')
     label_fnames.sort()
     labels_mne = [read_label(fname) for fname in label_fnames]
@@ -453,7 +452,7 @@ def test_read_labels_from_annot_annot2labels():
 
 @testing.requires_testing_data
 def test_write_labels_to_annot():
-    """Test writing FreeSurfer parcellation from labels"""
+    """Test writing FreeSurfer parcellation from labels."""
     tempdir = _TempDir()
 
     labels = read_labels_from_annot('sample', subjects_dir=subjects_dir)
@@ -580,7 +579,7 @@ def test_write_labels_to_annot():
 @requires_sklearn
 @testing.requires_testing_data
 def test_split_label():
-    """Test splitting labels"""
+    """Test splitting labels."""
     aparc = read_labels_from_annot('fsaverage', 'aparc', 'lh',
                                    regexp='lingual', subjects_dir=subjects_dir)
     lingual = aparc[0]
@@ -630,12 +629,11 @@ def test_split_label():
                  [16181, 7022, 5965, 5300, 823] + [1] * 23)
 
 
- at slow_test
+ at pytest.mark.slowtest
 @testing.requires_testing_data
 @requires_sklearn
 def test_stc_to_label():
-    """Test stc_to_label
-    """
+    """Test stc_to_label."""
     with warnings.catch_warnings(record=True) as w:
         warnings.simplefilter('always')
         src = read_source_spaces(fwd_fname)
@@ -693,11 +691,10 @@ def test_stc_to_label():
         assert_labels_equal(l1, l2, decimal=4)
 
 
- at slow_test
+ at pytest.mark.slowtest
 @testing.requires_testing_data
 def test_morph():
-    """Test inter-subject label morphing
-    """
+    """Test inter-subject label morphing."""
     label_orig = read_label(real_label_fname)
     label_orig.subject = 'sample'
     # should work for specifying vertices for both hemis, or just the
@@ -719,7 +716,8 @@ def test_morph():
     verts = [np.arange(10242), np.arange(10242)]
     for hemi in ['lh', 'rh']:
         label.hemi = hemi
-        label.morph(None, 'fsaverage', 5, verts, subjects_dir, 2)
+        with warnings.catch_warnings(record=True):  # morph map maybe missing
+            label.morph(None, 'fsaverage', 5, verts, subjects_dir, 2)
     assert_raises(TypeError, label.morph, None, 1, 5, verts,
                   subjects_dir, 2)
     assert_raises(TypeError, label.morph, None, 'fsaverage', 5.5, verts,
@@ -730,7 +728,7 @@ def test_morph():
 
 @testing.requires_testing_data
 def test_grow_labels():
-    """Test generation of circular source labels"""
+    """Test generation of circular source labels."""
     seeds = [0, 50000]
     # these were chosen manually in mne_analyze
     should_be_in = [[49, 227], [51207, 48794]]
@@ -772,7 +770,7 @@ def test_grow_labels():
 
 @testing.requires_testing_data
 def test_label_sign_flip():
-    """Test label sign flip computation"""
+    """Test label sign flip computation."""
     src = read_source_spaces(src_fname)
     label = Label(vertices=src[0]['vertno'][:5], hemi='lh')
     src[0]['nn'][label.vertices] = np.array(
@@ -784,14 +782,21 @@ def test_label_sign_flip():
     known_flips = np.array([1, 1, np.nan, 1, 1])
     idx = [0, 1, 3, 4]  # indices that are usable (third row is orthognoal)
     flip = label_sign_flip(label, src)
-    # Need the abs here because the direction is arbitrary
-    assert_array_almost_equal(np.abs(np.dot(flip[idx], known_flips[idx])),
-                              len(idx))
+    assert_array_almost_equal(np.dot(flip[idx], known_flips[idx]), len(idx))
+    bi_label = label + Label(vertices=src[1]['vertno'][:5], hemi='rh')
+    src[1]['nn'][src[1]['vertno'][:5]] = -src[0]['nn'][label.vertices]
+    flip = label_sign_flip(bi_label, src)
+    known_flips = np.array([1, 1, np.nan, 1, 1, 1, 1, np.nan, 1, 1])
+    idx = [0, 1, 3, 4, 5, 6, 8, 9]
+    assert_array_almost_equal(np.dot(flip[idx], known_flips[idx]), 0.)
+    src[1]['nn'][src[1]['vertno'][:5]] *= -1
+    flip = label_sign_flip(bi_label, src)
+    assert_array_almost_equal(np.dot(flip[idx], known_flips[idx]), len(idx))
 
 
 @testing.requires_testing_data
 def test_label_center_of_mass():
-    """Test computing the center of mass of a label"""
+    """Test computing the center of mass of a label."""
     stc = read_source_estimate(stc_fname)
     stc.lh_data[:] = 0
     vertex_stc = stc.center_of_mass('sample', subjects_dir=subjects_dir)[0]
@@ -811,6 +816,9 @@ def test_label_center_of_mass():
         label.values[:] = -1
         assert_raises(ValueError, label.center_of_mass,
                       subjects_dir=subjects_dir)
+        label.values[:] = 0
+        assert_raises(ValueError, label.center_of_mass,
+                      subjects_dir=subjects_dir)
         label.values[:] = 1
         assert_equal(label.center_of_mass(subjects_dir=subjects_dir), expected)
         assert_equal(label.center_of_mass(subjects_dir=subjects_dir,
@@ -840,4 +848,5 @@ def test_label_center_of_mass():
     assert_raises(IOError, label.center_of_mass, subjects_dir=subjects_dir,
                   surf='foo')
 
+
 run_tests_if_main()
diff --git a/mne/tests/test_line_endings.py b/mne/tests/test_line_endings.py
index 41d604b..fb16c18 100644
--- a/mne/tests/test_line_endings.py
+++ b/mne/tests/test_line_endings.py
@@ -5,7 +5,7 @@
 
 import os
 from nose.tools import assert_raises
-from nose.plugins.skip import SkipTest
+from unittest import SkipTest
 from os import path as op
 import sys
 
@@ -21,7 +21,8 @@ skip_files = (
     # part of testing compatibility with older BV formats is testing
     # the line endings and coding schemes used there
     'test_old_layout_latin1_software_filter.vhdr',
-    'test_old_layout_latin1_software_filter.vmrk'
+    'test_old_layout_latin1_software_filter.vmrk',
+    'searchindex.dat',
 )
 
 
@@ -69,4 +70,5 @@ def test_line_endings():
     # now check mne
     _assert_line_endings(_get_root_dir())
 
+
 run_tests_if_main()
diff --git a/mne/tests/test_proj.py b/mne/tests/test_proj.py
index a91cf25..1bd56a4 100644
--- a/mne/tests/test_proj.py
+++ b/mne/tests/test_proj.py
@@ -5,21 +5,22 @@ import warnings
 import numpy as np
 from numpy.testing import (assert_array_almost_equal, assert_allclose,
                            assert_equal)
+import pytest
 
 import copy as cp
 
 import mne
 from mne.datasets import testing
-from mne import pick_types
 from mne.io import read_raw_fif
-from mne import compute_proj_epochs, compute_proj_evoked, compute_proj_raw
+from mne import (compute_proj_epochs, compute_proj_evoked, compute_proj_raw,
+                 pick_types, read_events, Epochs, sensitivity_map,
+                 read_source_estimate)
 from mne.io.proj import (make_projector, activate_proj,
                          _needs_eeg_average_ref_proj)
 from mne.proj import (read_proj, write_proj, make_eeg_average_ref_proj,
                       _has_eeg_average_ref_proj)
-from mne import read_events, Epochs, sensitivity_map, read_source_estimate
 from mne.tests.common import assert_naming
-from mne.utils import _TempDir, run_tests_if_main, slow_test
+from mne.utils import _TempDir, run_tests_if_main
 
 warnings.simplefilter('always')  # enable b/c these tests throw warnings
 
@@ -41,7 +42,7 @@ ecg_fname = op.join(sample_path, 'sample_audvis_ecg-proj.fif')
 
 def test_bad_proj():
     """Test dealing with bad projection application."""
-    raw = read_raw_fif(raw_fname, preload=True, add_eeg_ref=False)
+    raw = read_raw_fif(raw_fname, preload=True)
     events = read_events(event_fname)
     picks = pick_types(raw.info, meg=True, stim=False, ecg=False,
                        eog=False, exclude='bads')
@@ -49,19 +50,51 @@ def test_bad_proj():
     _check_warnings(raw, events, picks)
     # still bad
     raw.pick_channels([raw.ch_names[ii] for ii in picks])
-    _check_warnings(raw, events, np.arange(len(raw.ch_names)))
+    _check_warnings(raw, events)
     # "fixed"
     raw.info.normalize_proj()  # avoid projection warnings
-    _check_warnings(raw, events, np.arange(len(raw.ch_names)), count=0)
+    _check_warnings(raw, events, count=0)
+    # eeg avg ref is okay
+    raw = read_raw_fif(raw_fname, preload=True).pick_types(meg=False, eeg=True)
+    raw.set_eeg_reference(projection=True)
+    _check_warnings(raw, events, count=0)
+    raw.info['bads'] = raw.ch_names[:10]
+    _check_warnings(raw, events, count=0)
+
+    raw = read_raw_fif(raw_fname)
+    assert_raises(ValueError, raw.del_proj, 'foo')
+    n_proj = len(raw.info['projs'])
+    raw.del_proj(0)
+    assert_equal(len(raw.info['projs']), n_proj - 1)
+    raw.del_proj()
+    assert_equal(len(raw.info['projs']), 0)
+
+    # Ensure we deal with newer-style Neuromag projs properly, were getting:
+    #
+    #     Projection vector "PCA-v2" has magnitude 1.00 (should be unity),
+    #     applying projector with 101/306 of the original channels available
+    #     may be dangerous.
+    raw = read_raw_fif(raw_fname).crop(0, 1)
+    raw.info['bads'] = ['MEG 0111']
+    meg_picks = mne.pick_types(raw.info, meg=True, exclude=())
+    ch_names = [raw.ch_names[pick] for pick in meg_picks]
+    for p in raw.info['projs'][:-1]:
+        data = np.zeros((1, len(ch_names)))
+        idx = [ch_names.index(ch_name) for ch_name in p['data']['col_names']]
+        data[:, idx] = p['data']['data']
+        p['data'].update(ncol=len(meg_picks), col_names=ch_names, data=data)
+    with warnings.catch_warnings(record=True) as w:
+        mne.cov.regularize(mne.compute_raw_covariance(raw, verbose='error'),
+                           raw.info)
+    assert_equal(len(w), 0)
 
 
-def _check_warnings(raw, events, picks, count=3):
+def _check_warnings(raw, events, picks=None, count=3):
     """Helper to count warnings."""
     with warnings.catch_warnings(record=True) as w:
         warnings.simplefilter('always')
         Epochs(raw, events, dict(aud_l=1, vis_l=3),
-               -0.2, 0.5, picks=picks, preload=True, proj=True,
-               add_eeg_ref=False)
+               -0.2, 0.5, picks=picks, preload=True, proj=True)
     assert_equal(len(w), count)
     for ww in w:
         assert_true('dangerous' in str(ww.message))
@@ -70,7 +103,8 @@ def _check_warnings(raw, events, picks, count=3):
 @testing.requires_testing_data
 def test_sensitivity_maps():
     """Test sensitivity map computation."""
-    fwd = mne.read_forward_solution(fwd_fname, surf_ori=True)
+    fwd = mne.read_forward_solution(fwd_fname)
+    fwd = mne.convert_forward_solution(fwd, surf_ori=True)
     with warnings.catch_warnings(record=True) as w:
         warnings.simplefilter('always')
         projs = read_proj(eog_fname)
@@ -122,13 +156,13 @@ def test_compute_proj_epochs():
     tempdir = _TempDir()
     event_id, tmin, tmax = 1, -0.2, 0.3
 
-    raw = read_raw_fif(raw_fname, preload=True, add_eeg_ref=False)
+    raw = read_raw_fif(raw_fname, preload=True)
     events = read_events(event_fname)
     bad_ch = 'MEG 2443'
     picks = pick_types(raw.info, meg=True, eeg=False, stim=False, eog=False,
                        exclude=[])
     epochs = Epochs(raw, events, event_id, tmin, tmax, picks=picks,
-                    baseline=None, proj=False, add_eeg_ref=False)
+                    baseline=None, proj=False)
 
     evoked = epochs.average()
     projs = compute_proj_epochs(epochs, n_grad=1, n_mag=1, n_eeg=0, n_jobs=1)
@@ -177,7 +211,7 @@ def test_compute_proj_epochs():
     # XXX : test something
 
     # test parallelization
-    projs = compute_proj_epochs(epochs, n_grad=1, n_mag=1, n_eeg=0, n_jobs=2,
+    projs = compute_proj_epochs(epochs, n_grad=1, n_mag=1, n_eeg=0, n_jobs=1,
                                 desc_prefix='foobar')
     assert_true(all('foobar' in x['desc'] for x in projs))
     projs = activate_proj(projs)
@@ -193,13 +227,13 @@ def test_compute_proj_epochs():
     assert_naming(w, 'test_proj.py', 2)
 
 
- at slow_test
+ at pytest.mark.slowtest
 def test_compute_proj_raw():
     """Test SSP computation on raw"""
     tempdir = _TempDir()
     # Test that the raw projectors work
     raw_time = 2.5  # Do shorter amount for speed
-    raw = read_raw_fif(raw_fname, add_eeg_ref=False).crop(0, raw_time)
+    raw = read_raw_fif(raw_fname).crop(0, raw_time)
     raw.load_data()
     for ii in (0.25, 0.5, 1, 2):
         with warnings.catch_warnings(record=True) as w:
@@ -263,7 +297,7 @@ def test_compute_proj_raw():
 
 def test_make_eeg_average_ref_proj():
     """Test EEG average reference projection."""
-    raw = read_raw_fif(raw_fname, add_eeg_ref=False, preload=True)
+    raw = read_raw_fif(raw_fname, preload=True)
     eeg = mne.pick_types(raw.info, meg=False, eeg=True)
 
     # No average EEG reference
@@ -285,28 +319,29 @@ def test_has_eeg_average_ref_proj():
     """Test checking whether an EEG average reference exists"""
     assert_true(not _has_eeg_average_ref_proj([]))
 
-    raw = read_raw_fif(raw_fname, add_eeg_ref=False, preload=False)
-    raw.set_eeg_reference()
+    raw = read_raw_fif(raw_fname)
+    raw.set_eeg_reference(projection=True)
     assert_true(_has_eeg_average_ref_proj(raw.info['projs']))
 
 
 def test_needs_eeg_average_ref_proj():
     """Test checking whether a recording needs an EEG average reference"""
-    raw = read_raw_fif(raw_fname, add_eeg_ref=False, preload=False)
+    raw = read_raw_fif(raw_fname)
     assert_true(_needs_eeg_average_ref_proj(raw.info))
 
-    raw.set_eeg_reference()
+    raw.set_eeg_reference(projection=True)
     assert_true(not _needs_eeg_average_ref_proj(raw.info))
 
     # No EEG channels
-    raw = read_raw_fif(raw_fname, add_eeg_ref=False, preload=True)
+    raw = read_raw_fif(raw_fname, preload=True)
     eeg = [raw.ch_names[c] for c in pick_types(raw.info, meg=False, eeg=True)]
     raw.drop_channels(eeg)
     assert_true(not _needs_eeg_average_ref_proj(raw.info))
 
     # Custom ref flag set
-    raw = read_raw_fif(raw_fname, add_eeg_ref=False, preload=False)
+    raw = read_raw_fif(raw_fname)
     raw.info['custom_ref_applied'] = True
     assert_true(not _needs_eeg_average_ref_proj(raw.info))
 
+
 run_tests_if_main()
diff --git a/mne/tests/test_report.py b/mne/tests/test_report.py
index d13be10..2d5bc9d 100644
--- a/mne/tests/test_report.py
+++ b/mne/tests/test_report.py
@@ -7,19 +7,18 @@ import glob
 import os
 import os.path as op
 import shutil
-import sys
 import warnings
 
 from nose.tools import assert_true, assert_equal, assert_raises
-from nose.plugins.skip import SkipTest
+import pytest
 
-from mne import Epochs, read_events, pick_types, read_evokeds
+from mne import Epochs, read_events, read_evokeds
 from mne.io import read_raw_fif
 from mne.datasets import testing
 from mne.report import Report
 from mne.utils import (_TempDir, requires_mayavi, requires_nibabel,
-                       requires_PIL, run_tests_if_main, slow_test)
-from mne.viz import plot_trans
+                       requires_PIL, run_tests_if_main)
+from mne.viz import plot_alignment
 
 import matplotlib
 matplotlib.use('Agg')  # for testing don't use X server
@@ -45,7 +44,7 @@ evoked_fname = op.join(base_dir, 'test-ave.fif')
 warnings.simplefilter('always')  # enable b/c these tests throw warnings
 
 
- at slow_test
+ at pytest.mark.slowtest
 @testing.requires_testing_data
 @requires_PIL
 def test_render_report():
@@ -66,16 +65,17 @@ def test_render_report():
     # create and add -epo.fif and -ave.fif files
     epochs_fname = op.join(tempdir, 'temp-epo.fif')
     evoked_fname = op.join(tempdir, 'temp-ave.fif')
-    raw = read_raw_fif(raw_fname_new, add_eeg_ref=False)
-    picks = pick_types(raw.info, meg='mag', eeg=False)  # faster with one type
-    epochs = Epochs(raw, read_events(event_fname), 1, -0.2, 0.2, picks=picks,
-                    add_eeg_ref=False)
+    # Speed it up by picking channels
+    raw = read_raw_fif(raw_fname_new, preload=True)
+    raw.pick_channels(['MEG 0111', 'MEG 0121'])
+    raw.del_proj()
+    epochs = Epochs(raw, read_events(event_fname), 1, -0.2, 0.2)
     epochs.save(epochs_fname)
-    epochs.average().save(evoked_fname)
+    # This can take forever (stall Travis), so let's make it fast
+    # Also, make sure crop range is wide enough to avoid rendering bug
+    epochs.average().crop(0.1, 0.2).save(evoked_fname)
 
     report = Report(info_fname=raw_fname_new, subjects_dir=subjects_dir)
-    if sys.version.startswith('3.5'):  # XXX Some strange MPL/3.5 error...
-        raise SkipTest('Python 3.5 and mpl have unresolved issues')
     with warnings.catch_warnings(record=True) as w:
         warnings.simplefilter('always')
         report.parse_folder(data_path=tempdir, on_error='raise')
@@ -125,6 +125,16 @@ def test_render_report():
                     [op.basename(x) for x in report.fnames])
         assert_true(''.join(report.html).find(op.basename(fname)) != -1)
 
+    assert_raises(ValueError, Report, image_format='foo')
+    assert_raises(ValueError, Report, image_format=None)
+
+    # SVG rendering
+    report = Report(info_fname=raw_fname_new, subjects_dir=subjects_dir,
+                    image_format='svg')
+    with warnings.catch_warnings(record=True) as w:
+        warnings.simplefilter('always')
+        report.parse_folder(data_path=tempdir, on_error='raise')
+
 
 @testing.requires_testing_data
 @requires_mayavi
@@ -169,15 +179,15 @@ def test_render_add_sections():
 
     evoked = read_evokeds(evoked_fname, condition='Left Auditory',
                           baseline=(-0.2, 0.0))
-    fig = plot_trans(evoked.info, trans_fname, subject='sample',
-                     subjects_dir=subjects_dir)
+    fig = plot_alignment(evoked.info, trans_fname, subject='sample',
+                         subjects_dir=subjects_dir)
 
     report.add_figs_to_section(figs=fig,  # test non-list input
                                captions='random image', scale=1.2)
     assert_true(repr(report))
 
 
- at slow_test
+ at pytest.mark.slowtest
 @testing.requires_testing_data
 @requires_mayavi
 @requires_nibabel()
diff --git a/mne/tests/test_selection.py b/mne/tests/test_selection.py
index f9b75e3..ed91c32 100644
--- a/mne/tests/test_selection.py
+++ b/mne/tests/test_selection.py
@@ -12,7 +12,7 @@ raw_new_fname = op.join(test_path, 'test_chpi_raw_sss.fif')
 
 
 def test_read_selection():
-    """Test reading of selections"""
+    """Test reading of selections."""
     # test one channel for each selection
     ch_names = ['MEG 2211', 'MEG 0223', 'MEG 1312', 'MEG 0412', 'MEG 1043',
                 'MEG 2042', 'MEG 2032', 'MEG 0522', 'MEG 1031']
@@ -20,7 +20,7 @@ def test_read_selection():
                  'Right-parietal', 'Left-occipital', 'Right-occipital',
                  'Left-frontal', 'Right-frontal']
 
-    raw = read_raw_fif(raw_fname, add_eeg_ref=False)
+    raw = read_raw_fif(raw_fname)
     for i, name in enumerate(sel_names):
         sel = read_selection(name)
         assert_true(ch_names[i] in sel)
@@ -40,7 +40,7 @@ def test_read_selection():
     assert_true(len(set(frontal).intersection(set(occipital))) == 0)
 
     ch_names_new = [ch.replace(' ', '') for ch in ch_names]
-    raw_new = read_raw_fif(raw_new_fname, add_eeg_ref=False)
+    raw_new = read_raw_fif(raw_new_fname)
     for i, name in enumerate(sel_names):
         sel = read_selection(name, info=raw_new.info)
         assert_true(ch_names_new[i] in sel)
diff --git a/mne/tests/test_source_estimate.py b/mne/tests/test_source_estimate.py
index d6a907c..a3824cb 100644
--- a/mne/tests/test_source_estimate.py
+++ b/mne/tests/test_source_estimate.py
@@ -7,23 +7,27 @@ from copy import deepcopy
 import numpy as np
 from numpy.testing import (assert_array_almost_equal, assert_array_equal,
                            assert_allclose, assert_equal)
-
+import pytest
 from scipy.fftpack import fft
 
 from mne.datasets import testing
-from mne import (stats, SourceEstimate, VolSourceEstimate, Label,
-                 read_source_spaces, MixedSourceEstimate, read_source_estimate,
-                 morph_data, extract_label_time_course,
+from mne import (stats, SourceEstimate, VectorSourceEstimate,
+                 VolSourceEstimate, Label, read_source_spaces,
+                 read_evokeds, MixedSourceEstimate, find_events, Epochs,
+                 read_source_estimate, morph_data, extract_label_time_course,
                  spatio_temporal_tris_connectivity,
                  spatio_temporal_src_connectivity,
-                 spatial_inter_hemi_connectivity)
+                 spatial_inter_hemi_connectivity,
+                 spatial_src_connectivity)
 from mne.source_estimate import (compute_morph_matrix, grade_to_vertices,
-                                 grade_to_tris)
+                                 grade_to_tris, _get_vol_mask)
 
-from mne.minimum_norm import read_inverse_operator
+from mne.minimum_norm import (read_inverse_operator, apply_inverse,
+                              apply_inverse_epochs)
 from mne.label import read_labels_from_annot, label_sign_flip
 from mne.utils import (_TempDir, requires_pandas, requires_sklearn,
-                       requires_h5py, run_tests_if_main, slow_test)
+                       requires_h5py, run_tests_if_main, requires_nibabel)
+from mne.io import read_raw_fif
 
 warnings.simplefilter('always')  # enable b/c these tests throw warnings
 
@@ -31,6 +35,9 @@ data_path = testing.data_path(download=False)
 subjects_dir = op.join(data_path, 'subjects')
 fname_inv = op.join(data_path, 'MEG', 'sample',
                     'sample_audvis_trunc-meg-eeg-oct-6-meg-inv.fif')
+fname_evoked = op.join(data_path, 'MEG', 'sample',
+                       'sample_audvis_trunc-ave.fif')
+fname_raw = op.join(data_path, 'MEG', 'sample', 'sample_audvis_trunc_raw.fif')
 fname_t1 = op.join(data_path, 'subjects', 'sample', 'mri', 'T1.mgz')
 fname_src = op.join(data_path, 'MEG', 'sample',
                     'sample_audvis_trunc-meg-eeg-oct-6-fwd.fif')
@@ -50,7 +57,7 @@ rng = np.random.RandomState(0)
 
 @testing.requires_testing_data
 def test_spatial_inter_hemi_connectivity():
-    """Test spatial connectivity between hemispheres"""
+    """Test spatial connectivity between hemispheres."""
     # trivial cases
     conn = spatial_inter_hemi_connectivity(fname_src_3, 5e-6)
     assert_equal(conn.data.size, 0)
@@ -82,11 +89,10 @@ def test_spatial_inter_hemi_connectivity():
         assert_true(set(use_labels) - set(good_labels) == set())
 
 
- at slow_test
+ at pytest.mark.slowtest
 @testing.requires_testing_data
 def test_volume_stc():
-    """Test volume STCs
-    """
+    """Test volume STCs."""
     tempdir = _TempDir()
     N = 100
     data = np.arange(N)[:, np.newaxis]
@@ -153,22 +159,27 @@ def test_volume_stc():
 
 @testing.requires_testing_data
 def test_expand():
-    """Test stc expansion
-    """
-    stc = read_source_estimate(fname_stc, 'sample')
-    assert_true('sample' in repr(stc))
-    labels_lh = read_labels_from_annot('sample', 'aparc', 'lh',
-                                       subjects_dir=subjects_dir)
-    new_label = labels_lh[0] + labels_lh[1]
-    stc_limited = stc.in_label(new_label)
-    stc_new = stc_limited.copy()
-    stc_new.data.fill(0)
-    for label in labels_lh[:2]:
-        stc_new += stc.in_label(label).expand(stc_limited.vertices)
-    assert_raises(TypeError, stc_new.expand, stc_limited.vertices[0])
-    assert_raises(ValueError, stc_new.expand, [stc_limited.vertices[0]])
-    # make sure we can't add unless vertno agree
-    assert_raises(ValueError, stc.__add__, stc.in_label(labels_lh[0]))
+    """Test stc expansion."""
+    stc_ = read_source_estimate(fname_stc, 'sample')
+    vec_stc_ = VectorSourceEstimate(np.zeros((stc_.data.shape[0], 3,
+                                              stc_.data.shape[1])),
+                                    stc_.vertices, stc_.tmin, stc_.tstep,
+                                    stc_.subject)
+
+    for stc in [stc_, vec_stc_]:
+        assert_true('sample' in repr(stc))
+        labels_lh = read_labels_from_annot('sample', 'aparc', 'lh',
+                                           subjects_dir=subjects_dir)
+        new_label = labels_lh[0] + labels_lh[1]
+        stc_limited = stc.in_label(new_label)
+        stc_new = stc_limited.copy()
+        stc_new.data.fill(0)
+        for label in labels_lh[:2]:
+            stc_new += stc.in_label(label).expand(stc_limited.vertices)
+        assert_raises(TypeError, stc_new.expand, stc_limited.vertices[0])
+        assert_raises(ValueError, stc_new.expand, [stc_limited.vertices[0]])
+        # make sure we can't add unless vertno agree
+        assert_raises(ValueError, stc.__add__, stc.in_label(labels_lh[0]))
 
 
 def _fake_stc(n_time=10):
@@ -176,9 +187,84 @@ def _fake_stc(n_time=10):
     return SourceEstimate(np.random.rand(100, n_time), verts, 0, 1e-1, 'foo')
 
 
+def _fake_vec_stc(n_time=10):
+    verts = [np.arange(10), np.arange(90)]
+    return VectorSourceEstimate(np.random.rand(100, 3, n_time), verts, 0, 1e-1,
+                                'foo')
+
+
+def _real_vec_stc():
+    inv = read_inverse_operator(fname_inv)
+    evoked = read_evokeds(fname_evoked, baseline=(None, 0))[0].crop(0, 0.01)
+    return apply_inverse(evoked, inv, pick_ori='vector')
+
+
+def _test_stc_integrety(stc):
+    """Test consistency of tmin, tstep, data.shape[-1] and times."""
+    n_times = len(stc.times)
+    assert_equal(stc._data.shape[-1], n_times)
+    assert_array_equal(stc.times, stc.tmin + np.arange(n_times) * stc.tstep)
+
+
+def test_stc_attributes():
+    """Test STC attributes."""
+    stc = _fake_stc(n_time=10)
+    vec_stc = _fake_vec_stc(n_time=10)
+
+    _test_stc_integrety(stc)
+    assert_array_almost_equal(
+        stc.times, [0., 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9])
+
+    def attempt_times_mutation(stc):
+        stc.times -= 1
+
+    def attempt_assignment(stc, attr, val):
+        setattr(stc, attr, val)
+
+    # .times is read-only
+    assert_raises(ValueError, attempt_times_mutation, stc)
+    assert_raises(ValueError, attempt_assignment, stc, 'times', [1])
+
+    # Changing .tmin or .tstep re-computes .times
+    stc.tmin = 1
+    assert_true(type(stc.tmin) == float)
+    assert_array_almost_equal(
+        stc.times, [1., 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9])
+
+    stc.tstep = 1
+    assert_true(type(stc.tstep) == float)
+    assert_array_almost_equal(
+        stc.times, [1., 2., 3., 4., 5., 6., 7., 8., 9., 10.])
+
+    # tstep <= 0 is not allowed
+    assert_raises(ValueError, attempt_assignment, stc, 'tstep', 0)
+    assert_raises(ValueError, attempt_assignment, stc, 'tstep', -1)
+
+    # Changing .data re-computes .times
+    stc.data = np.random.rand(100, 5)
+    assert_array_almost_equal(
+        stc.times, [1., 2., 3., 4., 5.])
+
+    # .data must match the number of vertices
+    assert_raises(ValueError, attempt_assignment, stc, 'data', [[1]])
+    assert_raises(ValueError, attempt_assignment, stc, 'data', None)
+
+    # .data much match number of dimensions
+    assert_raises(ValueError, attempt_assignment, stc, 'data', np.arange(100))
+    assert_raises(ValueError, attempt_assignment, vec_stc, 'data',
+                  [np.arange(100)])
+    assert_raises(ValueError, attempt_assignment, vec_stc, 'data',
+                  [[[np.arange(100)]]])
+
+    # .shape attribute must also work when ._data is None
+    stc._kernel = np.zeros((2, 2))
+    stc._sens_data = np.zeros((2, 3))
+    stc._data = None
+    assert_equal(stc.shape, (2, 3))
+
+
 def test_io_stc():
-    """Test IO for STC files
-    """
+    """Test IO for STC files."""
     tempdir = _TempDir()
     stc = _fake_stc()
     stc.save(op.join(tempdir, "tmp.stc"))
@@ -194,29 +280,31 @@ def test_io_stc():
 
 @requires_h5py
 def test_io_stc_h5():
-    """Test IO for STC files using HDF5
-    """
-    tempdir = _TempDir()
-    stc = _fake_stc()
-    assert_raises(ValueError, stc.save, op.join(tempdir, 'tmp'), ftype='foo')
-    out_name = op.join(tempdir, 'tmp')
-    stc.save(out_name, ftype='h5')
-    stc3 = read_source_estimate(out_name)
-    stc4 = read_source_estimate(out_name + '-stc.h5')
-    assert_raises(RuntimeError, read_source_estimate, out_name, subject='bar')
-    for stc_new in stc3, stc4:
-        assert_equal(stc_new.subject, stc.subject)
-        assert_array_equal(stc_new.data, stc.data)
-        assert_array_equal(stc_new.tmin, stc.tmin)
-        assert_array_equal(stc_new.tstep, stc.tstep)
-        assert_equal(len(stc_new.vertices), len(stc.vertices))
-        for v1, v2 in zip(stc_new.vertices, stc.vertices):
-            assert_array_equal(v1, v2)
+    """Test IO for STC files using HDF5."""
+    for stc in [_fake_stc(), _fake_vec_stc()]:
+        tempdir = _TempDir()
+        assert_raises(ValueError, stc.save, op.join(tempdir, 'tmp'),
+                      ftype='foo')
+        out_name = op.join(tempdir, 'tmp')
+        stc.save(out_name, ftype='h5')
+        stc.save(out_name, ftype='h5')  # test overwrite
+        stc3 = read_source_estimate(out_name)
+        stc4 = read_source_estimate(out_name + '-stc')
+        stc5 = read_source_estimate(out_name + '-stc.h5')
+        assert_raises(RuntimeError, read_source_estimate, out_name,
+                      subject='bar')
+        for stc_new in stc3, stc4, stc5:
+            assert_equal(stc_new.subject, stc.subject)
+            assert_array_equal(stc_new.data, stc.data)
+            assert_array_equal(stc_new.tmin, stc.tmin)
+            assert_array_equal(stc_new.tstep, stc.tstep)
+            assert_equal(len(stc_new.vertices), len(stc.vertices))
+            for v1, v2 in zip(stc_new.vertices, stc.vertices):
+                assert_array_equal(v1, v2)
 
 
 def test_io_w():
-    """Test IO for w files
-    """
+    """Test IO for w files."""
     tempdir = _TempDir()
     stc = _fake_stc(n_time=1)
     w_fname = op.join(tempdir, 'fake')
@@ -230,13 +318,14 @@ def test_io_w():
 
 
 def test_stc_arithmetic():
-    """Test arithmetic for STC files
-    """
+    """Test arithmetic for STC files."""
     stc = _fake_stc()
     data = stc.data.copy()
+    vec_stc = _fake_vec_stc()
+    vec_data = vec_stc.data.copy()
 
     out = list()
-    for a in [data, stc]:
+    for a in [data, stc, vec_data, vec_stc]:
         a = a + a * 3 + 3 * a - a ** 2 / 2
 
         a += a
@@ -260,76 +349,94 @@ def test_stc_arithmetic():
         out.append(a)
 
     assert_array_equal(out[0], out[1].data)
+    assert_array_equal(out[2], out[3].data)
     assert_array_equal(stc.sqrt().data, np.sqrt(stc.data))
+    assert_array_equal(vec_stc.sqrt().data, np.sqrt(vec_stc.data))
+    assert_array_equal(abs(stc).data, abs(stc.data))
+    assert_array_equal(abs(vec_stc).data, abs(vec_stc.data))
 
     stc_mean = stc.mean()
     assert_array_equal(stc_mean.data, np.mean(stc.data, 1)[:, None])
+    vec_stc_mean = vec_stc.mean()
+    assert_array_equal(vec_stc_mean.data,
+                       np.mean(vec_stc.data, 2)[:, :, None])
 
 
- at slow_test
+ at pytest.mark.slowtest
 @testing.requires_testing_data
 def test_stc_methods():
-    """Test stc methods lh_data, rh_data, bin, center_of_mass, resample"""
-    stc = read_source_estimate(fname_stc)
+    """Test stc methods lh_data, rh_data, bin(), resample()."""
+    stc_ = read_source_estimate(fname_stc)
+
+    # Make a vector version of the above source estimate
+    x = stc_.data[:, np.newaxis, :]
+    yz = np.zeros((x.shape[0], 2, x.shape[2]))
+    vec_stc_ = VectorSourceEstimate(
+        np.concatenate((x, yz), 1),
+        stc_.vertices, stc_.tmin, stc_.tstep, stc_.subject
+    )
+
+    for stc in [stc_, vec_stc_]:
+        # lh_data / rh_data
+        assert_array_equal(stc.lh_data, stc.data[:len(stc.lh_vertno)])
+        assert_array_equal(stc.rh_data, stc.data[len(stc.lh_vertno):])
+
+        # bin
+        binned = stc.bin(.12)
+        a = np.mean(stc.data[..., :np.searchsorted(stc.times, .12)], axis=-1)
+        assert_array_equal(a, binned.data[..., 0])
+
+        stc = read_source_estimate(fname_stc)
+        stc.subject = 'sample'
+        label_lh = read_labels_from_annot('sample', 'aparc', 'lh',
+                                          subjects_dir=subjects_dir)[0]
+        label_rh = read_labels_from_annot('sample', 'aparc', 'rh',
+                                          subjects_dir=subjects_dir)[0]
+        label_both = label_lh + label_rh
+        for label in (label_lh, label_rh, label_both):
+            assert_true(isinstance(stc.shape, tuple) and len(stc.shape) == 2)
+            stc_label = stc.in_label(label)
+            if label.hemi != 'both':
+                if label.hemi == 'lh':
+                    verts = stc_label.vertices[0]
+                else:  # label.hemi == 'rh':
+                    verts = stc_label.vertices[1]
+                n_vertices_used = len(label.get_vertices_used(verts))
+                assert_equal(len(stc_label.data), n_vertices_used)
+        stc_lh = stc.in_label(label_lh)
+        assert_raises(ValueError, stc_lh.in_label, label_rh)
+        label_lh.subject = 'foo'
+        assert_raises(RuntimeError, stc.in_label, label_lh)
+
+        stc_new = deepcopy(stc)
+        o_sfreq = 1.0 / stc.tstep
+        # note that using no padding for this STC reduces edge ringing...
+        stc_new.resample(2 * o_sfreq, npad=0, n_jobs=2)
+        assert_true(stc_new.data.shape[1] == 2 * stc.data.shape[1])
+        assert_true(stc_new.tstep == stc.tstep / 2)
+        stc_new.resample(o_sfreq, npad=0)
+        assert_true(stc_new.data.shape[1] == stc.data.shape[1])
+        assert_true(stc_new.tstep == stc.tstep)
+        assert_array_almost_equal(stc_new.data, stc.data, 5)
 
-    # lh_data / rh_data
-    assert_array_equal(stc.lh_data, stc.data[:len(stc.lh_vertno)])
-    assert_array_equal(stc.rh_data, stc.data[len(stc.lh_vertno):])
-
-    # bin
-    bin = stc.bin(.12)
-    a = np.array((1,), dtype=stc.data.dtype)
-    a[0] = np.mean(stc.data[0, stc.times < .12])
-    assert a[0] == bin.data[0, 0]
 
+ at testing.requires_testing_data
+def test_center_of_mass():
+    """Test computing the center of mass on an stc."""
+    stc = read_source_estimate(fname_stc)
     assert_raises(ValueError, stc.center_of_mass, 'sample')
-    assert_raises(TypeError, stc.center_of_mass, 'sample',
-                  subjects_dir=subjects_dir, surf=1)
     stc.lh_data[:] = 0
     vertex, hemi, t = stc.center_of_mass('sample', subjects_dir=subjects_dir)
     assert_true(hemi == 1)
-    # XXX Should design a fool-proof test case, but here were the results:
+    # XXX Should design a fool-proof test case, but here were the
+    # results:
     assert_equal(vertex, 124791)
     assert_equal(np.round(t, 2), 0.12)
 
-    stc = read_source_estimate(fname_stc)
-    stc.subject = 'sample'
-    label_lh = read_labels_from_annot('sample', 'aparc', 'lh',
-                                      subjects_dir=subjects_dir)[0]
-    label_rh = read_labels_from_annot('sample', 'aparc', 'rh',
-                                      subjects_dir=subjects_dir)[0]
-    label_both = label_lh + label_rh
-    for label in (label_lh, label_rh, label_both):
-        assert_true(isinstance(stc.shape, tuple) and len(stc.shape) == 2)
-        stc_label = stc.in_label(label)
-        if label.hemi != 'both':
-            if label.hemi == 'lh':
-                verts = stc_label.vertices[0]
-            else:  # label.hemi == 'rh':
-                verts = stc_label.vertices[1]
-            n_vertices_used = len(label.get_vertices_used(verts))
-            assert_equal(len(stc_label.data), n_vertices_used)
-    stc_lh = stc.in_label(label_lh)
-    assert_raises(ValueError, stc_lh.in_label, label_rh)
-    label_lh.subject = 'foo'
-    assert_raises(RuntimeError, stc.in_label, label_lh)
-
-    stc_new = deepcopy(stc)
-    o_sfreq = 1.0 / stc.tstep
-    # note that using no padding for this STC reduces edge ringing...
-    stc_new.resample(2 * o_sfreq, npad=0, n_jobs=2)
-    assert_true(stc_new.data.shape[1] == 2 * stc.data.shape[1])
-    assert_true(stc_new.tstep == stc.tstep / 2)
-    stc_new.resample(o_sfreq, npad=0)
-    assert_true(stc_new.data.shape[1] == stc.data.shape[1])
-    assert_true(stc_new.tstep == stc.tstep)
-    assert_array_almost_equal(stc_new.data, stc.data, 5)
-
 
 @testing.requires_testing_data
 def test_extract_label_time_course():
-    """Test extraction of label time courses from stc
-    """
+    """Test extraction of label time courses from stc."""
     n_stcs = 3
     n_times = 50
 
@@ -420,11 +527,10 @@ def test_extract_label_time_course():
     assert_true(x.size == 0)
 
 
- at slow_test
+ at pytest.mark.slowtest
 @testing.requires_testing_data
 def test_morph_data():
-    """Test morphing of data
-    """
+    """Test morphing of data."""
     tempdir = _TempDir()
     subject_from = 'sample'
     subject_to = 'fsaverage'
@@ -530,16 +636,25 @@ def test_morph_data():
     assert_equal(stc_from.tmin, stc_from.tmin)
     assert_equal(stc_from.tstep, stc_from.tstep)
 
+    # Morph vector data
+    stc_vec = _real_vec_stc()
+
+    # Ignore warnings about number of steps
+    stc_vec_to1 = stc_vec.morph(subject_to, grade=3, smooth=12,
+                                buffer_size=1000, subjects_dir=subjects_dir)
+    stc_vec_to2 = stc_vec.morph_precomputed(subject_to, vertices_to, morph_mat)
+    assert_array_almost_equal(stc_vec_to1.data, stc_vec_to2.data)
+
 
 def _my_trans(data):
-    """FFT that adds an additional dimension by repeating result"""
+    """FFT that adds an additional dimension by repeating result."""
     data_t = fft(data)
     data_t = np.concatenate([data_t[:, :, None], data_t[:, :, None]], axis=2)
     return data_t, None
 
 
 def test_transform_data():
-    """Test applying linear (time) transform to data"""
+    """Test applying linear (time) transform to data."""
     # make up some data
     n_sensors, n_vertices, n_times = 10, 20, 4
     kernel = rng.randn(n_vertices, n_sensors)
@@ -569,7 +684,7 @@ def test_transform_data():
 
 
 def test_transform():
-    """Test applying linear (time) transform to data"""
+    """Test applying linear (time) transform to data."""
     # make up some data
     n_verts_lh, n_verts_rh, n_times = 10, 10, 10
     vertices = [np.arange(n_verts_lh), n_verts_lh + np.arange(n_verts_rh)]
@@ -601,27 +716,30 @@ def test_transform():
     verts = np.arange(len(stc.lh_vertno),
                       len(stc.lh_vertno) + len(stc.rh_vertno), 1)
     verts_rh = stc.rh_vertno
-    t_idx = [np.where(times >= -50)[0][0], np.where(times <= 500)[0][-1]]
-    data_t = stc.transform_data(np.abs, idx=verts, tmin_idx=t_idx[0],
-                                tmax_idx=t_idx[-1])
+    tmin_idx = np.searchsorted(times, 0)
+    tmax_idx = np.searchsorted(times, 501)  # Include 500ms in the range
+    data_t = stc.transform_data(np.abs, idx=verts, tmin_idx=tmin_idx,
+                                tmax_idx=tmax_idx)
     stc.transform(np.abs, idx=verts, tmin=-50, tmax=500, copy=False)
     assert_true(isinstance(stc, SourceEstimate))
-    assert_true((stc.tmin == 0.) & (stc.times[-1] == 0.5))
-    assert_true(len(stc.vertices[0]) == 0)
+    assert_equal(stc.tmin, 0.)
+    assert_equal(stc.times[-1], 0.5)
+    assert_equal(len(stc.vertices[0]), 0)
     assert_equal(stc.vertices[1], verts_rh)
     assert_array_equal(stc.data, data_t)
 
     times = np.round(1000 * stc.times)
-    t_idx = [np.where(times >= 0)[0][0], np.where(times <= 250)[0][-1]]
-    data_t = stc.transform_data(np.abs, tmin_idx=t_idx[0], tmax_idx=t_idx[-1])
+    tmin_idx, tmax_idx = np.searchsorted(times, 0), np.searchsorted(times, 250)
+    data_t = stc.transform_data(np.abs, tmin_idx=tmin_idx, tmax_idx=tmax_idx)
     stc.transform(np.abs, tmin=0, tmax=250, copy=False)
-    assert_true((stc.tmin == 0.) & (stc.times[-1] == 0.2))
+    assert_equal(stc.tmin, 0.)
+    assert_equal(stc.times[-1], 0.2)
     assert_array_equal(stc.data, data_t)
 
 
 @requires_sklearn
 def test_spatio_temporal_tris_connectivity():
-    """Test spatio-temporal connectivity from triangles"""
+    """Test spatio-temporal connectivity from triangles."""
     tris = np.array([[0, 1, 2], [3, 4, 5]])
     connectivity = spatio_temporal_tris_connectivity(tris, 2)
     x = [1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]
@@ -638,7 +756,7 @@ def test_spatio_temporal_tris_connectivity():
 
 @testing.requires_testing_data
 def test_spatio_temporal_src_connectivity():
-    """Test spatio-temporal connectivity from source spaces"""
+    """Test spatio-temporal connectivity from source spaces."""
     tris = np.array([[0, 1, 2], [3, 4, 5]])
     src = [dict(), dict()]
     connectivity = spatio_temporal_tris_connectivity(tris, 2)
@@ -646,6 +764,8 @@ def test_spatio_temporal_src_connectivity():
     src[1]['use_tris'] = np.array([[0, 1, 2]])
     src[0]['vertno'] = np.array([0, 1, 2])
     src[1]['vertno'] = np.array([0, 1, 2])
+    src[0]['type'] = 'surf'
+    src[1]['type'] = 'surf'
     connectivity2 = spatio_temporal_src_connectivity(src, 2)
     assert_array_equal(connectivity.todense(), connectivity2.todense())
     # add test for dist connectivity
@@ -653,6 +773,8 @@ def test_spatio_temporal_src_connectivity():
     src[1]['dist'] = np.ones((3, 3)) - np.eye(3)
     src[0]['vertno'] = [0, 1, 2]
     src[1]['vertno'] = [0, 1, 2]
+    src[0]['type'] = 'surf'
+    src[1]['type'] = 'surf'
     connectivity3 = spatio_temporal_src_connectivity(src, 2, dist=2)
     assert_array_equal(connectivity.todense(), connectivity3.todense())
     # add test for source space connectivity with omitted vertices
@@ -661,7 +783,7 @@ def test_spatio_temporal_src_connectivity():
         warnings.simplefilter('always')
         src_ = inverse_operator['src']
         connectivity = spatio_temporal_src_connectivity(src_, n_times=2)
-        assert len(w) == 1
+    assert_equal(len(w), 1)
     a = connectivity.shape[0] / 2
     b = sum([s['nuse'] for s in inverse_operator['src']])
     assert_true(a == b)
@@ -671,7 +793,7 @@ def test_spatio_temporal_src_connectivity():
 
 @requires_pandas
 def test_to_data_frame():
-    """Test stc Pandas exporter"""
+    """Test stc Pandas exporter."""
     n_vert, n_times = 10, 5
     vertices = [np.arange(n_vert, dtype=np.int), np.empty(0, dtype=np.int)]
     data = rng.randn(n_vert, n_times)
@@ -692,8 +814,7 @@ def test_to_data_frame():
 
 
 def test_get_peak():
-    """Test peak getter
-    """
+    """Test peak getter."""
     n_vert, n_times = 10, 5
     vertices = [np.arange(n_vert, dtype=np.int), np.empty(0, dtype=np.int)]
     data = rng.randn(n_vert, n_times)
@@ -721,8 +842,7 @@ def test_get_peak():
 
 @testing.requires_testing_data
 def test_mixed_stc():
-    """Test source estimate from mixed source space
-    """
+    """Test source estimate from mixed source space."""
     N = 90  # number of sources
     T = 2  # number of time points
     S = 3  # number of source spaces
@@ -742,4 +862,103 @@ def test_mixed_stc():
     assert_raises(ValueError, stc.plot_surface, src=vol)
 
 
+def test_vec_stc():
+    """Test vector source estimate."""
+    nn = np.array([
+        [1, 0, 0],
+        [0, 1, 0],
+        [0, 0, 1],
+        [np.sqrt(1 / 3.)] * 3
+    ])
+    src = [dict(nn=nn[:2]), dict(nn=nn[2:])]
+
+    verts = [np.array([0, 1]), np.array([0, 1])]
+    data = np.array([
+        [1, 0, 0],
+        [0, 2, 0],
+        [3, 0, 0],
+        [1, 1, 1],
+    ])[:, :, np.newaxis]
+    stc = VectorSourceEstimate(data, verts, 0, 1, 'foo')
+
+    # Magnitude of the vectors
+    assert_array_equal(stc.magnitude().data[:, 0], [1, 2, 3, np.sqrt(3)])
+
+    # Vector components projected onto the vertex normals
+    normal = stc.normal(src)
+    assert_array_equal(normal.data[:, 0], [1, 2, 0, np.sqrt(3)])
+
+
+ at testing.requires_testing_data
+def test_epochs_vector_inverse():
+    """Test vector inverse consistency between evoked and epochs"""
+
+    raw = read_raw_fif(fname_raw)
+    events = find_events(raw, stim_channel='STI 014')[:2]
+    reject = dict(grad=2000e-13, mag=4e-12, eog=150e-6)
+
+    epochs = Epochs(raw, events, None, 0, 0.01, baseline=None,
+                    reject=reject, preload=True)
+
+    assert_equal(len(epochs), 2)
+
+    evoked = epochs.average(picks=range(len(epochs.ch_names)))
+
+    inv = read_inverse_operator(fname_inv)
+
+    method = "MNE"
+    snr = 3.
+    lambda2 = 1. / snr ** 2
+
+    stcs_epo = apply_inverse_epochs(epochs, inv, lambda2, method=method,
+                                    pick_ori='vector', return_generator=False)
+    stc_epo = np.mean(stcs_epo)
+
+    stc_evo = apply_inverse(evoked, inv, lambda2, method=method,
+                            pick_ori='vector')
+
+    assert_allclose(stc_epo.data, stc_evo.data, rtol=1e-9, atol=0)
+
+
+ at requires_sklearn
+ at testing.requires_testing_data
+def test_vol_connectivity():
+    """Test volume connectivity."""
+    from scipy import sparse
+    vol = read_source_spaces(fname_vsrc)
+
+    assert_raises(ValueError, spatial_src_connectivity, vol, dist=1.)
+
+    connectivity = spatial_src_connectivity(vol)
+    n_vertices = vol[0]['inuse'].sum()
+    assert_equal(connectivity.shape, (n_vertices, n_vertices))
+    assert_true(np.all(connectivity.data == 1))
+    assert_true(isinstance(connectivity, sparse.coo_matrix))
+
+    connectivity2 = spatio_temporal_src_connectivity(vol, n_times=2)
+    assert_equal(connectivity2.shape, (2 * n_vertices, 2 * n_vertices))
+    assert_true(np.all(connectivity2.data == 1))
+
+
+ at requires_sklearn
+ at requires_nibabel()
+ at testing.requires_testing_data
+def test_vol_mask():
+    """Test extraction of volume mask."""
+    src = read_source_spaces(fname_vsrc)
+    mask = _get_vol_mask(src)
+    # Let's use an alternative way that should be equivalent
+    vertices = src[0]['vertno']
+    n_vertices = len(vertices)
+    data = (1 + np.arange(n_vertices))[:, np.newaxis]
+    stc_tmp = VolSourceEstimate(data, vertices, tmin=0., tstep=1.)
+    img = stc_tmp.as_volume(src, mri_resolution=False)
+    img_data = img.get_data()[:, :, :, 0].T
+    mask_nib = (img_data != 0)
+    assert_array_equal(img_data[mask_nib], data[:, 0])
+    assert_array_equal(np.where(mask_nib.ravel())[0], src[0]['vertno'])
+    assert_array_equal(mask, mask_nib)
+    assert_array_equal(img_data.shape, mask.shape)
+
+
 run_tests_if_main()
diff --git a/mne/tests/test_source_space.py b/mne/tests/test_source_space.py
index f6b184a..0d0c7da 100644
--- a/mne/tests/test_source_space.py
+++ b/mne/tests/test_source_space.py
@@ -2,8 +2,9 @@ from __future__ import print_function
 
 import os
 import os.path as op
+from unittest import SkipTest
 from nose.tools import assert_true, assert_raises
-from nose.plugins.skip import SkipTest
+import pytest
 import numpy as np
 from numpy.testing import assert_array_equal, assert_allclose, assert_equal
 import warnings
@@ -14,12 +15,13 @@ from mne import (read_source_spaces, vertex_to_mni, write_source_spaces,
                  add_source_space_distances, read_bem_surfaces,
                  morph_source_spaces, SourceEstimate)
 from mne.utils import (_TempDir, requires_fs_or_nibabel, requires_nibabel,
-                       requires_freesurfer, run_subprocess, slow_test,
+                       requires_freesurfer, run_subprocess,
                        requires_mne, requires_version, run_tests_if_main)
 from mne.surface import _accumulate_normals, _triangle_neighbors
 from mne.source_space import _get_mri_header, _get_mgz_header
 from mne.externals.six.moves import zip
 from mne.source_space import (get_volume_labels_from_aseg, SourceSpaces,
+                              get_volume_labels_from_src,
                               _compare_source_spaces)
 from mne.tests.common import assert_naming
 from mne.io.constants import FIFF
@@ -46,7 +48,7 @@ rng = np.random.RandomState(0)
 @testing.requires_testing_data
 @requires_nibabel(vox2ras_tkr=True)
 def test_mgz_header():
-    """Test MGZ header reading"""
+    """Test MGZ header reading."""
     header = _get_mgz_header(fname_mri)
     mri_hdr = _get_mri_header(fname_mri)
     assert_allclose(mri_hdr.get_data_shape(), header['dims'])
@@ -56,7 +58,7 @@ def test_mgz_header():
 
 @requires_version('scipy', '0.11')
 def test_add_patch_info():
-    """Test adding patch info to source space"""
+    """Test adding patch info to source space."""
     # let's setup a small source space
     src = read_source_spaces(fname_small)
     src_new = read_source_spaces(fname_small)
@@ -89,7 +91,7 @@ def test_add_patch_info():
 @testing.requires_testing_data
 @requires_version('scipy', '0.11')
 def test_add_source_space_distances_limited():
-    """Test adding distances to source space with a dist_limit"""
+    """Test adding distances to source space with a dist_limit."""
     tempdir = _TempDir()
     src = read_source_spaces(fname)
     src_new = read_source_spaces(fname)
@@ -124,11 +126,11 @@ def test_add_source_space_distances_limited():
         assert_allclose(np.zeros_like(d.data), d.data, rtol=0, atol=1e-6)
 
 
- at slow_test
+ at pytest.mark.slowtest
 @testing.requires_testing_data
 @requires_version('scipy', '0.11')
 def test_add_source_space_distances():
-    """Test adding distances to source space"""
+    """Test adding distances to source space."""
     tempdir = _TempDir()
     src = read_source_spaces(fname)
     src_new = read_source_spaces(fname)
@@ -171,8 +173,7 @@ def test_add_source_space_distances():
 @testing.requires_testing_data
 @requires_mne
 def test_discrete_source_space():
-    """Test setting up (and reading/writing) discrete source spaces
-    """
+    """Test setting up (and reading/writing) discrete source spaces."""
     tempdir = _TempDir()
     src = read_source_spaces(fname)
     v = src[0]['vertno']
@@ -188,9 +189,7 @@ def test_discrete_source_space():
                         '--pos', temp_pos, '--src', temp_name])
         src_c = read_source_spaces(temp_name)
         pos_dict = dict(rr=src[0]['rr'][v], nn=src[0]['nn'][v])
-        src_new = setup_volume_source_space('sample', None,
-                                            pos=pos_dict,
-                                            subjects_dir=subjects_dir)
+        src_new = setup_volume_source_space(None, pos=pos_dict)
         _compare_source_spaces(src_c, src_new, mode='approx')
         assert_allclose(src[0]['rr'][v], src_new[0]['rr'],
                         rtol=1e-3, atol=1e-6)
@@ -198,7 +197,7 @@ def test_discrete_source_space():
                         rtol=1e-3, atol=1e-6)
 
         # now do writing
-        write_source_spaces(temp_name, src_c)
+        write_source_spaces(temp_name, src_c, overwrite=True)
         src_c2 = read_source_spaces(temp_name)
         _compare_source_spaces(src_c, src_c2)
 
@@ -212,11 +211,10 @@ def test_discrete_source_space():
             os.remove(temp_name)
 
 
- at slow_test
+ at pytest.mark.slowtest
 @testing.requires_testing_data
 def test_volume_source_space():
-    """Test setting up volume source spaces
-    """
+    """Test setting up volume source spaces."""
     tempdir = _TempDir()
     src = read_source_spaces(fname_vol)
     temp_name = op.join(tempdir, 'temp-src.fif')
@@ -224,15 +222,16 @@ def test_volume_source_space():
     surf['rr'] *= 1e3  # convert to mm
     # The one in the testing dataset (uses bem as bounds)
     for bem, surf in zip((fname_bem, None), (None, surf)):
-        src_new = setup_volume_source_space('sample', temp_name, pos=7.0,
-                                            bem=bem, surface=surf,
-                                            mri=fname_mri,
-                                            subjects_dir=subjects_dir)
+        src_new = setup_volume_source_space(
+            'sample', pos=7.0, bem=bem, surface=surf, mri='T1.mgz',
+            subjects_dir=subjects_dir)
+        write_source_spaces(temp_name, src_new, overwrite=True)
+        src[0]['subject_his_id'] = 'sample'  # XXX: to make comparison pass
         _compare_source_spaces(src, src_new, mode='approx')
         del src_new
         src_new = read_source_spaces(temp_name)
         _compare_source_spaces(src, src_new, mode='approx')
-    assert_raises(IOError, setup_volume_source_space, 'sample', temp_name,
+    assert_raises(IOError, setup_volume_source_space, 'sample',
                   pos=7.0, bem=None, surface='foo',  # bad surf
                   mri=fname_mri, subjects_dir=subjects_dir)
     assert_equal(repr(src), repr(src_new))
@@ -242,7 +241,7 @@ def test_volume_source_space():
 @testing.requires_testing_data
 @requires_mne
 def test_other_volume_source_spaces():
-    """Test setting up other volume source spaces"""
+    """Test setting up other volume source spaces."""
     # these are split off because they require the MNE tools, and
     # Travis doesn't seem to like them
 
@@ -254,15 +253,26 @@ def test_other_volume_source_spaces():
                     '--src', temp_name,
                     '--mri', fname_mri])
     src = read_source_spaces(temp_name)
-    src_new = setup_volume_source_space('sample', temp_name, pos=7.0,
-                                        mri=fname_mri,
+    src_new = setup_volume_source_space(None, pos=7.0, mri=fname_mri,
                                         subjects_dir=subjects_dir)
-    _compare_source_spaces(src, src_new, mode='approx')
+    # we use a more accurate elimination criteria, so let's fix the MNE-C
+    # source space
+    assert_equal(len(src_new[0]['vertno']), 7497)
+    assert_equal(len(src), 1)
+    assert_equal(len(src_new), 1)
+    good_mask = np.in1d(src[0]['vertno'], src_new[0]['vertno'])
+    src[0]['inuse'][src[0]['vertno'][~good_mask]] = 0
+    assert_equal(src[0]['inuse'].sum(), 7497)
+    src[0]['vertno'] = src[0]['vertno'][good_mask]
+    assert_equal(len(src[0]['vertno']), 7497)
+    src[0]['nuse'] = len(src[0]['vertno'])
+    assert_equal(src[0]['nuse'], 7497)
+    _compare_source_spaces(src_new, src, mode='approx')
     assert_true('volume, shape' in repr(src))
     del src
     del src_new
-    assert_raises(ValueError, setup_volume_source_space, 'sample', temp_name,
-                  pos=7.0, sphere=[1., 1.], mri=fname_mri,  # bad sphere
+    assert_raises(ValueError, setup_volume_source_space, 'sample', pos=7.0,
+                  sphere=[1., 1.], mri=fname_mri,  # bad sphere
                   subjects_dir=subjects_dir)
 
     # now without MRI argument, it should give an error when we try
@@ -275,7 +285,7 @@ def test_other_volume_source_spaces():
 
 @testing.requires_testing_data
 def test_triangle_neighbors():
-    """Test efficient vertex neighboring triangles for surfaces"""
+    """Test efficient vertex neighboring triangles for surfaces."""
     this = read_source_spaces(fname)[0]
     this['neighbor_tri'] = [list() for _ in range(this['np'])]
     for p in range(this['ntri']):
@@ -291,7 +301,7 @@ def test_triangle_neighbors():
 
 
 def test_accumulate_normals():
-    """Test efficient normal accumulation for surfaces"""
+    """Test efficient normal accumulation for surfaces."""
     # set up comparison
     n_pts = int(1.6e5)  # approx number in sample source space
     n_tris = int(3.2e5)
@@ -315,36 +325,31 @@ def test_accumulate_normals():
     assert_allclose(nn, this['nn'], rtol=1e-7, atol=1e-7)
 
 
- at slow_test
+ at pytest.mark.slowtest
 @testing.requires_testing_data
 def test_setup_source_space():
-    """Test setting up ico, oct, and all source spaces
-    """
+    """Test setting up ico, oct, and all source spaces."""
     tempdir = _TempDir()
     fname_ico = op.join(data_path, 'subjects', 'fsaverage', 'bem',
                         'fsaverage-ico-5-src.fif')
     # first lets test some input params
     assert_raises(ValueError, setup_source_space, 'sample', spacing='oct',
-                  add_dist=False)
+                  add_dist=False, subjects_dir=subjects_dir)
     assert_raises(ValueError, setup_source_space, 'sample', spacing='octo',
-                  add_dist=False)
+                  add_dist=False, subjects_dir=subjects_dir)
     assert_raises(ValueError, setup_source_space, 'sample', spacing='oct6e',
-                  add_dist=False)
+                  add_dist=False, subjects_dir=subjects_dir)
     assert_raises(ValueError, setup_source_space, 'sample', spacing='7emm',
-                  add_dist=False)
+                  add_dist=False, subjects_dir=subjects_dir)
     assert_raises(ValueError, setup_source_space, 'sample', spacing='alls',
-                  add_dist=False)
-    assert_raises(IOError, setup_source_space, 'sample', spacing='oct6',
-                  subjects_dir=subjects_dir, add_dist=False)
+                  add_dist=False, subjects_dir=subjects_dir)
 
     # ico 5 (fsaverage) - write to temp file
     src = read_source_spaces(fname_ico)
-    temp_name = op.join(tempdir, 'temp-src.fif')
     with warnings.catch_warnings(record=True):  # sklearn equiv neighbors
         warnings.simplefilter('always')
-        src_new = setup_source_space('fsaverage', temp_name, spacing='ico5',
-                                     subjects_dir=subjects_dir, add_dist=False,
-                                     overwrite=True)
+        src_new = setup_source_space('fsaverage', spacing='ico5',
+                                     subjects_dir=subjects_dir, add_dist=False)
     _compare_source_spaces(src, src_new, mode='approx')
     assert_equal(repr(src), repr(src_new))
     assert_equal(repr(src).count('surface ('), 2)
@@ -356,28 +361,28 @@ def test_setup_source_space():
     temp_name = op.join(tempdir, 'temp-src.fif')
     with warnings.catch_warnings(record=True):  # sklearn equiv neighbors
         warnings.simplefilter('always')
-        src_new = setup_source_space('sample', temp_name, spacing='oct6',
-                                     subjects_dir=subjects_dir,
-                                     overwrite=True, add_dist=False)
+        src_new = setup_source_space('sample', spacing='oct6',
+                                     subjects_dir=subjects_dir, add_dist=False)
+        write_source_spaces(temp_name, src_new, overwrite=True)
+    assert_equal(src_new[0]['nuse'], 4098)
     _compare_source_spaces(src, src_new, mode='approx', nearest=False)
     src_new = read_source_spaces(temp_name)
     _compare_source_spaces(src, src_new, mode='approx', nearest=False)
 
     # all source points - no file writing
-    src_new = setup_source_space('sample', None, spacing='all',
+    src_new = setup_source_space('sample', spacing='all',
                                  subjects_dir=subjects_dir, add_dist=False)
     assert_true(src_new[0]['nuse'] == len(src_new[0]['rr']))
     assert_true(src_new[1]['nuse'] == len(src_new[1]['rr']))
 
     # dense source space to hit surf['inuse'] lines of _create_surf_spacing
-    assert_raises(RuntimeError, setup_source_space, 'sample', None,
+    assert_raises(RuntimeError, setup_source_space, 'sample',
                   spacing='ico6', subjects_dir=subjects_dir, add_dist=False)
 
 
 @testing.requires_testing_data
 def test_read_source_spaces():
-    """Test reading of source space meshes
-    """
+    """Test reading of source space meshes."""
     src = read_source_spaces(fname, patch_stats=True)
 
     # 3D source space
@@ -397,11 +402,10 @@ def test_read_source_spaces():
     assert_true(rh_use_faces.max() <= rh_points.shape[0] - 1)
 
 
- at slow_test
+ at pytest.mark.slowtest
 @testing.requires_testing_data
 def test_write_source_space():
-    """Test reading and writing of source spaces
-    """
+    """Test reading and writing of source spaces."""
     tempdir = _TempDir()
     src0 = read_source_spaces(fname, patch_stats=False)
     write_source_spaces(op.join(tempdir, 'tmp-src.fif'), src0)
@@ -421,8 +425,7 @@ def test_write_source_space():
 @testing.requires_testing_data
 @requires_fs_or_nibabel
 def test_vertex_to_mni():
-    """Test conversion of vertices to MNI coordinates
-    """
+    """Test conversion of vertices to MNI coordinates."""
     # obtained using "tksurfer (sample) (l/r)h white"
     vertices = [100960, 7620, 150549, 96761]
     coords = np.array([[-60.86, -11.18, -3.19], [-36.46, -93.18, -2.36],
@@ -437,8 +440,7 @@ def test_vertex_to_mni():
 @requires_freesurfer
 @requires_nibabel()
 def test_vertex_to_mni_fs_nibabel():
-    """Test equivalence of vert_to_mni for nibabel and freesurfer
-    """
+    """Test equivalence of vert_to_mni for nibabel and freesurfer."""
     n_check = 1000
     subject = 'sample'
     vertices = rng.randint(0, 100000, n_check)
@@ -455,19 +457,20 @@ def test_vertex_to_mni_fs_nibabel():
 @requires_freesurfer
 @requires_nibabel()
 def test_get_volume_label_names():
-    """Test reading volume label names
-    """
+    """Test reading volume label names."""
     aseg_fname = op.join(subjects_dir, 'sample', 'mri', 'aseg.mgz')
-    label_names = get_volume_labels_from_aseg(aseg_fname)
+    label_names, label_colors = get_volume_labels_from_aseg(aseg_fname,
+                                                            return_colors=True)
     assert_equal(label_names.count('Brain-Stem'), 1)
 
+    assert_equal(len(label_colors), len(label_names))
+
 
 @testing.requires_testing_data
 @requires_freesurfer
 @requires_nibabel()
 def test_source_space_from_label():
-    """Test generating a source space from volume label
-    """
+    """Test generating a source space from volume label."""
     tempdir = _TempDir()
     aseg_fname = op.join(subjects_dir, 'sample', 'mri', 'aseg.mgz')
     label_names = get_volume_labels_from_aseg(aseg_fname)
@@ -501,9 +504,39 @@ def test_source_space_from_label():
 @testing.requires_testing_data
 @requires_freesurfer
 @requires_nibabel()
+def test_read_volume_from_src():
+    """Test reading volumes from a mixed source space."""
+    aseg_fname = op.join(subjects_dir, 'sample', 'mri', 'aseg.mgz')
+    labels_vol = ['Left-Amygdala',
+                  'Brain-Stem',
+                  'Right-Amygdala']
+
+    src = read_source_spaces(fname)
+
+    # Setup a volume source space
+    vol_src = setup_volume_source_space('sample', mri=aseg_fname,
+                                        pos=5.0,
+                                        bem=fname_bem,
+                                        volume_label=labels_vol,
+                                        subjects_dir=subjects_dir)
+    # Generate the mixed source space
+    src += vol_src
+
+    volume_src = get_volume_labels_from_src(src, 'sample', subjects_dir)
+    volume_label = volume_src[0].name
+    volume_label = 'Left-' + volume_label.replace('-lh', '')
+
+    # Test
+    assert_equal(volume_label, src[2]['seg_name'])
+
+    assert_equal(src[2]['type'], 'vol')
+
+
+ at testing.requires_testing_data
+ at requires_freesurfer
+ at requires_nibabel()
 def test_combine_source_spaces():
-    """Test combining source spaces
-    """
+    """Test combining source spaces."""
     tempdir = _TempDir()
     aseg_fname = op.join(subjects_dir, 'sample', 'mri', 'aseg.mgz')
     label_names = get_volume_labels_from_aseg(aseg_fname)
@@ -574,8 +607,7 @@ def test_combine_source_spaces():
 
 @testing.requires_testing_data
 def test_morph_source_spaces():
-    """Test morphing of source spaces
-    """
+    """Test morphing of source spaces."""
     src = read_source_spaces(fname_fs)
     src_morph = read_source_spaces(fname_morph)
     src_morph_py = morph_source_spaces(src, 'sample',
@@ -583,10 +615,10 @@ def test_morph_source_spaces():
     _compare_source_spaces(src_morph, src_morph_py, mode='approx')
 
 
- at slow_test
+ at pytest.mark.slowtest
 @testing.requires_testing_data
 def test_morphed_source_space_return():
-    """Test returning a morphed source space to the original subject"""
+    """Test returning a morphed source space to the original subject."""
     # let's create some random data on fsaverage
     data = rng.randn(20484, 1)
     tmin, tstep = 0, 1.
@@ -618,8 +650,7 @@ def test_morphed_source_space_return():
 
     # Compare to the original data
     stc_morph_morph = stc_morph.morph('fsaverage', stc_morph_return.vertices,
-                                      smooth=1,
-                                      subjects_dir=subjects_dir)
+                                      smooth=1, subjects_dir=subjects_dir)
     assert_equal(stc_morph_return.subject, stc_morph_morph.subject)
     for ii in range(2):
         assert_array_equal(stc_morph_return.vertices[ii],
@@ -629,6 +660,14 @@ def test_morphed_source_space_return():
                        stc_morph_morph.data[:, 0])[0, 1]
     assert_true(corr > 0.99, corr)
 
+    # Explicitly test having two vertices map to the same target vertex. We
+    # simulate this by having two vertices be at the same position.
+    src_fs2 = src_fs.copy()
+    vert1, vert2 = src_fs2[0]['vertno'][:2]
+    src_fs2[0]['rr'][vert1] = src_fs2[0]['rr'][vert2]
+    stc_morph_return = stc_morph.to_original_src(
+        src_fs2, subjects_dir=subjects_dir)
+
     # Degenerate cases
     stc_morph.subject = None  # no .subject provided
     assert_raises(ValueError, stc_morph.to_original_src,
@@ -645,6 +684,7 @@ def test_morphed_source_space_return():
     assert_raises(RuntimeError, stc_morph.to_original_src,
                   src, subjects_dir=subjects_dir)
 
+
 run_tests_if_main()
 
 # The following code was used to generate small-src.fif.gz.
diff --git a/mne/tests/test_surface.py b/mne/tests/test_surface.py
index 23c7255..75b15da 100644
--- a/mne/tests/test_surface.py
+++ b/mne/tests/test_surface.py
@@ -6,6 +6,7 @@ import warnings
 from shutil import copyfile
 from scipy import sparse
 from nose.tools import assert_true, assert_raises
+import pytest
 from numpy.testing import assert_array_equal, assert_allclose, assert_equal
 
 from mne.datasets import testing
@@ -14,10 +15,9 @@ from mne.surface import (read_morph_map, _compute_nearest,
                          fast_cross_3d, get_head_surf, read_curvature,
                          get_meg_helmet_surf)
 from mne.utils import (_TempDir, requires_mayavi, requires_tvtk,
-                       run_tests_if_main, slow_test)
+                       run_tests_if_main, object_diff)
 from mne.io import read_info
 from mne.transforms import _get_trans
-from mne.io.meas_info import _is_equal_dict
 
 data_path = testing.data_path(download=False)
 subjects_dir = op.join(data_path, 'subjects')
@@ -87,7 +87,7 @@ def test_compute_nearest():
         assert_array_equal(nn1, nn2)
 
 
- at slow_test
+ at pytest.mark.slowtest
 @testing.requires_testing_data
 def test_make_morph_maps():
     """Test reading and creating morph maps."""
@@ -96,20 +96,27 @@ def test_make_morph_maps():
     for subject in ('sample', 'sample_ds', 'fsaverage_ds'):
         os.mkdir(op.join(tempdir, subject))
         os.mkdir(op.join(tempdir, subject, 'surf'))
+        regs = ('reg', 'left_right') if subject == 'fsaverage_ds' else ('reg',)
         for hemi in ['lh', 'rh']:
-            args = [subject, 'surf', hemi + '.sphere.reg']
-            copyfile(op.join(subjects_dir, *args),
-                     op.join(tempdir, *args))
-
-    # this should trigger the creation of morph-maps dir and create the map
-    with warnings.catch_warnings(record=True):
-        mmap = read_morph_map('fsaverage_ds', 'sample_ds', tempdir)
-    mmap2 = read_morph_map('fsaverage_ds', 'sample_ds', subjects_dir)
-    assert_equal(len(mmap), len(mmap2))
-    for m1, m2 in zip(mmap, mmap2):
-        # deal with sparse matrix stuff
-        diff = (m1 - m2).data
-        assert_allclose(diff, np.zeros_like(diff), atol=1e-3, rtol=0)
+            for reg in regs:
+                args = [subject, 'surf', hemi + '.sphere.' + reg]
+                copyfile(op.join(subjects_dir, *args),
+                         op.join(tempdir, *args))
+
+    for subject_from, subject_to, xhemi in (
+            ('fsaverage_ds', 'sample_ds', False),
+            ('fsaverage_ds', 'fsaverage_ds', True)):
+        # trigger the creation of morph-maps dir and create the map
+        with warnings.catch_warnings(record=True):
+            mmap = read_morph_map(subject_from, subject_to, tempdir,
+                                  xhemi=xhemi)
+        mmap2 = read_morph_map(subject_from, subject_to, subjects_dir,
+                               xhemi=xhemi)
+        assert_equal(len(mmap), len(mmap2))
+        for m1, m2 in zip(mmap, mmap2):
+            # deal with sparse matrix stuff
+            diff = (m1 - m2).data
+            assert_allclose(diff, np.zeros_like(diff), atol=1e-3, rtol=0)
 
     # This will also trigger creation, but it's trivial
     with warnings.catch_warnings(record=True):
@@ -136,7 +143,7 @@ def test_io_surface():
                                                     read_metadata=True)
         assert_array_equal(pts, c_pts)
         assert_array_equal(tri, c_tri)
-        assert_true(_is_equal_dict([vol_info, c_vol_info]))
+        assert_equal(object_diff(vol_info, c_vol_info), '')
 
 
 @testing.requires_testing_data
diff --git a/mne/tests/test_transforms.py b/mne/tests/test_transforms.py
index 841165e..81057b8 100644
--- a/mne/tests/test_transforms.py
+++ b/mne/tests/test_transforms.py
@@ -1,11 +1,10 @@
 import os
 import os.path as op
-import numpy as np
+import warnings
 
+import numpy as np
 from nose.tools import assert_true, assert_raises
-from numpy.testing import (assert_array_equal, assert_equal, assert_allclose,
-                           assert_almost_equal, assert_array_almost_equal)
-import warnings
+from numpy.testing import assert_array_equal, assert_equal, assert_allclose
 
 from mne.datasets import testing
 from mne import read_trans, write_trans
@@ -15,10 +14,12 @@ from mne.tests.common import assert_naming
 from mne.transforms import (invert_transform, _get_trans,
                             rotation, rotation3d, rotation_angles, _find_trans,
                             combine_transforms, apply_trans, translation,
-                            get_ras_to_neuromag_trans, _sphere_to_cartesian,
-                            _polar_to_cartesian, _cartesian_to_sphere,
+                            get_ras_to_neuromag_trans, _pol_to_cart,
                             quat_to_rot, rot_to_quat, _angle_between_quats,
-                            _find_vector_rotation)
+                            _find_vector_rotation, _sph_to_cart, _cart_to_sph,
+                            _topo_to_sph,
+                            _SphericalSurfaceWarp as SphericalSurfaceWarp,
+                            rotation3d_align_z_axis)
 
 warnings.simplefilter('always')  # enable b/c these tests throw warnings
 
@@ -34,21 +35,38 @@ ctf_fname = op.join(base_dir, 'test_ctf_raw.fif')
 hp_fif_fname = op.join(base_dir, 'test_chpi_raw_sss.fif')
 
 
+def test_tps():
+    """Test TPS warping."""
+    az = np.linspace(0., 2 * np.pi, 20, endpoint=False)
+    pol = np.linspace(0, np.pi, 12)[1:-1]
+    sph = np.array(np.meshgrid(1, az, pol, indexing='ij'))
+    sph.shape = (3, -1)
+    assert_equal(sph.shape[1], 200)
+    source = _sph_to_cart(sph.T)
+    destination = source.copy()
+    destination *= 2
+    destination[:, 0] += 1
+    # fit with 100 points
+    warp = SphericalSurfaceWarp()
+    assert_true('no ' in repr(warp))
+    warp.fit(source[::3], destination[::2])
+    assert_true('oct5' in repr(warp))
+    destination_est = warp.transform(source)
+    assert_allclose(destination_est, destination, atol=1e-3)
+
+
 @testing.requires_testing_data
 def test_get_trans():
     """Test converting '-trans.txt' to '-trans.fif'"""
     trans = read_trans(fname)
     trans = invert_transform(trans)  # starts out as head->MRI, so invert
     trans_2 = _get_trans(fname_trans)[0]
-    assert_equal(trans['from'], trans_2['from'])
-    assert_equal(trans['to'], trans_2['to'])
-    assert_allclose(trans['trans'], trans_2['trans'], rtol=1e-5, atol=1e-5)
+    assert trans.__eq__(trans_2, atol=1e-5)
 
 
 @testing.requires_testing_data
 def test_io_trans():
-    """Test reading and writing of trans files
-    """
+    """Test reading and writing of trans files."""
     tempdir = _TempDir()
     os.mkdir(op.join(tempdir, 'sample'))
     assert_raises(RuntimeError, _find_trans, 'sample', subjects_dir=tempdir)
@@ -59,9 +77,7 @@ def test_io_trans():
     trans1 = read_trans(fname1)
 
     # check all properties
-    assert_true(trans0['from'] == trans1['from'])
-    assert_true(trans0['to'] == trans1['to'])
-    assert_array_equal(trans0['trans'], trans1['trans'])
+    assert trans0 == trans1
 
     # check reading non -trans.fif files
     assert_raises(IOError, read_trans, fname_eve)
@@ -74,7 +90,7 @@ def test_io_trans():
 
 
 def test_get_ras_to_neuromag_trans():
-    """Test the coordinate transformation from ras to neuromag"""
+    """Test the coordinate transformation from ras to neuromag."""
     # create model points in neuromag-like space
     rng = np.random.RandomState(0)
     anterior = [0, 1, 0]
@@ -95,21 +111,59 @@ def test_get_ras_to_neuromag_trans():
     pts_restored = apply_trans(hsp_trans, pts_changed)
 
     err = "Neuromag transformation failed"
-    assert_array_almost_equal(pts_restored, pts, 6, err)
+    assert_allclose(pts_restored, pts, atol=1e-6, err_msg=err)
+
 
+def _cartesian_to_sphere(x, y, z):
+    """Convert using old function."""
+    hypotxy = np.hypot(x, y)
+    r = np.hypot(hypotxy, z)
+    elev = np.arctan2(z, hypotxy)
+    az = np.arctan2(y, x)
+    return az, elev, r
 
-def test_sphere_to_cartesian():
-    """Test helper transform function from sphere to cartesian"""
-    phi, theta, r = (np.pi, np.pi, 1)
-    # expected value is (1, 0, 0)
+
+def _sphere_to_cartesian(theta, phi, r):
+    """Convert using old function."""
     z = r * np.sin(phi)
     rcos_phi = r * np.cos(phi)
     x = rcos_phi * np.cos(theta)
     y = rcos_phi * np.sin(theta)
-    coord = _sphere_to_cartesian(phi, theta, r)
-    # np.pi is an approx since pi is irrational
-    assert_almost_equal(coord, (x, y, z), 10)
-    assert_almost_equal(coord, (1, 0, 0), 10)
+    return x, y, z
+
+
+def test_sph_to_cart():
+    """Test conversion between sphere and cartesian."""
+    # Simple test, expected value (11, 0, 0)
+    r, theta, phi = 11., 0., np.pi / 2.
+    z = r * np.cos(phi)
+    rsin_phi = r * np.sin(phi)
+    x = rsin_phi * np.cos(theta)
+    y = rsin_phi * np.sin(theta)
+    coord = _sph_to_cart(np.array([[r, theta, phi]]))[0]
+    assert_allclose(coord, (x, y, z), atol=1e-7)
+    assert_allclose(coord, (r, 0, 0), atol=1e-7)
+    rng = np.random.RandomState(0)
+    # round-trip test
+    coords = rng.randn(10, 3)
+    assert_allclose(_sph_to_cart(_cart_to_sph(coords)), coords, atol=1e-5)
+    # equivalence tests to old versions
+    for coord in coords:
+        sph = _cart_to_sph(coord[np.newaxis])
+        cart = _sph_to_cart(sph)
+        sph_old = np.array(_cartesian_to_sphere(*coord))
+        cart_old = _sphere_to_cartesian(*sph_old)
+        sph_old[1] = np.pi / 2. - sph_old[1]  # new convention
+        assert_allclose(sph[0], sph_old[[2, 0, 1]], atol=1e-7)
+        assert_allclose(cart[0], cart_old, atol=1e-7)
+        assert_allclose(cart[0], coord, atol=1e-7)
+
+
+def _polar_to_cartesian(theta, r):
+    """Transform polar coordinates to cartesian"""
+    x = r * np.cos(theta)
+    y = r * np.sin(theta)
+    return x, y
 
 
 def test_polar_to_cartesian():
@@ -119,28 +173,58 @@ def test_polar_to_cartesian():
     # expected values are (-1, 0)
     x = r * np.cos(theta)
     y = r * np.sin(theta)
-    coord = _polar_to_cartesian(theta, r)
+    coord = _pol_to_cart(np.array([[r, theta]]))[0]
     # np.pi is an approx since pi is irrational
-    assert_almost_equal(coord, (x, y), 10)
-    assert_almost_equal(coord, (-1, 0), 10)
+    assert_allclose(coord, (x, y), atol=1e-7)
+    assert_allclose(coord, (-1, 0), atol=1e-7)
+    assert_allclose(coord, _polar_to_cartesian(theta, r), atol=1e-7)
+    rng = np.random.RandomState(0)
+    r = rng.randn(10)
+    theta = rng.rand(10) * (2 * np.pi)
+    polar = np.array((r, theta)).T
+    assert_allclose([_polar_to_cartesian(p[1], p[0]) for p in polar],
+                    _pol_to_cart(polar), atol=1e-7)
 
 
-def test_cartesian_to_sphere():
-    """Test helper transform function from cartesian to sphere"""
-    x, y, z = (1, 0, 0)
-    # expected values are (0, 0, 1)
-    hypotxy = np.hypot(x, y)
-    r = np.hypot(hypotxy, z)
-    elev = np.arctan2(z, hypotxy)
-    az = np.arctan2(y, x)
-    coord = _cartesian_to_sphere(x, y, z)
-    assert_equal(coord, (az, elev, r))
-    assert_equal(coord, (0, 0, 1))
+def _topo_to_sphere(theta, radius):
+    """Convert using old function."""
+    sph_phi = (0.5 - radius) * 180
+    sph_theta = -theta
+    return sph_phi, sph_theta
+
+
+def test_topo_to_sph():
+    """Test topo to sphere conversion."""
+    rng = np.random.RandomState(0)
+    angles = rng.rand(10) * 360
+    radii = rng.rand(10)
+    angles[0] = 30
+    radii[0] = 0.25
+    # new way
+    sph = _topo_to_sph(np.array([angles, radii]).T)
+    new = _sph_to_cart(sph)
+    new[:, [0, 1]] = new[:, [1, 0]] * [-1, 1]
+    # old way
+    for ii, (angle, radius) in enumerate(zip(angles, radii)):
+        sph_phi, sph_theta = _topo_to_sphere(angle, radius)
+        if ii == 0:
+            assert_allclose(_topo_to_sphere(angle, radius), [45, -30])
+        azimuth = sph_theta / 180.0 * np.pi
+        elevation = sph_phi / 180.0 * np.pi
+        assert_allclose(sph[ii], [1., azimuth, np.pi / 2. - elevation],
+                        atol=1e-7)
+        r = np.ones_like(radius)
+        x, y, z = _sphere_to_cartesian(azimuth, elevation, r)
+        pos = [-y, x, z]
+        if ii == 0:
+            expected = np.array([1. / 2., np.sqrt(3) / 2., 1.])
+            expected /= np.sqrt(2)
+            assert_allclose(pos, expected, atol=1e-7)
+        assert_allclose(pos, new[ii], atol=1e-7)
 
 
 def test_rotation():
-    """Test conversion between rotation angles and transformation matrix
-    """
+    """Test conversion between rotation angles and transformation matrix."""
     tests = [(0, 0, 1), (.5, .5, .5), (np.pi, 0, -1.5)]
     for rot in tests:
         x, y, z = rot
@@ -153,6 +237,25 @@ def test_rotation():
         assert_equal(back4, rot)
 
 
+def test_rotation3d_align_z_axis():
+    """Test rotation3d_align_z_axis."""
+    # The more complex z axis fails the assert presumably due to tolerance
+    #
+    inp_zs = [[0, 0, 1], [0, 1, 0], [1, 0, 0], [0, 0, -1],
+              [-0.75071668, -0.62183808,  0.22302888]]
+
+    exp_res = [[[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]],
+               [[1., 0., 0.], [0., 0., 1.], [0., -1., 0.]],
+               [[0., 0., 1.], [0., 1., 0.], [-1., 0., 0.]],
+               [[1., 0., 0.], [0., -1., 0.], [0., 0., -1.]],
+               [[0.53919688, -0.38169517, -0.75071668],
+                [-0.38169517, 0.683832, -0.62183808],
+                [0.75071668, 0.62183808, 0.22302888]]]
+
+    for res, z in zip(exp_res, inp_zs):
+        assert_allclose(res, rotation3d_align_z_axis(z), atol=1e-7)
+
+
 @testing.requires_testing_data
 def test_combine():
     """Test combining transforms
@@ -169,8 +272,7 @@ def test_combine():
 
 
 def test_quaternions():
-    """Test quaternion calculations
-    """
+    """Test quaternion calculations."""
     rots = [np.eye(3)]
     for fname in [test_fif_fname, ctf_fname, hp_fif_fname]:
         rots += [read_info(fname)['dev_head_t']['trans'][:3, :3]]
@@ -209,8 +311,7 @@ def test_quaternions():
 
 
 def test_vector_rotation():
-    """Test basic rotation matrix math
-    """
+    """Test basic rotation matrix math."""
     x = np.array([1., 0., 0.])
     y = np.array([0., 1., 0.])
     rot = _find_vector_rotation(x, y)
diff --git a/mne/tests/test_utils.py b/mne/tests/test_utils.py
index 4669bec..97559cc 100644
--- a/mne/tests/test_utils.py
+++ b/mne/tests/test_utils.py
@@ -1,13 +1,15 @@
 from numpy.testing import assert_equal, assert_array_equal, assert_allclose
-from nose.tools import assert_true, assert_raises, assert_not_equal
+from nose.tools import (assert_true, assert_raises, assert_not_equal,
+                        assert_not_in)
 from copy import deepcopy
 import os.path as op
 import numpy as np
 from scipy import sparse
 import os
 import warnings
+import webbrowser
 
-from mne import read_evokeds
+from mne import read_evokeds, open_docs
 from mne.datasets import testing
 from mne.externals.six.moves import StringIO
 from mne.io import show_fiff, read_raw_fif
@@ -26,7 +28,7 @@ from mne.utils import (set_log_level, set_log_file, _TempDir,
                        _get_call_line, compute_corr, sys_info, verbose,
                        check_fname, requires_ftp, get_config_path,
                        object_size, buggy_mkl_svd, _get_inst_data,
-                       copy_doc, copy_function_doc_to_method_doc)
+                       copy_doc, copy_function_doc_to_method_doc, ProgressBar)
 
 
 warnings.simplefilter('always')  # enable b/c these tests throw warnings
@@ -48,8 +50,8 @@ def clean_lines(lines=[]):
 
 
 def test_buggy_mkl():
-    """Test decorator for buggy MKL issues"""
-    from nose.plugins.skip import SkipTest
+    """Test decorator for buggy MKL issues."""
+    from unittest import SkipTest
 
     @buggy_mkl_svd
     def foo(a, b):
@@ -65,8 +67,7 @@ def test_buggy_mkl():
 
 
 def test_sys_info():
-    """Test info-showing utility
-    """
+    """Test info-showing utility."""
     out = StringIO()
     sys_info(fid=out)
     out = out.getvalue()
@@ -74,8 +75,7 @@ def test_sys_info():
 
 
 def test_get_call_line():
-    """Test getting a call line
-    """
+    """Test getting a call line."""
     @verbose
     def foo(verbose=None):
         return _get_call_line(in_verbose=True)
@@ -92,13 +92,13 @@ def test_get_call_line():
 
 
 def test_object_size():
-    """Test object size estimation"""
+    """Test object size estimation."""
     assert_true(object_size(np.ones(10, np.float32)) <
                 object_size(np.ones(10, np.float64)))
     for lower, upper, obj in ((0, 60, ''),
                               (0, 30, 1),
                               (0, 30, 1.),
-                              (0, 60, 'foo'),
+                              (0, 70, 'foo'),
                               (0, 150, np.ones(0)),
                               (0, 150, np.int32(1)),
                               (150, 500, np.ones(20)),
@@ -112,8 +112,8 @@ def test_object_size():
 
 
 def test_get_inst_data():
-    """Test _get_inst_data"""
-    raw = read_raw_fif(fname_raw, add_eeg_ref=False)
+    """Test _get_inst_data."""
+    raw = read_raw_fif(fname_raw)
     raw.crop(tmax=1.)
     assert_equal(_get_inst_data(raw), raw._data)
     raw.pick_channels(raw.ch_names[:2])
@@ -135,7 +135,7 @@ def test_get_inst_data():
 
 
 def test_misc():
-    """Test misc utilities"""
+    """Test misc utilities."""
     assert_equal(_memory_usage(-1)[0], -1)
     assert_equal(_memory_usage((clean_lines, [], {}))[0], -1)
     assert_equal(_memory_usage(clean_lines)[0], -1)
@@ -156,12 +156,12 @@ def test_misc():
 
 @requires_mayavi
 def test_check_mayavi():
-    """Test mayavi version check"""
+    """Test mayavi version check."""
     assert_raises(RuntimeError, _check_mayavi_version, '100.0.0')
 
 
 def test_run_tests_if_main():
-    """Test run_tests_if_main functionality"""
+    """Test run_tests_if_main functionality."""
     x = []
 
     def test_a():
@@ -192,7 +192,7 @@ def test_run_tests_if_main():
 
 
 def test_hash():
-    """Test dictionary hashing and comparison functions"""
+    """Test dictionary hashing and comparison functions."""
     # does hashing all of these types work:
     # {dict, list, tuple, ndarray, str, float, int, None}
     d0 = dict(a=dict(a=0.1, b='fo', c=1), b=[1, 'b'], c=(), d=np.ones(3),
@@ -274,8 +274,7 @@ def test_hash():
 
 
 def test_md5sum():
-    """Test md5sum calculation
-    """
+    """Test md5sum calculation."""
     tempdir = _TempDir()
     fname1 = op.join(tempdir, 'foo')
     fname2 = op.join(tempdir, 'bar')
@@ -289,8 +288,7 @@ def test_md5sum():
 
 
 def test_tempdir():
-    """Test TempDir
-    """
+    """Test TempDir."""
     tempdir2 = _TempDir()
     assert_true(op.isdir(tempdir2))
     x = str(tempdir2)
@@ -299,8 +297,7 @@ def test_tempdir():
 
 
 def test_estimate_rank():
-    """Test rank estimation
-    """
+    """Test rank estimation."""
     data = np.eye(10)
     assert_array_equal(estimate_rank(data, return_singular=True)[1],
                        np.ones(10))
@@ -310,15 +307,18 @@ def test_estimate_rank():
 
 
 def test_logging():
-    """Test logging (to file)
-    """
+    """Test logging (to file)."""
     assert_raises(ValueError, set_log_level, 'foo')
     tempdir = _TempDir()
     test_name = op.join(tempdir, 'test.log')
     with open(fname_log, 'r') as old_log_file:
+        # [:-1] used to strip an extra "No baseline correction applied"
         old_lines = clean_lines(old_log_file.readlines())
+        old_lines.pop(-1)
     with open(fname_log_2, 'r') as old_log_file_2:
         old_lines_2 = clean_lines(old_log_file_2.readlines())
+        old_lines_2.pop(14)
+        old_lines_2.pop(-1)
 
     if op.isfile(test_name):
         os.remove(test_name)
@@ -385,23 +385,24 @@ def test_logging():
 
 
 def test_config():
-    """Test mne-python config file support"""
+    """Test mne-python config file support."""
     tempdir = _TempDir()
     key = '_MNE_PYTHON_CONFIG_TESTING'
     value = '123456'
+    value2 = '123'
     old_val = os.getenv(key, None)
     os.environ[key] = value
     assert_true(get_config(key) == value)
     del os.environ[key]
     # catch the warning about it being a non-standard config key
     assert_true(len(set_config(None, None)) > 10)  # tuple of valid keys
-    with warnings.catch_warnings(record=True) as w:
+    with warnings.catch_warnings(record=True) as w:  # non-standard key
         warnings.simplefilter('always')
         set_config(key, None, home_dir=tempdir, set_env=False)
     assert_true(len(w) == 1)
     assert_true(get_config(key, home_dir=tempdir) is None)
     assert_raises(KeyError, get_config, key, raise_error=True)
-    with warnings.catch_warnings(record=True):
+    with warnings.catch_warnings(record=True):  # non-standard key
         warnings.simplefilter('always')
         assert_true(key not in os.environ)
         set_config(key, value, home_dir=tempdir, set_env=True)
@@ -413,28 +414,36 @@ def test_config():
         assert_true(key not in os.environ)
     if old_val is not None:
         os.environ[key] = old_val
-    # Check if get_config with no input returns all config
+    # Check if get_config with key=None returns all config
     key = 'MNE_PYTHON_TESTING_KEY'
-    config = {key: value}
+    assert_not_in(key, get_config(home_dir=tempdir))
     with warnings.catch_warnings(record=True):  # non-standard key
         warnings.simplefilter('always')
         set_config(key, value, home_dir=tempdir)
-    assert_equal(get_config(home_dir=tempdir), config)
+    assert_equal(get_config(home_dir=tempdir)[key], value)
+    old_val = os.environ.get(key)
+    try:  # os.environ should take precedence over config file
+        os.environ[key] = value2
+        assert_equal(get_config(home_dir=tempdir)[key], value2)
+    finally:  # reset os.environ
+        if old_val is None:
+            os.environ.pop(key, None)
+        else:
+            os.environ[key] = old_val
     # Check what happens when we use a corrupted file
     json_fname = get_config_path(home_dir=tempdir)
     with open(json_fname, 'w') as fid:
         fid.write('foo{}')
     with warnings.catch_warnings(record=True) as w:
-        assert_equal(get_config(home_dir=tempdir), dict())
+        assert_not_in(key, get_config(home_dir=tempdir))
     assert_true(any('not a valid JSON' in str(ww.message) for ww in w))
-    with warnings.catch_warnings(record=True) as w:  # non-standard key
+    with warnings.catch_warnings(record=True):  # non-standard key
         assert_raises(RuntimeError, set_config, key, 'true', home_dir=tempdir)
 
 
 @testing.requires_testing_data
 def test_show_fiff():
-    """Test show_fiff
-    """
+    """Test show_fiff."""
     # this is not exhaustive, but hopefully bugs will be found in use
     info = show_fiff(fname_evoked)
     keys = ['FIFF_EPOCH', 'FIFFB_HPI_COIL', 'FIFFB_PROJ_ITEM',
@@ -458,8 +467,7 @@ class deprecated_class(object):
 
 
 def test_deprecated():
-    """Test deprecated function
-    """
+    """Test deprecated function."""
     with warnings.catch_warnings(record=True) as w:
         warnings.simplefilter('always')
         deprecated_func()
@@ -471,7 +479,7 @@ def test_deprecated():
 
 
 def _test_fetch(url):
-    """Helper to test URL retrieval"""
+    """Helper to test URL retrieval."""
     tempdir = _TempDir()
     with ArgvSetter(disable_stderr=False):  # to capture stdout
         archive_name = op.join(tempdir, "download_test")
@@ -493,42 +501,38 @@ def _test_fetch(url):
 
 @requires_good_network
 def test_fetch_file_html():
-    """Test file downloading over http"""
+    """Test file downloading over http."""
     _test_fetch('http://google.com')
 
 
 @requires_ftp
 @requires_good_network
 def test_fetch_file_ftp():
-    """Test file downloading over ftp"""
+    """Test file downloading over ftp."""
     _test_fetch('ftp://speedtest.tele2.net/1KB.zip')
 
 
 def test_sum_squared():
-    """Test optimized sum of squares
-    """
+    """Test optimized sum of squares."""
     X = np.random.RandomState(0).randint(0, 50, (3, 3))
     assert_equal(np.sum(X ** 2), sum_squared(X))
 
 
 def test_sizeof_fmt():
-    """Test sizeof_fmt
-    """
+    """Test sizeof_fmt."""
     assert_equal(sizeof_fmt(0), '0 bytes')
     assert_equal(sizeof_fmt(1), '1 byte')
     assert_equal(sizeof_fmt(1000), '1000 bytes')
 
 
 def test_url_to_local_path():
-    """Test URL to local path
-    """
+    """Test URL to local path."""
     assert_equal(_url_to_local_path('http://google.com/home/why.html', '.'),
                  op.join('.', 'home', 'why.html'))
 
 
 def test_check_type_picks():
-    """Test checking type integrity checks of picks
-    """
+    """Test checking type integrity checks of picks."""
     picks = np.arange(12)
     assert_array_equal(picks, _check_type_picks(picks))
     picks = list(range(12))
@@ -542,8 +546,7 @@ def test_check_type_picks():
 
 
 def test_compute_corr():
-    """Test Anscombe's Quartett
-    """
+    """Test Anscombe's Quartett."""
     x = np.array([10, 8, 13, 9, 11, 14, 6, 4, 12, 7, 5])
     y = np.array([[8.04, 6.95, 7.58, 8.81, 8.33, 9.96,
                    7.24, 4.26, 10.84, 4.82, 5.68],
@@ -563,8 +566,7 @@ def test_compute_corr():
 
 
 def test_create_slices():
-    """Test checking the create of time create_slices
-    """
+    """Test checking the create of time create_slices."""
     # Test that create_slices default provide an empty list
     assert_true(create_slices(0, 0) == [])
     # Test that create_slice return correct number of slices
@@ -599,8 +601,7 @@ def test_create_slices():
 
 
 def test_time_mask():
-    """Test safe time masking
-    """
+    """Test safe time masking."""
     N = 10
     x = np.arange(N).astype(float)
     assert_equal(_time_mask(x, 0, N - 1).sum(), N)
@@ -627,8 +628,7 @@ def test_time_mask():
 
 
 def test_random_permutation():
-    """Test random permutation function
-    """
+    """Test random permutation function."""
     n_samples = 10
     random_state = 42
     python_randperm = random_permutation(n_samples, random_state)
@@ -640,7 +640,7 @@ def test_random_permutation():
 
 
 def test_copy_doc():
-    '''Test decorator for copying docstrings'''
+    """Test decorator for copying docstrings."""
     class A:
         def m1():
             """Docstring for m1"""
@@ -660,7 +660,7 @@ def test_copy_doc():
 
 
 def test_copy_function_doc_to_method_doc():
-    '''Test decorator for re-using function docstring as method docstrings'''
+    """Test decorator for re-using function docstring as method docstrings."""
     def f1(object, a, b, c):
         """Docstring for f1
 
@@ -757,4 +757,36 @@ def test_copy_function_doc_to_method_doc():
     assert_raises(ValueError, copy_function_doc_to_method_doc(f4), A.method_f1)
     assert_raises(ValueError, copy_function_doc_to_method_doc(f5), A.method_f1)
 
+
+def test_progressbar():
+    a = np.arange(10)
+    pbar = ProgressBar(a)
+    assert_equal(a, pbar.iterable)
+    assert_equal(10, pbar.max_value)
+
+    pbar = ProgressBar(10)
+    assert_equal(10, pbar.max_value)
+    assert_true(pbar.iterable is None)
+
+    # Make sure that non-iterable input raises an error
+    def iter_func(a):
+        for ii in a:
+            pass
+    assert_raises(ValueError, iter_func, ProgressBar(20))
+
+
+def test_open_docs():
+    """Test doc launching."""
+    old_tab = webbrowser.open_new_tab
+    try:
+        # monkey patch temporarily to prevent tabs from actually spawning
+        webbrowser.open_new_tab = lambda x: assert_true('martinos' in x)
+        open_docs()
+        open_docs('tutorials', 'dev')
+        open_docs('examples', 'stable')
+        assert_raises(ValueError, open_docs, 'foo')
+        assert_raises(ValueError, open_docs, 'api', 'foo')
+    finally:
+        webbrowser.open_new_tab = old_tab
+
 run_tests_if_main()
diff --git a/mne/time_frequency/__init__.py b/mne/time_frequency/__init__.py
index 72ab5b2..9ec355f 100644
--- a/mne/time_frequency/__init__.py
+++ b/mne/time_frequency/__init__.py
@@ -1,13 +1,11 @@
-"""Time frequency analysis tools
-"""
+"""Time frequency analysis tools."""
 
-from .tfr import (single_trial_power, morlet, tfr_morlet, cwt_morlet,
-                  AverageTFR, tfr_multitaper, read_tfrs, write_tfrs,
-                  EpochsTFR)
-from .psd import psd_welch, psd_multitaper
-from .csd import (CrossSpectralDensity, compute_epochs_csd, csd_epochs,
-                  csd_array)
+from .tfr import (morlet, tfr_morlet, AverageTFR, tfr_multitaper,
+                  read_tfrs, write_tfrs, EpochsTFR, tfr_array_morlet)
+from .psd import psd_welch, psd_multitaper, psd_array_welch
+from .csd import CrossSpectralDensity, csd_epochs, csd_array
 from .ar import fit_iir_model_raw
-from .multitaper import dpss_windows
+from .multitaper import (dpss_windows, psd_array_multitaper,
+                         tfr_array_multitaper)
 from .stft import stft, istft, stftfreq
-from ._stockwell import tfr_stockwell
+from ._stockwell import tfr_stockwell, tfr_array_stockwell
diff --git a/mne/time_frequency/_stockwell.py b/mne/time_frequency/_stockwell.py
index 0af3180..42b4ebd 100644
--- a/mne/time_frequency/_stockwell.py
+++ b/mne/time_frequency/_stockwell.py
@@ -16,7 +16,7 @@ from .tfr import AverageTFR, _get_data
 
 
 def _check_input_st(x_in, n_fft):
-    """Aux function"""
+    """Aux function."""
     # flatten to 2 D and memorize original shape
     n_times = x_in.shape[-1]
 
@@ -41,7 +41,7 @@ def _check_input_st(x_in, n_fft):
 
 
 def _precompute_st_windows(n_samp, start_f, stop_f, sfreq, width):
-    """Precompute stockwell gausian windows (in the freq domain)"""
+    """Precompute stockwell gausian windows (in the freq domain)."""
     tw = fftpack.fftfreq(n_samp, 1. / sfreq) / n_samp
     tw = np.r_[tw[:1], tw[1:][::-1]]
 
@@ -60,7 +60,7 @@ def _precompute_st_windows(n_samp, start_f, stop_f, sfreq, width):
 
 
 def _st(x, start_f, windows):
-    """Implementation based on Ali Moukadem Matlab code (only used in tests)"""
+    """Compute ST based on Ali Moukadem MATLAB code (used in tests)."""
     n_samp = x.shape[-1]
     ST = np.empty(x.shape[:-1] + (len(windows), n_samp), dtype=np.complex)
     # do the work
@@ -73,7 +73,7 @@ def _st(x, start_f, windows):
 
 
 def _st_power_itc(x, start_f, compute_itc, zero_pad, decim, W):
-    """Aux function"""
+    """Aux function."""
     n_samp = x.shape[-1]
     n_out = (n_samp - zero_pad)
     n_out = n_out // decim + bool(n_out % decim)
@@ -89,6 +89,7 @@ def _st_power_itc(x, start_f, compute_itc, zero_pad, decim, W):
         else:
             TFR = ST[:, ::decim]
         TFR_abs = np.abs(TFR)
+        TFR_abs[TFR_abs == 0] = 1.
         if compute_itc:
             TFR /= TFR_abs
             itc[i_f] = np.abs(np.mean(TFR, axis=0))
@@ -97,9 +98,11 @@ def _st_power_itc(x, start_f, compute_itc, zero_pad, decim, W):
     return psd, itc
 
 
-def _induced_power_stockwell(data, sfreq, fmin, fmax, n_fft=None, width=1.0,
-                             decim=1, return_itc=False, n_jobs=1):
-    """Computes power and intertrial coherence using Stockwell (S) transform
+def tfr_array_stockwell(data, sfreq, fmin=None, fmax=None, n_fft=None,
+                        width=1.0, decim=1, return_itc=False, n_jobs=1):
+    """Compute power and intertrial coherence using Stockwell (S) transform.
+
+    See [1]_, [2]_, [3]_, [4]_ for more information.
 
     Parameters
     ----------
@@ -139,23 +142,31 @@ def _induced_power_stockwell(data, sfreq, fmin, fmax, n_fft=None, width=1.0,
 
     References
     ----------
-    Stockwell, R. G. "Why use the S-transform." AMS Pseudo-differential
-        operators: Partial differential equations and time-frequency
-        analysis 52 (2007): 279-309.
-    Moukadem, A., Bouguila, Z., Abdeslam, D. O, and Dieterlen, A. Stockwell
-        transform optimization applied on the detection of split in heart
-        sounds (2014). Signal Processing Conference (EUSIPCO), 2013 Proceedings
-        of the 22nd European, pages 2015--2019.
-    Wheat, K., Cornelissen, P. L., Frost, S.J, and Peter C. Hansen (2010).
-        During Visual Word Recognition, Phonology Is Accessed
-        within 100 ms and May Be Mediated by a Speech Production
-        Code: Evidence from Magnetoencephalography. The Journal of
-        Neuroscience, 30 (15), 5229-5233.
-    K. A. Jones and B. Porjesz and D. Chorlian and M. Rangaswamy and C.
-        Kamarajan and A. Padmanabhapillai and A. Stimus and H. Begleiter
-        (2006). S-transform time-frequency analysis of P300 reveals deficits in
-        individuals diagnosed with alcoholism.
-        Clinical Neurophysiology 117 2128--2143
+    .. [1] Stockwell, R. G. "Why use the S-transform." AMS Pseudo-differential
+       operators: Partial differential equations and time-frequency
+       analysis 52 (2007): 279-309.
+    .. [2] Moukadem, A., Bouguila, Z., Abdeslam, D. O, and Dieterlen, A.
+       Stockwell transform optimization applied on the detection of split in
+       heart sounds (2014). Signal Processing Conference (EUSIPCO), 2013
+       Proceedings of the 22nd European, pages 2015--2019.
+    .. [3] Wheat, K., Cornelissen, P. L., Frost, S.J, and Peter C. Hansen
+       (2010). During Visual Word Recognition, Phonology Is Accessed
+       within 100 ms and May Be Mediated by a Speech Production
+       Code: Evidence from Magnetoencephalography. The Journal of
+       Neuroscience, 30 (15), 5229-5233.
+    .. [4] K. A. Jones and B. Porjesz and D. Chorlian and M. Rangaswamy and C.
+       Kamarajan and A. Padmanabhapillai and A. Stimus and H. Begleiter
+       (2006). S-transform time-frequency analysis of P300 reveals deficits in
+       individuals diagnosed with alcoholism.
+       Clinical Neurophysiology 117 2128--2143
+
+    See Also
+    --------
+    mne.time_frequency.tfr_stockwell
+    mne.time_frequency.tfr_multitaper
+    mne.time_frequency.tfr_array_multitaper
+    mne.time_frequency.tfr_morlet
+    mne.time_frequency.tfr_array_morlet
     """
     n_epochs, n_channels = data.shape[:2]
     n_out = data.shape[2] // decim + bool(data.shape[2] % decim)
@@ -192,7 +203,7 @@ def _induced_power_stockwell(data, sfreq, fmin, fmax, n_fft=None, width=1.0,
 def tfr_stockwell(inst, fmin=None, fmax=None, n_fft=None,
                   width=1.0, decim=1, return_itc=False, n_jobs=1,
                   verbose=None):
-    """Time-Frequency Representation (TFR) using Stockwell Transform
+    """Time-Frequency Representation (TFR) using Stockwell Transform.
 
     Parameters
     ----------
@@ -217,7 +228,8 @@ def tfr_stockwell(inst, fmin=None, fmax=None, n_fft=None,
     n_jobs : int
         The number of jobs to run in parallel (over channels).
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -228,8 +240,11 @@ def tfr_stockwell(inst, fmin=None, fmax=None, n_fft=None,
 
     See Also
     --------
-    cwt : Compute time-frequency decomposition with user-provided wavelets
-    cwt_morlet, psd_multitaper
+    mne.time_frequency.tfr_array_stockwell
+    mne.time_frequency.tfr_multitaper
+    mne.time_frequency.tfr_array_multitaper
+    mne.time_frequency.tfr_morlet
+    mne.time_frequency.tfr_array_morlet
 
     Notes
     -----
@@ -241,14 +256,11 @@ def tfr_stockwell(inst, fmin=None, fmax=None, n_fft=None,
     info = pick_info(inst.info, picks)
     data = data[:, picks, :]
     n_jobs = check_n_jobs(n_jobs)
-    power, itc, freqs = _induced_power_stockwell(data,
-                                                 sfreq=info['sfreq'],
-                                                 fmin=fmin, fmax=fmax,
-                                                 n_fft=n_fft,
-                                                 width=width,
-                                                 decim=decim,
-                                                 return_itc=return_itc,
-                                                 n_jobs=n_jobs)
+    power, itc, freqs = tfr_array_stockwell(data, sfreq=info['sfreq'],
+                                            fmin=fmin, fmax=fmax, n_fft=n_fft,
+                                            width=width, decim=decim,
+                                            return_itc=return_itc,
+                                            n_jobs=n_jobs)
     times = inst.times[::decim].copy()
     nave = len(data)
     out = AverageTFR(info, power, times, freqs, nave, method='stockwell-power')
diff --git a/mne/time_frequency/ar.py b/mne/time_frequency/ar.py
index 8be7039..30cbc12 100644
--- a/mne/time_frequency/ar.py
+++ b/mne/time_frequency/ar.py
@@ -4,136 +4,43 @@
 # License: BSD (3-clause)
 
 import numpy as np
-from scipy.linalg import toeplitz
+from scipy import linalg
 
-from ..io.pick import pick_types
+from ..defaults import _handle_default
+from ..io.pick import _pick_data_channels, _picks_by_type, pick_info
 from ..utils import verbose
 
 
-# XXX : Back ported from statsmodels
+def _yule_walker(X, order=1):
+    """Compute Yule-Walker (adapted from statsmodels).
 
-def yule_walker(X, order=1, method="unbiased", df=None, inv=False,
-                demean=True):
+    Operates in-place.
     """
-    Estimate AR(p) parameters from a sequence X using Yule-Walker equation.
-
-    Unbiased or maximum-likelihood estimator (mle)
-
-    See, for example:
-
-    http://en.wikipedia.org/wiki/Autoregressive_moving_average_model
-
-    Parameters
-    ----------
-    X : array-like
-        1d array
-    order : integer, optional
-        The order of the autoregressive process.  Default is 1.
-    method : string, optional
-       Method can be "unbiased" or "mle" and this determines denominator in
-       estimate of autocorrelation function (ACF) at lag k. If "mle", the
-       denominator is n=X.shape[0], if "unbiased" the denominator is n-k.
-       The default is unbiased.
-    df : integer, optional
-       Specifies the degrees of freedom. If `df` is supplied, then it is
-       assumed the X has `df` degrees of freedom rather than `n`.  Default is
-       None.
-    inv : bool
-        If inv is True the inverse of R is also returned.  Default is False.
-    demean : bool
-        True, the mean is subtracted from `X` before estimation.
-
-    Returns
-    -------
-    rho
-        The autoregressive coefficients
-    sigma
-        TODO
-
-    """
-    # TODO: define R better, look back at notes and technical notes on YW.
-    # First link here is useful
-    # http://www-stat.wharton.upenn.edu/~steele/Courses/956/ResourceDetails/YuleWalkerAndMore.htm  # noqa
-    method = str(method).lower()
-    if method not in ["unbiased", "mle"]:
-        raise ValueError("ACF estimation method must be 'unbiased' or 'MLE'")
-    X = np.array(X, float)
-    if demean:
-        X -= X.mean()                  # automatically demean's X
-    n = df or X.shape[0]
-
-    if method == "unbiased":        # this is df_resid ie., n - p
-        def denom(k):
-            return n - k
-    else:
-        def denom(k):
-            return n
-    if X.ndim > 1 and X.shape[1] != 1:
-        raise ValueError("expecting a vector to estimate AR parameters")
+    assert X.ndim == 2
+    denom = X.shape[-1] - np.arange(order + 1)
     r = np.zeros(order + 1, np.float64)
-    r[0] = (X ** 2).sum() / denom(0)
-    for k in range(1, order + 1):
-        r[k] = (X[0:-k] * X[k:]).sum() / denom(k)
-    R = toeplitz(r[:-1])
-
-    rho = np.linalg.solve(R, r[1:])
+    for di, d in enumerate(X):
+        d -= d.mean()
+        r[0] += np.dot(d, d)
+        for k in range(1, order + 1):
+            r[k] += np.dot(d[0:-k], d[k:])
+    r /= denom * len(X)
+    rho = linalg.solve(linalg.toeplitz(r[:-1]), r[1:])
     sigmasq = r[0] - (r[1:] * rho).sum()
-    if inv:
-        return rho, np.sqrt(sigmasq), np.linalg.inv(R)
-    else:
-        return rho, np.sqrt(sigmasq)
-
-
-def ar_raw(raw, order, picks, tmin=None, tmax=None):
-    """Fit AR model on raw data
-
-    Fit AR models for each channels and returns the models
-    coefficients for each of them.
-
-    Parameters
-    ----------
-    raw : Raw instance
-        The raw data
-    order : int
-        The AR model order
-    picks : array-like of int
-        The channels indices to include
-    tmin : float
-        The beginning of time interval in seconds.
-    tmax : float
-        The end of time interval in seconds.
-
-    Returns
-    -------
-    coefs : array
-        Sets of coefficients for each channel
-    """
-    start, stop = None, None
-    if tmin is not None:
-        start = raw.time_as_index(tmin)[0]
-    if tmax is not None:
-        stop = raw.time_as_index(tmax)[0] + 1
-    data, times = raw[picks, start:stop]
-
-    coefs = np.empty((len(data), order))
-    for k, d in enumerate(data):
-        this_coefs, _ = yule_walker(d, order=order)
-        coefs[k, :] = this_coefs
-    return coefs
+    return rho, np.sqrt(sigmasq)
 
 
 @verbose
 def fit_iir_model_raw(raw, order=2, picks=None, tmin=None, tmax=None,
                       verbose=None):
-    """Fits an AR model to raw data and creates the corresponding IIR filter
+    r"""Fit an AR model to raw data and creates the corresponding IIR filter.
 
-    The computed filter is the average filter for all the picked channels.
-    The frequency response is given by:
+    The computed filter is fitted to data from all of the picked channels,
+    with frequency response given by the standard IIR formula:
 
     .. math::
 
-        H(e^{jw}) = \\frac{1}{a[0] + a[1]e^{-jw} + ...
-                                  + a[n]e^{-jnw}}
+        H(e^{jw}) = \frac{1}{a[0] + a[1]e^{-jw} + ... + a[n]e^{-jnw}}
 
     Parameters
     ----------
@@ -148,7 +55,8 @@ def fit_iir_model_raw(raw, order=2, picks=None, tmin=None, tmax=None,
     tmax : float
         The end of time interval in seconds.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -157,9 +65,19 @@ def fit_iir_model_raw(raw, order=2, picks=None, tmin=None, tmax=None,
     a : ndarray
         Denominator filter coefficients
     """
+    from ..cov import _apply_scaling_array
+    start, stop = None, None
+    if tmin is not None:
+        start = raw.time_as_index(tmin)[0]
+    if tmax is not None:
+        stop = raw.time_as_index(tmax)[0] + 1
     if picks is None:
-        picks = pick_types(raw.info, meg=True, eeg=True)
-    coefs = ar_raw(raw, order=order, picks=picks, tmin=tmin, tmax=tmax)
-    mean_coefs = np.mean(coefs, axis=0)  # mean model across channels
-    a = np.concatenate(([1.], -mean_coefs))  # filter coefficients
-    return np.array([1.]), a
+        picks = _pick_data_channels(raw.info, exclude='bads')
+    data = raw[picks, start:stop][0]
+    # rescale data to similar levels
+    picks_list = _picks_by_type(pick_info(raw.info, picks))
+    scalings = _handle_default('scalings_cov_rank', None)
+    _apply_scaling_array(data, picks_list=picks_list, scalings=scalings)
+    # do the fitting
+    coeffs, _ = _yule_walker(data, order=order)
+    return np.array([1.]), np.concatenate(([1.], -coeffs))
diff --git a/mne/time_frequency/csd.py b/mne/time_frequency/csd.py
index 7f618ec..2ac6224 100644
--- a/mne/time_frequency/csd.py
+++ b/mne/time_frequency/csd.py
@@ -5,19 +5,17 @@
 import copy as cp
 
 import numpy as np
-from scipy.fftpack import fftfreq
 
 from ..io.pick import pick_types
-from ..utils import logger, verbose, warn
+from ..utils import logger, verbose, warn, _freqs_dep
 from ..time_frequency.multitaper import (dpss_windows, _mt_spectra,
                                          _csd_from_mt, _psd_from_mt_adaptive)
 
-from ..utils import deprecated
 from ..externals.six.moves import xrange as range
 
 
 class CrossSpectralDensity(object):
-    """Cross-spectral density
+    """Cross-spectral density.
 
     Parameters
     ----------
@@ -29,47 +27,44 @@ class CrossSpectralDensity(object):
         List of projectors used in CSD calculation.
     bads :
         List of bad channels.
-    frequencies : float | list of float
+    freqs : float | list of float
         Frequency or frequencies for which the CSD matrix was calculated. If a
         list is passed, data is a sum across CSD matrices for all frequencies.
     n_fft : int
         Length of the FFT used when calculating the CSD matrix.
     """
-    def __init__(self, data, ch_names, projs, bads, frequencies, n_fft):
+
+    def __init__(self, data, ch_names, projs, bads, freqs,
+                 n_fft, frequencies=None):  # noqa: D102
+        freqs = _freqs_dep(freqs, frequencies)
         self.data = data
         self.dim = len(data)
         self.ch_names = cp.deepcopy(ch_names)
         self.projs = cp.deepcopy(projs)
         self.bads = cp.deepcopy(bads)
-        self.frequencies = np.atleast_1d(np.copy(frequencies))
+        self.freqs = np.atleast_1d(np.copy(freqs))
         self.n_fft = n_fft
 
-    def __repr__(self):
-        s = 'frequencies : %s' % self.frequencies
+    @property
+    def frequencies(self):
+        """Deprecated and will be removed in 0.16. Use freqs."""
+        warn('frequencies is deprecated and will be removed in 0.16, use'
+             'freqs instead', DeprecationWarning)
+        return self.freqs
+
+    def __repr__(self):  # noqa: D105
+        s = 'frequencies : %s' % self.freqs
         s += ', size : %s x %s' % self.data.shape
         s += ', data : %s' % self.data
         return '<CrossSpectralDensity  |  %s>' % s
 
 
- at deprecated(("compute_epochs_csd has been deprecated and will be removed in "
-             "0.14, use csd_epochs instead."))
- at verbose
-def compute_epochs_csd(epochs, mode='multitaper', fmin=0, fmax=np.inf,
-                       fsum=True, tmin=None, tmax=None, n_fft=None,
-                       mt_bandwidth=None, mt_adaptive=False, mt_low_bias=True,
-                       projs=None, verbose=None):
-    return csd_epochs(epochs, mode=mode, fmin=fmin, fmax=fmax,
-                      fsum=fsum, tmin=tmin, tmax=tmax, n_fft=n_fft,
-                      mt_bandwidth=mt_bandwidth, mt_adaptive=mt_adaptive,
-                      mt_low_bias=mt_low_bias, projs=projs, verbose=verbose)
-
-
 @verbose
 def csd_epochs(epochs, mode='multitaper', fmin=0, fmax=np.inf,
                fsum=True, tmin=None, tmax=None, n_fft=None,
                mt_bandwidth=None, mt_adaptive=False, mt_low_bias=True,
                projs=None, verbose=None):
-    """Estimate cross-spectral density from epochs
+    """Estimate cross-spectral density from epochs.
 
     Note: Baseline correction should be used when creating the Epochs.
           Otherwise the computed cross-spectral density will be inaccurate.
@@ -112,7 +107,8 @@ def csd_epochs(epochs, mode='multitaper', fmin=0, fmax=np.inf,
         List of projectors to use in CSD calculation, or None to indicate that
         the projectors from the epochs should be inherited.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -159,10 +155,10 @@ def csd_epochs(epochs, mode='multitaper', fmin=0, fmax=np.inf,
 
     # Preparing frequencies of interest
     sfreq = epochs.info['sfreq']
-    orig_frequencies = fftfreq(n_fft, 1. / sfreq)
-    freq_mask = (orig_frequencies > fmin) & (orig_frequencies < fmax)
-    frequencies = orig_frequencies[freq_mask]
-    n_freqs = len(frequencies)
+    orig_freqs = np.fft.rfftfreq(n_fft, 1. / sfreq)
+    freq_mask = (orig_freqs > fmin) & (orig_freqs < fmax)
+    freqs = orig_freqs[freq_mask]
+    n_freqs = len(freqs)
 
     if n_freqs == 0:
         raise ValueError('No discrete fourier transform results within '
@@ -178,7 +174,7 @@ def csd_epochs(epochs, mode='multitaper', fmin=0, fmax=np.inf,
                          dtype=complex)
 
     # Picking frequencies of interest
-    freq_mask_mt = freq_mask[orig_frequencies >= 0]
+    freq_mask_mt = freq_mask[orig_freqs >= 0]
 
     # Compute CSD for each epoch
     n_epochs = 0
@@ -211,15 +207,14 @@ def csd_epochs(epochs, mode='multitaper', fmin=0, fmax=np.inf,
         csd_mean_fsum = np.sum(csds_mean, 2)
         csd = CrossSpectralDensity(csd_mean_fsum, ch_names, projs,
                                    epochs.info['bads'],
-                                   frequencies=frequencies, n_fft=n_fft)
+                                   freqs=freqs, n_fft=n_fft)
         return csd
     else:
         csds = []
         for i in range(n_freqs):
             csds.append(CrossSpectralDensity(csds_mean[:, :, i], ch_names,
                                              projs, epochs.info['bads'],
-                                             frequencies=frequencies[i],
-                                             n_fft=n_fft))
+                                             freqs=freqs[i], n_fft=n_fft))
         return csds
 
 
@@ -265,7 +260,7 @@ def csd_array(X, sfreq, mode='multitaper', fmin=0, fmax=np.inf,
         Only use tapers with more than 90% spectral concentration within
         bandwidth. Only used in 'multitaper' mode.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`).
 
     Returns
     -------
@@ -273,8 +268,7 @@ def csd_array(X, sfreq, mode='multitaper', fmin=0, fmax=np.inf,
         The computed cross spectral-density (either summed or not).
     freqs : array
         Frequencies the cross spectral-density is evaluated at.
-    """  # noqa
-
+    """  # noqa: E501
     # Check correctness of input data and parameters
     if fmax < fmin:
         raise ValueError('fmax must be larger than fmin')
@@ -286,10 +280,10 @@ def csd_array(X, sfreq, mode='multitaper', fmin=0, fmax=np.inf,
 
     # Preparing frequencies of interest
     n_fft = n_times if n_fft is None else n_fft
-    orig_frequencies = fftfreq(n_fft, 1. / sfreq)
-    freq_mask = (orig_frequencies > fmin) & (orig_frequencies < fmax)
-    frequencies = orig_frequencies[freq_mask]
-    n_freqs = len(frequencies)
+    orig_freqs = np.fft.rfftfreq(n_fft, 1. / sfreq)
+    freq_mask = (orig_freqs > fmin) & (orig_freqs < fmax)
+    freqs = orig_freqs[freq_mask]
+    n_freqs = len(freqs)
 
     if n_freqs == 0:
         raise ValueError('No discrete fourier transform results within '
@@ -304,7 +298,7 @@ def csd_array(X, sfreq, mode='multitaper', fmin=0, fmax=np.inf,
     csds_mean = np.zeros((n_series, n_series, n_freqs), dtype=complex)
 
     # Picking frequencies of interest
-    freq_mask_mt = freq_mask[orig_frequencies >= 0]
+    freq_mask_mt = freq_mask[orig_freqs >= 0]
 
     # Compute CSD for each trial
     for xi in X:
@@ -332,12 +326,12 @@ def csd_array(X, sfreq, mode='multitaper', fmin=0, fmax=np.inf,
     if fsum is True:
         csds_mean = np.sum(csds_mean, 2)
 
-    return csds_mean, frequencies
+    return csds_mean, freqs
 
 
 def _compute_csd_params(n_times, sfreq, mode, mt_bandwidth, mt_low_bias,
                         mt_adaptive):
-    """ Auxliary function to compute windowing and multitaper parameters.
+    """Compute windowing and multitaper parameters.
 
     Parameters
     ----------
@@ -406,10 +400,10 @@ def _compute_csd_params(n_times, sfreq, mode, mt_bandwidth, mt_low_bias,
 
 def _csd_array(x, sfreq, window_fun, eigvals, freq_mask, freq_mask_mt, n_fft,
                mode, mt_adaptive):
-    """ Calculating Fourier transform using multitaper module.
+    """Calculate Fourier transform using multitaper module.
 
-        The arguments correspond to the values in `compute_csd_epochs` and
-        `csd_array`.
+    The arguments correspond to the values in `compute_csd_epochs` and
+    `csd_array`.
     """
     x_mt, _ = _mt_spectra(x, window_fun, sfreq, n_fft)
 
diff --git a/mne/time_frequency/multitaper.py b/mne/time_frequency/multitaper.py
index 945ff8b..906f4d4 100644
--- a/mne/time_frequency/multitaper.py
+++ b/mne/time_frequency/multitaper.py
@@ -4,21 +4,19 @@
 # Parts of this code were copied from NiTime http://nipy.sourceforge.net/nitime
 
 import numpy as np
-from scipy import fftpack, linalg
+from scipy import linalg
 
 from ..parallel import parallel_func
-from ..utils import sum_squared, warn
+from ..utils import sum_squared, warn, verbose
 
 
 def tridisolve(d, e, b, overwrite_b=True):
-    """
-    Symmetric tridiagonal system solver, from Golub and Van Loan pg 157
+    """Symmetric tridiagonal system solver, from Golub and Van Loan p157.
 
-    Note: Copied from NiTime
+    .. note:: Copied from NiTime.
 
     Parameters
     ----------
-
     d : ndarray
       main diagonal stored in d[:]
     e : ndarray
@@ -28,7 +26,6 @@ def tridisolve(d, e, b, overwrite_b=True):
 
     Returns
     -------
-
     x : ndarray
       Solution to Ax = b (if overwrite_b is False). Otherwise solution is
       stored in previous RHS vector b
@@ -59,14 +56,15 @@ def tridisolve(d, e, b, overwrite_b=True):
 
 
 def tridi_inverse_iteration(d, e, w, x0=None, rtol=1e-8):
-    """Perform an inverse iteration to find the eigenvector corresponding
-    to the given eigenvalue in a symmetric tridiagonal system.
+    """Perform an inverse iteration.
+
+    This will find the eigenvector corresponding to the given eigenvalue
+    in a symmetric tridiagonal system.
 
-    Note: Copied from NiTime
+    ..note:: Copied from NiTime.
 
     Parameters
     ----------
-
     d : ndarray
       main diagonal of the tridiagonal system
     e : ndarray
@@ -80,10 +78,8 @@ def tridi_inverse_iteration(d, e, w, x0=None, rtol=1e-8):
 
     Returns
     -------
-
     e: ndarray
       The converged eigenvector
-
     """
     eig_diag = d - w
     if x0 is None:
@@ -103,11 +99,12 @@ def tridi_inverse_iteration(d, e, w, x0=None, rtol=1e-8):
 
 def dpss_windows(N, half_nbw, Kmax, low_bias=True, interp_from=None,
                  interp_kind='linear'):
-    """
-    Returns the Discrete Prolate Spheroidal Sequences of orders [0,Kmax-1]
-    for a given frequency-spacing multiple NW and sequence length N.
+    """Compute Discrete Prolate Spheroidal Sequences.
 
-    Note: Copied from NiTime
+    Will give of orders [0,Kmax-1] for a given frequency-spacing multiple
+    NW and sequence length N.
+
+    .. note:: Copied from NiTime.
 
     Parameters
     ----------
@@ -146,6 +143,7 @@ def dpss_windows(N, half_nbw, Kmax, low_bias=True, interp_from=None,
     Volume 57 (1978), 1371430
     """
     from scipy import interpolate
+    from ..filter import next_fast_len
     Kmax = int(Kmax)
     W = float(half_nbw) / N
     nidx = np.arange(N, dtype='d')
@@ -229,9 +227,9 @@ def dpss_windows(N, half_nbw, Kmax, low_bias=True, interp_from=None,
 
     # compute autocorr using FFT (same as nitime.utils.autocorr(dpss) * N)
     rxx_size = 2 * N - 1
-    n_fft = 2 ** int(np.ceil(np.log2(rxx_size)))
-    dpss_fft = fftpack.fft(dpss, n_fft)
-    dpss_rxx = np.real(fftpack.ifft(dpss_fft * dpss_fft.conj()))
+    n_fft = next_fast_len(rxx_size)
+    dpss_fft = np.fft.rfft(dpss, n_fft)
+    dpss_rxx = np.fft.irfft(dpss_fft * dpss_fft.conj(), n_fft)
     dpss_rxx = dpss_rxx[:, :N]
 
     r = 4 * W * np.sinc(2 * W * nidx)
@@ -251,15 +249,12 @@ def dpss_windows(N, half_nbw, Kmax, low_bias=True, interp_from=None,
 
 def _psd_from_mt_adaptive(x_mt, eigvals, freq_mask, max_iter=150,
                           return_weights=False):
-    """
-    Perform an iterative procedure to compute the PSD from tapered spectra
-    using the optimal weights.
+    r"""Use iterative procedure to compute the PSD from tapered spectra.
 
-    Note: Modified from NiTime
+    .. note:: Modified from NiTime.
 
     Parameters
     ----------
-
     x_mt : array, shape=(n_signals, n_tapers, n_freqs)
        The DFTs of the tapered sequences (only positive frequencies)
     eigvals : array, length n_tapers
@@ -280,7 +275,6 @@ def _psd_from_mt_adaptive(x_mt, eigvals, freq_mask, max_iter=150,
 
     Notes
     -----
-
     The weights to use for making the multitaper estimate, such that
     :math:`S_{mt} = \sum_{k} |w_k|^2S_k^{mt} / \sum_{k} |w_k|^2`
     """
@@ -363,7 +357,7 @@ def _psd_from_mt_adaptive(x_mt, eigvals, freq_mask, max_iter=150,
 
 
 def _psd_from_mt(x_mt, weights):
-    """ compute PSD from tapered spectra
+    """Compute PSD from tapered spectra.
 
     Parameters
     ----------
@@ -385,7 +379,7 @@ def _psd_from_mt(x_mt, weights):
 
 
 def _csd_from_mt(x_mt, y_mt, weights_x, weights_y):
-    """ Compute CSD from tapered spectra
+    """Compute CSD from tapered spectra.
 
     Parameters
     ----------
@@ -411,7 +405,7 @@ def _csd_from_mt(x_mt, y_mt, weights_x, weights_y):
 
 
 def _mt_spectra(x, dpss, sfreq, n_fft=None):
-    """ Compute tapered spectra
+    """Compute tapered spectra.
 
     Parameters
     ----------
@@ -432,7 +426,6 @@ def _mt_spectra(x, dpss, sfreq, n_fft=None):
     freqs : array
         The frequency points in Hz of the spectra
     """
-
     if n_fft is None:
         n_fft = x.shape[1]
 
@@ -440,24 +433,26 @@ def _mt_spectra(x, dpss, sfreq, n_fft=None):
     x = x - np.mean(x, axis=-1)[:, np.newaxis]
 
     # only keep positive frequencies
-    freqs = fftpack.fftfreq(n_fft, 1. / sfreq)
-    freq_mask = (freqs >= 0)
-    freqs = freqs[freq_mask]
+    freqs = np.fft.rfftfreq(n_fft, 1. / sfreq)
 
     # The following is equivalent to this, but uses less memory:
     # x_mt = fftpack.fft(x[:, np.newaxis, :] * dpss, n=n_fft)
     n_tapers = dpss.shape[0] if dpss.ndim > 1 else 1
-    x_mt = np.zeros((len(x), n_tapers, freq_mask.sum()), dtype=np.complex128)
+    x_mt = np.zeros((len(x), n_tapers, len(freqs)), dtype=np.complex128)
     for idx, sig in enumerate(x):
-        x_mt[idx] = fftpack.fft(sig[np.newaxis, :] * dpss,
-                                n=n_fft)[:, freq_mask]
+        x_mt[idx] = np.fft.rfft(sig[np.newaxis, :] * dpss, n=n_fft)
+    # Adjust DC and maybe Nyquist, depending on one-sided transform
+    x_mt[:, :, 0] /= np.sqrt(2.)
+    if x.shape[1] % 2 == 0:
+        x_mt[:, :, -1] /= np.sqrt(2.)
     return x_mt, freqs
 
 
-def _psd_multitaper(x, sfreq, fmin=0, fmax=np.inf, bandwidth=None,
-                    adaptive=False, low_bias=True, normalization='length',
-                    n_jobs=1):
-    """Compute power spectrum density (PSD) using a multi-taper method
+ at verbose
+def psd_array_multitaper(x, sfreq, fmin=0, fmax=np.inf, bandwidth=None,
+                         adaptive=False, low_bias=True, normalization='length',
+                         n_jobs=1, verbose=None):
+    """Compute power spectrum density (PSD) using a multi-taper method.
 
     Parameters
     ----------
@@ -483,6 +478,9 @@ def _psd_multitaper(x, sfreq, fmin=0, fmax=np.inf, bandwidth=None,
         the signal (as in nitime).
     n_jobs : int
         Number of parallel jobs to use (only used if adaptive=True).
+    verbose : bool, str, int, or None
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -494,11 +492,11 @@ def _psd_multitaper(x, sfreq, fmin=0, fmax=np.inf, bandwidth=None,
 
     See Also
     --------
-    mne.io.Raw.plot_psd, mne.Epochs.plot_psd, csd_epochs
+    mne.io.Raw.plot_psd, mne.Epochs.plot_psd, csd_epochs, psd_multitaper
 
     Notes
     -----
-    .. versionadded:: 0.12.0
+    .. versionadded:: 0.14.0
     """
     if normalization not in ('length', 'full'):
         raise ValueError('Normalization must be "length" or "full", not %s'
@@ -522,9 +520,8 @@ def _psd_multitaper(x, sfreq, fmin=0, fmax=np.inf, bandwidth=None,
     dpss, eigvals = dpss_windows(n_times, half_nbw, n_tapers_max,
                                  low_bias=low_bias)
 
-    # descide which frequencies to keep
-    freqs = fftpack.fftfreq(x.shape[1], 1. / sfreq)
-    freqs = freqs[(freqs >= 0)]  # what we get from _mt_spectra
+    # decide which frequencies to keep
+    freqs = np.fft.rfftfreq(x.shape[1], 1. / sfreq)
     freq_mask = (freqs >= fmin) & (freqs <= fmax)
     freqs = freqs[freq_mask]
 
@@ -559,3 +556,88 @@ def _psd_multitaper(x, sfreq, fmin=0, fmax=np.inf, bandwidth=None,
     if ndim_in == 1:
         psd = psd[0]
     return psd, freqs
+
+
+ at verbose
+def tfr_array_multitaper(epoch_data, sfreq, freqs, n_cycles=7.0,
+                         zero_mean=True, time_bandwidth=None, use_fft=True,
+                         decim=1, output='complex', n_jobs=1,
+                         frequencies=None, verbose=None):
+    """Compute time-frequency transforms using wavelets and multitaper windows.
+
+    Uses Morlet wavelets windowed with multiple DPSS tapers.
+
+    Parameters
+    ----------
+    epoch_data : array of shape (n_epochs, n_channels, n_times)
+        The epochs.
+    sfreq : float | int
+        Sampling frequency of the data.
+    freqs : array-like of floats, shape (n_freqs)
+        The frequencies.
+    n_cycles : float | array of float
+        Number of cycles  in the Morlet wavelet. Fixed number or one per
+        frequency. Defaults to 7.0.
+    zero_mean : bool
+        If True, make sure the wavelets have a mean of zero. Defaults to True.
+    time_bandwidth : float
+        If None, will be set to 4.0 (3 tapers). Time x (Full) Bandwidth
+        product. The number of good tapers (low-bias) is chosen automatically
+        based on this to equal floor(time_bandwidth - 1). Defaults to None
+    use_fft : bool
+        Use the FFT for convolutions or not. Defaults to True.
+    decim : int | slice
+        To reduce memory usage, decimation factor after time-frequency
+        decomposition. Defaults to 1.
+        If `int`, returns tfr[..., ::decim].
+        If `slice`, returns tfr[..., decim].
+
+        .. note::
+            Decimation may create aliasing artifacts, yet decimation
+            is done after the convolutions.
+
+    output : str, defaults to 'complex'
+
+        * 'complex' : single trial complex.
+        * 'power' : single trial power.
+        * 'phase' : single trial phase.
+        * 'avg_power' : average of single trial power.
+        * 'itc' : inter-trial coherence.
+        * 'avg_power_itc' : average of single trial power and inter-trial
+          coherence across trials.
+
+    n_jobs : int
+        The number of epochs to process at the same time. The parallelization
+        is implemented across channels. Defaults to 1.
+    verbose : bool, str, int, or None, defaults to None
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
+
+    Returns
+    -------
+    out : array
+        Time frequency transform of epoch_data. If output is in ['complex',
+        'phase', 'power'], then shape of out is (n_epochs, n_chans, n_freqs,
+        n_times), else it is (n_chans, n_freqs, n_times). If output is
+        'avg_power_itc', the real values code for 'avg_power' and the
+        imaginary values code for the 'itc': out = avg_power + i * itc
+
+    See Also
+    --------
+    mne.time_frequency.tfr_multitaper
+    mne.time_frequency.tfr_morlet
+    mne.time_frequency.tfr_array_morlet
+    mne.time_frequency.tfr_stockwell
+    mne.time_frequency.tfr_array_stockwell
+
+    Notes
+    -----
+    .. versionadded:: 0.14.0
+    """
+    from .tfr import _compute_tfr
+    return _compute_tfr(epoch_data, freqs, sfreq=sfreq,
+                        method='multitaper', n_cycles=n_cycles,
+                        zero_mean=zero_mean, time_bandwidth=time_bandwidth,
+                        use_fft=use_fft, decim=decim, output=output,
+                        n_jobs=n_jobs, frequencies=frequencies,
+                        verbose=verbose)
diff --git a/mne/time_frequency/psd.py b/mne/time_frequency/psd.py
index 950efc3..a6116fd 100644
--- a/mne/time_frequency/psd.py
+++ b/mne/time_frequency/psd.py
@@ -7,39 +7,38 @@ import numpy as np
 from ..parallel import parallel_func
 from ..io.pick import _pick_data_channels
 from ..utils import logger, verbose, _time_mask
-from .multitaper import _psd_multitaper
-
-
-def _pwelch(epoch, noverlap, nfft, fs, freq_mask, welch_fun):
-    """Aux function"""
-    return welch_fun(epoch, nperseg=nfft, noverlap=noverlap,
-                     nfft=nfft, fs=fs)[1][..., freq_mask]
-
-
-def _compute_psd(data, fmin, fmax, Fs, n_fft, psd, n_overlap, pad_to):
-    """Compute the PSD"""
-    out = [psd(d, Fs=Fs, NFFT=n_fft, noverlap=n_overlap, pad_to=pad_to)
-           for d in data]
-    psd = np.array([o[0] for o in out])
-    freqs = out[0][1]
-    mask = (freqs >= fmin) & (freqs <= fmax)
-    freqs = freqs[mask]
-    return psd[:, mask], freqs
-
-
-def _check_nfft(n, n_fft, n_overlap):
-    """Helper to make sure n_fft and n_overlap make sense"""
-    n_fft = n if n_fft > n else n_fft
-    n_overlap = n_fft - 1 if n_overlap >= n_fft else n_overlap
-    return n_fft, n_overlap
-
-
-def _check_psd_data(inst, tmin, tmax, picks, proj):
-    """Helper to do checks on PSD data / pull arrays from inst"""
-    from ..io.base import _BaseRaw
-    from ..epochs import _BaseEpochs
+from ..fixes import get_spectrogram
+from .multitaper import psd_array_multitaper
+
+
+def _psd_func(epoch, noverlap, n_per_seg, nfft, fs, freq_mask, func):
+    """Aux function."""
+    return func(epoch, fs=fs, nperseg=n_per_seg, noverlap=noverlap,
+                nfft=nfft, window='hamming')[2][..., freq_mask, :]
+
+
+def _check_nfft(n, n_fft, n_per_seg, n_overlap):
+    """Ensure n_fft, n_per_seg and n_overlap make sense."""
+    if n_per_seg is None and n_fft > n:
+        raise ValueError(('If n_per_seg is None n_fft is not allowed to be > '
+                          'n_times. If you want zero-padding, you have to set '
+                          'n_per_seg to relevant length. Got n_fft of %d while'
+                          ' signal length is %d.') % (n_fft, n))
+    n_per_seg = n_fft if n_per_seg is None or n_per_seg > n_fft else n_per_seg
+    n_per_seg = n if n_per_seg > n else n_per_seg
+    if n_overlap >= n_per_seg:
+        raise ValueError(('n_overlap cannot be greater than n_per_seg (or '
+                          'n_fft). Got n_overlap of %d while n_per_seg is '
+                          '%d.') % (n_overlap, n_per_seg))
+    return n_fft, n_per_seg, n_overlap
+
+
+def _check_psd_data(inst, tmin, tmax, picks, proj, reject_by_annotation=False):
+    """Check PSD data / pull arrays from inst."""
+    from ..io.base import BaseRaw
+    from ..epochs import BaseEpochs
     from ..evoked import Evoked
-    if not isinstance(inst, (_BaseEpochs, _BaseRaw, Evoked)):
+    if not isinstance(inst, (BaseEpochs, BaseRaw, Evoked)):
         raise ValueError('epochs must be an instance of Epochs, Raw, or'
                          'Evoked. Got type {0}'.format(type(inst)))
 
@@ -51,21 +50,25 @@ def _check_psd_data(inst, tmin, tmax, picks, proj):
         inst = inst.copy().apply_proj()
 
     sfreq = inst.info['sfreq']
-    if isinstance(inst, _BaseRaw):
+    if isinstance(inst, BaseRaw):
         start, stop = np.where(time_mask)[0][[0, -1]]
-        data, times = inst[picks, start:(stop + 1)]
-    elif isinstance(inst, _BaseEpochs):
+        rba = 'NaN' if reject_by_annotation else reject_by_annotation
+        data = inst.get_data(picks, start, stop + 1, reject_by_annotation=rba)
+    elif isinstance(inst, BaseEpochs):
         data = inst.get_data()[:, picks][:, :, time_mask]
-    elif isinstance(inst, Evoked):
+    else:  # Evoked
         data = inst.data[picks][:, time_mask]
 
     return data, sfreq
 
 
-def _psd_welch(x, sfreq, fmin=0, fmax=np.inf, n_fft=256, n_overlap=0,
-               n_jobs=1):
+ at verbose
+def psd_array_welch(x, sfreq, fmin=0, fmax=np.inf, n_fft=256, n_overlap=0,
+                    n_per_seg=None, n_jobs=1, verbose=None):
     """Compute power spectral density (PSD) using Welch's method.
 
+    Parameters
+    ----------
     x : array, shape=(..., n_times)
         The data to compute PSD from.
     sfreq : float
@@ -75,15 +78,19 @@ def _psd_welch(x, sfreq, fmin=0, fmax=np.inf, n_fft=256, n_overlap=0,
     fmax : float
         The upper frequency of interest.
     n_fft : int
-        The length of the tapers ie. the windows. The smaller
-        it is the smoother are the PSDs. The default value is 256.
-        If ``n_fft > len(inst.times)``, it will be adjusted down to
-        ``len(inst.times)``.
+        The length of FFT used, must be ``>= n_per_seg`` (default: 256).
+        The segments will be zero-padded if ``n_fft > n_per_seg``.
     n_overlap : int
-        The number of points of overlap between blocks. Will be adjusted
-        to be <= n_fft. The default value is 0.
+        The number of points of overlap between segments. Will be adjusted
+        to be <= n_per_seg. The default value is 0.
+    n_per_seg : int | None
+        Length of each Welch segment (windowed with a Hamming window). Defaults
+        to None, which sets n_per_seg equal to n_fft.
     n_jobs : int
         Number of CPUs to use in the computation.
+    verbose : bool, str, int, or None
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -92,14 +99,19 @@ def _psd_welch(x, sfreq, fmin=0, fmax=np.inf, n_fft=256, n_overlap=0,
         be the same as input.
     freqs : ndarray, shape (n_freqs,)
         The frequencies.
+
+    Notes
+    -----
+    .. versionadded:: 0.14.0
     """
-    from scipy.signal import welch
+    spectrogram = get_spectrogram()
     dshape = x.shape[:-1]
     n_times = x.shape[-1]
     x = x.reshape(-1, n_times)
 
     # Prep the PSD
-    n_fft, n_overlap = _check_nfft(n_times, n_fft, n_overlap)
+    n_fft, n_per_seg, n_overlap = _check_nfft(n_times, n_fft, n_per_seg,
+                                              n_overlap)
     win_size = n_fft / float(sfreq)
     logger.info("Effective window size : %0.3f (s)" % win_size)
     freqs = np.arange(n_fft // 2 + 1, dtype=float) * (sfreq / n_fft)
@@ -107,26 +119,28 @@ def _psd_welch(x, sfreq, fmin=0, fmax=np.inf, n_fft=256, n_overlap=0,
     freqs = freqs[freq_mask]
 
     # Parallelize across first N-1 dimensions
-    parallel, my_pwelch, n_jobs = parallel_func(_pwelch, n_jobs=n_jobs)
+    parallel, my_psd_func, n_jobs = parallel_func(_psd_func, n_jobs=n_jobs)
     x_splits = np.array_split(x, n_jobs)
-    f_psd = parallel(my_pwelch(d, noverlap=n_overlap, nfft=n_fft,
-                     fs=sfreq, freq_mask=freq_mask,
-                     welch_fun=welch)
-                     for d in x_splits)
-
-    # Combining/reshaping to original data shape
-    psds = np.concatenate(f_psd, axis=0)
-    psds = psds.reshape(np.hstack([dshape, -1]))
+    f_spectrogram = parallel(my_psd_func(d, noverlap=n_overlap, nfft=n_fft,
+                                         fs=sfreq, freq_mask=freq_mask,
+                                         func=spectrogram, n_per_seg=n_per_seg)
+                             for d in x_splits)
+
+    # Combining, reducing windows and reshaping to original data shape
+    psds = np.concatenate([np.nanmean(f_s, axis=-1)
+                           for f_s in f_spectrogram], axis=0)
+    psds.shape = dshape + (-1,)
     return psds, freqs
 
 
 @verbose
 def psd_welch(inst, fmin=0, fmax=np.inf, tmin=None, tmax=None, n_fft=256,
-              n_overlap=0, picks=None, proj=False, n_jobs=1, verbose=None):
+              n_overlap=0, n_per_seg=None, picks=None, proj=False, n_jobs=1,
+              reject_by_annotation=True, verbose=None):
     """Compute the power spectral density (PSD) using Welch's method.
 
-    Calculates periodigrams for a sliding window over the
-    time dimension, then averages them together for each channel/epoch.
+    Calculates periodograms for a sliding window over the time dimension, then
+    averages them together for each channel/epoch.
 
     Parameters
     ----------
@@ -141,13 +155,16 @@ def psd_welch(inst, fmin=0, fmax=np.inf, tmin=None, tmax=None, n_fft=256,
     tmax : float | None
         Max time of interest
     n_fft : int
-        The length of the tapers ie. the windows. The smaller
-        it is the smoother are the PSDs. The default value is 256.
-        If ``n_fft > len(inst.times)``, it will be adjusted down to
-        ``len(inst.times)``.
+        The length of FFT used, must be ``>= n_per_seg`` (default: 256).
+        The segments will be zero-padded if ``n_fft > n_per_seg``.
+        If n_per_seg is None, n_fft must be >= number of time points
+        in the data.
     n_overlap : int
-        The number of points of overlap between blocks. Will be adjusted
-        to be <= n_fft. The default value is 0.
+        The number of points of overlap between segments. Will be adjusted
+        to be <= n_per_seg. The default value is 0.
+    n_per_seg : int | None
+        Length of each Welch segment (windowed with a Hamming window). Defaults
+        to None, which sets n_per_seg equal to n_fft.
     picks : array-like of int | None
         The selection of channels to include in the computation.
         If None, take all channels.
@@ -155,8 +172,16 @@ def psd_welch(inst, fmin=0, fmax=np.inf, tmin=None, tmax=None, n_fft=256,
         Apply SSP projection vectors. If inst is ndarray this is not used.
     n_jobs : int
         Number of CPUs to use in the computation.
+    reject_by_annotation : bool
+        Whether to omit bad segments from the data while computing the
+        PSD. If True, annotated segments with a description that starts
+        with 'bad' are omitted. Has no effect if ``inst`` is an Epochs or
+        Evoked object. Defaults to True.
+
+        .. versionadded:: 0.15.0
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -170,16 +195,18 @@ def psd_welch(inst, fmin=0, fmax=np.inf, tmin=None, tmax=None, n_fft=256,
     See Also
     --------
     mne.io.Raw.plot_psd, mne.Epochs.plot_psd, psd_multitaper,
-    csd_epochs
+    csd_epochs, psd_array_welch
 
     Notes
     -----
     .. versionadded:: 0.12.0
     """
     # Prep data
-    data, sfreq = _check_psd_data(inst, tmin, tmax, picks, proj)
-    return _psd_welch(data, sfreq, fmin=fmin, fmax=fmax, n_fft=n_fft,
-                      n_overlap=n_overlap, n_jobs=n_jobs)
+    data, sfreq = _check_psd_data(inst, tmin, tmax, picks, proj,
+                                  reject_by_annotation=reject_by_annotation)
+    return psd_array_welch(data, sfreq, fmin=fmin, fmax=fmax, n_fft=n_fft,
+                           n_overlap=n_overlap, n_per_seg=n_per_seg,
+                           n_jobs=n_jobs, verbose=verbose)
 
 
 @verbose
@@ -226,7 +253,8 @@ def psd_multitaper(inst, fmin=0, fmax=np.inf, tmin=None, tmax=None,
     n_jobs : int
         Number of CPUs to use in the computation.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -249,7 +277,8 @@ def psd_multitaper(inst, fmin=0, fmax=np.inf, tmin=None, tmax=None,
 
     See Also
     --------
-    mne.io.Raw.plot_psd, mne.Epochs.plot_psd, psd_welch, csd_epochs
+    mne.io.Raw.plot_psd, mne.Epochs.plot_psd, psd_welch, csd_epochs,
+    psd_array_multitaper
 
     Notes
     -----
@@ -257,7 +286,7 @@ def psd_multitaper(inst, fmin=0, fmax=np.inf, tmin=None, tmax=None,
     """
     # Prep data
     data, sfreq = _check_psd_data(inst, tmin, tmax, picks, proj)
-    return _psd_multitaper(data, sfreq, fmin=fmin, fmax=fmax,
-                           bandwidth=bandwidth, adaptive=adaptive,
-                           low_bias=low_bias,
-                           normalization=normalization,  n_jobs=n_jobs)
+    return psd_array_multitaper(data, sfreq, fmin=fmin, fmax=fmax,
+                                bandwidth=bandwidth, adaptive=adaptive,
+                                low_bias=low_bias, normalization=normalization,
+                                n_jobs=n_jobs, verbose=verbose)
diff --git a/mne/time_frequency/stft.py b/mne/time_frequency/stft.py
index 83e2733..1131c20 100644
--- a/mne/time_frequency/stft.py
+++ b/mne/time_frequency/stft.py
@@ -22,7 +22,8 @@ def stft(x, wsize, tstep=None, verbose=None):
         step between successive windows in samples (must be a multiple of 2,
         a divider of wsize and smaller than wsize/2) (default: wsize/2)
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -103,7 +104,7 @@ def stft(x, wsize, tstep=None, verbose=None):
 
 
 def istft(X, tstep=None, Tx=None):
-    """ISTFT Inverse Short-Term Fourier Transform using a sine window
+    """ISTFT Inverse Short-Term Fourier Transform using a sine window.
 
     Parameters
     ----------
@@ -184,8 +185,8 @@ def istft(X, tstep=None, Tx=None):
     return x
 
 
-def stftfreq(wsize, sfreq=None):
-    """Frequencies of stft transformation
+def stftfreq(wsize, sfreq=None):  # noqa: D401
+    """Frequencies of stft transformation.
 
     Parameters
     ----------
@@ -200,7 +201,6 @@ def stftfreq(wsize, sfreq=None):
     freqs : array
         The positive frequencies returned by stft
 
-
     See Also
     --------
     stft
@@ -215,7 +215,7 @@ def stftfreq(wsize, sfreq=None):
 
 
 def stft_norm2(X):
-    """Compute L2 norm of STFT transform
+    """Compute L2 norm of STFT transform.
 
     It takes into account that stft only return positive frequencies.
     As we use tight frame this quantity is conserved by the stft.
diff --git a/mne/time_frequency/tests/test_ar.py b/mne/time_frequency/tests/test_ar.py
index 15d128d..d2d8fce 100644
--- a/mne/time_frequency/tests/test_ar.py
+++ b/mne/time_frequency/tests/test_ar.py
@@ -1,36 +1,52 @@
 import os.path as op
+
 import numpy as np
-from numpy.testing import assert_array_almost_equal
-from nose.tools import assert_true, assert_equal
+from numpy.testing import assert_array_almost_equal, assert_allclose
+from nose.tools import assert_equal
+from scipy.signal import lfilter
 
-from mne import io, pick_types
-from mne.time_frequency.ar import yule_walker, fit_iir_model_raw
-from mne.utils import requires_statsmodels, requires_patsy
+from mne import io
+from mne.time_frequency.ar import _yule_walker, fit_iir_model_raw
+from mne.utils import requires_version, run_tests_if_main
 
 
 raw_fname = op.join(op.dirname(__file__), '..', '..', 'io', 'tests', 'data',
                     'test_raw.fif')
 
 
- at requires_patsy
- at requires_statsmodels
+# 0.7 attempts to import nonexistent TimeSeries from Pandas 0.20
+ at requires_version('statsmodels', '0.8')
 def test_yule_walker():
     """Test Yule-Walker against statsmodels."""
     from statsmodels.regression.linear_model import yule_walker as sm_yw
     d = np.random.randn(100)
     sm_rho, sm_sigma = sm_yw(d, order=2)
-    rho, sigma = yule_walker(d, order=2)
+    rho, sigma = _yule_walker(d[np.newaxis], order=2)
     assert_array_almost_equal(sm_sigma, sigma)
     assert_array_almost_equal(sm_rho, rho)
 
 
 def test_ar_raw():
     """Test fitting AR model on raw data."""
-    raw = io.read_raw_fif(raw_fname, add_eeg_ref=False)
+    raw = io.read_raw_fif(raw_fname).crop(0, 2).load_data()
+    raw.pick_types(meg='grad')
     # pick MEG gradiometers
-    picks = pick_types(raw.info, meg='grad', exclude='bads')
-    picks = picks[:2]
-    tmin, tmax, order = 0, 10, 2
-    coefs = fit_iir_model_raw(raw, order, picks, tmin, tmax)[1][1:]
-    assert_equal(coefs.shape, (order,))
-    assert_true(0.9 < -coefs[0] < 1.1)
+    for order in (2, 5, 10):
+        coeffs = fit_iir_model_raw(raw, order)[1][1:]
+        assert_equal(coeffs.shape, (order,))
+        assert_allclose(-coeffs[0], 1., atol=0.5)
+    # let's make sure we're doing something reasonable: first, white noise
+    rng = np.random.RandomState(0)
+    raw._data = rng.randn(*raw._data.shape)
+    raw._data *= 1e-15
+    for order in (2, 5, 10):
+        coeffs = fit_iir_model_raw(raw, order)[1]
+        assert_allclose(coeffs, [1.] + [0.] * order, atol=2e-2)
+    # Now let's try pink noise
+    iir = [1, -1, 0.2]
+    raw._data = lfilter([1.], iir, raw._data)
+    for order in (2, 5, 10):
+        coeffs = fit_iir_model_raw(raw, order)[1]
+        assert_allclose(coeffs, iir + [0.] * (order - 2), atol=5e-2)
+
+run_tests_if_main()
diff --git a/mne/time_frequency/tests/test_csd.py b/mne/time_frequency/tests/test_csd.py
index 61dfae2..1b09d25 100644
--- a/mne/time_frequency/tests/test_csd.py
+++ b/mne/time_frequency/tests/test_csd.py
@@ -7,7 +7,7 @@ import warnings
 import mne
 
 from mne.io import read_raw_fif
-from mne.utils import sum_squared
+from mne.utils import sum_squared, run_tests_if_main
 from mne.time_frequency import csd_epochs, csd_array, tfr_morlet
 
 warnings.simplefilter('always')
@@ -18,7 +18,7 @@ event_fname = op.join(base_dir, 'test-eve.fif')
 
 def _get_data(mode='real'):
     """Get data."""
-    raw = read_raw_fif(raw_fname, add_eeg_ref=False)
+    raw = read_raw_fif(raw_fname)
     events = mne.read_events(event_fname)[0:100]
     if mode == 'real':
         # Read raw data
@@ -30,17 +30,15 @@ def _get_data(mode='real'):
 
         # Read several epochs
         event_id, tmin, tmax = 1, -0.2, 0.5
-        epochs = mne.Epochs(raw, events, event_id, tmin, tmax, proj=True,
-                            picks=picks, baseline=(None, 0), preload=True,
-                            reject=dict(grad=4000e-13, mag=4e-12),
-                            add_eeg_ref=False)
+        epochs = mne.Epochs(raw, events, event_id, tmin, tmax, picks=picks,
+                            preload=True,
+                            reject=dict(grad=4000e-13, mag=4e-12))
     elif mode == 'sin':
         # Create an epochs object with one epoch and one channel of artificial
         # data
         event_id, tmin, tmax = 1, 0.0, 1.0
-        epochs = mne.Epochs(raw, events[0:5], event_id, tmin, tmax, proj=True,
-                            picks=[0], baseline=(None, 0), preload=True,
-                            reject=dict(grad=4000e-13), add_eeg_ref=False)
+        epochs = mne.Epochs(raw, events[0:5], event_id, tmin, tmax,  picks=[0],
+                            preload=True, reject=dict(grad=4000e-13))
         freq = 10
         epochs._data = np.sin(2 * np.pi * freq *
                               epochs.times)[None, None, :]
@@ -100,15 +98,15 @@ def test_csd_epochs():
     # a given range when fsum=False
     csd_fsum = csd_epochs(epochs, mode='fourier', fmin=8, fmax=20, fsum=True)
     csds = csd_epochs(epochs, mode='fourier', fmin=8, fmax=20, fsum=False)
-    freqs = [csd.frequencies[0] for csd in csds]
+    freqs = [csd.freqs[0] for csd in csds]
 
     csd_sum = np.zeros_like(csd_fsum.data)
     for csd in csds:
         csd_sum += csd.data
 
     assert_equal(len(csds), 2)
-    assert_equal(len(csd_fsum.frequencies), 2)
-    assert_array_equal(csd_fsum.frequencies, freqs)
+    assert_equal(len(csd_fsum.freqs), 2)
+    assert_array_equal(csd_fsum.freqs, freqs)
     assert_array_equal(csd_fsum.data, csd_sum)
 
 
@@ -316,3 +314,5 @@ def test_csd_on_artificial_data():
                     delta = 0.004
                 assert_true(abs(signal_power_per_sample -
                                 mt_power_per_sample) < delta)
+
+run_tests_if_main()
diff --git a/mne/time_frequency/tests/test_multitaper.py b/mne/time_frequency/tests/test_multitaper.py
index e64e8ae..6a2e74b 100644
--- a/mne/time_frequency/tests/test_multitaper.py
+++ b/mne/time_frequency/tests/test_multitaper.py
@@ -1,7 +1,9 @@
+from distutils.version import LooseVersion
+import warnings
+
 import numpy as np
 from nose.tools import assert_raises
 from numpy.testing import assert_array_almost_equal
-from distutils.version import LooseVersion
 
 from mne.time_frequency import psd_multitaper
 from mne.time_frequency.multitaper import dpss_windows
@@ -12,7 +14,7 @@ from mne import create_info
 
 @requires_nitime
 def test_dpss_windows():
-    """ Test computation of DPSS windows """
+    """Test computation of DPSS windows."""
 
     import nitime as ni
     N = 1000
@@ -36,27 +38,27 @@ def test_dpss_windows():
 
 @requires_nitime
 def test_multitaper_psd():
-    """ Test multi-taper PSD computation """
-
+    """Test multi-taper PSD computation."""
     import nitime as ni
-    n_times = 1000
-    n_channels = 5
-    data = np.random.RandomState(0).randn(n_channels, n_times)
-    sfreq = 500
-    info = create_info(n_channels, sfreq, 'eeg')
-    raw = RawArray(data, info)
-    assert_raises(ValueError, psd_multitaper, raw, sfreq, normalization='foo')
-    ni_5 = (LooseVersion(ni.__version__) >= LooseVersion('0.5'))
-    norm = 'full' if ni_5 else 'length'
-
-    for adaptive, n_jobs in zip((False, True, True), (1, 1, 2)):
-        psd, freqs = psd_multitaper(raw, adaptive=adaptive,
-                                    n_jobs=n_jobs,
-                                    normalization=norm)
-        freqs_ni, psd_ni, _ = ni.algorithms.spectral.multi_taper_psd(
-            data, sfreq, adaptive=adaptive, jackknife=False)
-
-        # for some reason nitime returns n_times + 1 frequency points
-        # causing the value at 0 to be different
-        assert_array_almost_equal(psd[:, 1:], psd_ni[:, 1:-1], decimal=3)
-        assert_array_almost_equal(freqs, freqs_ni[:-1])
+    for n_times in (100, 101):
+        n_channels = 5
+        data = np.random.RandomState(0).randn(n_channels, n_times)
+        sfreq = 500
+        info = create_info(n_channels, sfreq, 'eeg')
+        raw = RawArray(data, info)
+        assert_raises(ValueError, psd_multitaper, raw, sfreq,
+                      normalization='foo')
+        ni_5 = (LooseVersion(ni.__version__) >= LooseVersion('0.5'))
+        norm = 'full' if ni_5 else 'length'
+        for adaptive, n_jobs in zip((False, True, True), (1, 1, 2)):
+            psd, freqs = psd_multitaper(raw, adaptive=adaptive,
+                                        n_jobs=n_jobs,
+                                        normalization=norm)
+            with warnings.catch_warnings(record=True):  # nitime integers
+                freqs_ni, psd_ni, _ = ni.algorithms.spectral.multi_taper_psd(
+                    data, sfreq, adaptive=adaptive, jackknife=False)
+            assert_array_almost_equal(psd, psd_ni, decimal=4)
+            if n_times % 2 == 0:
+                # nitime's frequency definitions must be incorrect,
+                # they give the same values for 100 and 101 samples
+                assert_array_almost_equal(freqs, freqs_ni)
diff --git a/mne/time_frequency/tests/test_psd.py b/mne/time_frequency/tests/test_psd.py
index 25b2356..36d70ba 100644
--- a/mne/time_frequency/tests/test_psd.py
+++ b/mne/time_frequency/tests/test_psd.py
@@ -1,22 +1,41 @@
 import numpy as np
 import os.path as op
-from numpy.testing import assert_array_almost_equal, assert_raises
-from nose.tools import assert_true
+from numpy.testing import (assert_array_almost_equal, assert_raises,
+                           assert_allclose)
+from nose.tools import assert_true, assert_equal
+import pytest
 
 from mne import pick_types, Epochs, read_events
 from mne.io import RawArray, read_raw_fif
-from mne.utils import requires_version, slow_test
-from mne.time_frequency import psd_welch, psd_multitaper
+from mne.utils import run_tests_if_main
+from mne.time_frequency import psd_welch, psd_multitaper, psd_array_welch
 
 base_dir = op.join(op.dirname(__file__), '..', '..', 'io', 'tests', 'data')
 raw_fname = op.join(base_dir, 'test_raw.fif')
 event_fname = op.join(base_dir, 'test-eve.fif')
 
 
- at requires_version('scipy', '0.12')
+def test_psd_nan():
+    """Test handling of NaN in psd_array_welch."""
+    n_samples, n_fft, n_overlap = 2048,  1024, 512
+    x = np.random.RandomState(0).randn(1, n_samples)
+    psds, freqs = psd_array_welch(
+        x[:n_fft + n_overlap], float(n_fft), n_fft=n_fft, n_overlap=n_overlap)
+    x[n_fft + n_overlap:] = np.nan  # what Raw.get_data() will give us
+    psds_2, freqs_2 = psd_array_welch(
+        x, float(n_fft), n_fft=n_fft, n_overlap=n_overlap)
+    assert_allclose(freqs, freqs_2)
+    assert_allclose(psds, psds_2)
+    # 1-d
+    psds_2, freqs_2 = psd_array_welch(
+        x[0], float(n_fft), n_fft=n_fft, n_overlap=n_overlap)
+    assert_allclose(freqs, freqs_2)
+    assert_allclose(psds[0], psds_2)
+
+
 def test_psd():
     """Tests the welch and multitaper PSD."""
-    raw = read_raw_fif(raw_fname, add_eeg_ref=False)
+    raw = read_raw_fif(raw_fname)
     picks_psd = [0, 1]
 
     # Populate raw with sinusoids
@@ -46,9 +65,9 @@ def test_psd():
         psds, freqs = func(raw, proj=False, **kws)
         psds_proj, freqs_proj = func(raw, proj=True, **kws)
 
-        assert_true(psds.shape == (len(kws['picks']), len(freqs)))
-        assert_true(np.sum(freqs < 0) == 0)
-        assert_true(np.sum(psds < 0) == 0)
+        assert_equal(psds.shape, (len(kws['picks']), len(freqs)))
+        assert_equal(np.sum(freqs < 0), 0)
+        assert_equal(np.sum(psds < 0), 0)
 
         # Is power found where it should be
         ixs_max = np.argmax(psds, axis=1)
@@ -62,18 +81,34 @@ def test_psd():
         # Array input shouldn't work
         assert_raises(ValueError, func, raw[:3, :20][0])
 
+    # test n_per_seg in psd_welch (and padding)
+    psds1, freqs1 = psd_welch(raw, proj=False, n_fft=128, n_per_seg=128,
+                              **kws_psd)
+    psds2, freqs2 = psd_welch(raw, proj=False, n_fft=256, n_per_seg=128,
+                              **kws_psd)
+    assert_true(len(freqs1) == np.floor(len(freqs2) / 2.))
+    assert_true(psds1.shape[-1] == np.floor(psds2.shape[-1] / 2.))
+
+    # tests ValueError when n_per_seg=None and n_fft > signal length
+    kws_psd.update(dict(n_fft=tmax * 1.1 * raw.info['sfreq']))
+    assert_raises(ValueError, psd_welch, raw, proj=False, n_per_seg=None,
+                  **kws_psd)
+    # ValueError when n_overlap > n_per_seg
+    kws_psd.update(dict(n_fft=128, n_per_seg=64, n_overlap=90))
+    assert_raises(ValueError, psd_welch, raw, proj=False, **kws_psd)
+
     # -- Epochs/Evoked --
     events = read_events(event_fname)
     events[:, 0] -= first_samp
     tmin, tmax, event_id = -0.5, 0.5, 1
     epochs = Epochs(raw, events[:10], event_id, tmin, tmax, picks=picks_psd,
-                    proj=False, preload=True, baseline=None, add_eeg_ref=False)
+                    proj=False, preload=True, baseline=None)
     evoked = epochs.average()
 
     tmin_full, tmax_full = -1, 1
     epochs_full = Epochs(raw, events[:10], event_id, tmin_full, tmax_full,
                          picks=picks_psd, proj=False, preload=True,
-                         baseline=None, add_eeg_ref=False)
+                         baseline=None)
     kws_psd = dict(tmin=tmin, tmax=tmax, fmin=fmin, fmax=fmax,
                    picks=picks_psd)  # Common to all
     funcs = [(psd_welch, kws_welch),
@@ -126,11 +161,10 @@ def test_psd():
         assert_true(psds_ev.shape == (len(kws['picks']), len(freqs)))
 
 
- at slow_test
- at requires_version('scipy', '0.12')
+ at pytest.mark.slowtest
 def test_compares_psd():
     """Test PSD estimation on raw for plt.psd and scipy.signal.welch."""
-    raw = read_raw_fif(raw_fname, add_eeg_ref=False)
+    raw = read_raw_fif(raw_fname)
 
     exclude = raw.info['bads'] + ['MEG 2443', 'EEG 053']  # bads + 2 more
 
@@ -143,9 +177,8 @@ def test_compares_psd():
     n_fft = 2048
 
     # Compute psds with the new implementation using Welch
-    psds_welch, freqs_welch = psd_welch(raw, tmin=tmin, tmax=tmax,
-                                        fmin=fmin, fmax=fmax,
-                                        proj=False, picks=picks,
+    psds_welch, freqs_welch = psd_welch(raw, tmin=tmin, tmax=tmax, fmin=fmin,
+                                        fmax=fmax, proj=False, picks=picks,
                                         n_fft=n_fft, n_jobs=1)
 
     # Compute psds with plt.psd
@@ -171,3 +204,5 @@ def test_compares_psd():
 
     assert_true(np.sum(psds_welch < 0) == 0)
     assert_true(np.sum(psds_mpl < 0) == 0)
+
+run_tests_if_main()
diff --git a/mne/time_frequency/tests/test_stockwell.py b/mne/time_frequency/tests/test_stockwell.py
index 9eb1c50..659fb53 100644
--- a/mne/time_frequency/tests/test_stockwell.py
+++ b/mne/time_frequency/tests/test_stockwell.py
@@ -20,6 +20,7 @@ from mne.time_frequency._stockwell import (tfr_stockwell, _st,
                                            _st_power_itc)
 
 from mne.time_frequency.tfr import AverageTFR
+from mne.utils import run_tests_if_main
 
 base_dir = op.join(op.dirname(__file__), '..', '..', 'io', 'tests', 'data')
 raw_fname = op.join(base_dir, 'test_raw.fif')
@@ -31,7 +32,8 @@ def test_stockwell_check_input():
 
     for last_dim in (127, 128):
         data = np.zeros((2, 10, last_dim))
-        x_in, n_fft, zero_pad = _check_input_st(data, None)
+        with warnings.catch_warnings(record=True):  # 127 < n_fft
+            x_in, n_fft, zero_pad = _check_input_st(data, None)
 
         assert_equal(x_in.shape, (2, 10, 128))
         assert_equal(n_fft, 128)
@@ -92,13 +94,12 @@ def test_stockwell_core():
 
 def test_stockwell_api():
     """Test stockwell functions."""
-    raw = read_raw_fif(raw_fname, add_eeg_ref=False)
+    raw = read_raw_fif(raw_fname)
     event_id, tmin, tmax = 1, -0.2, 0.5
     event_name = op.join(base_dir, 'test-eve.fif')
     events = read_events(event_name)
     epochs = Epochs(raw, events,  # XXX pick 2 has epochs of zeros.
-                    event_id, tmin, tmax, picks=[0, 1, 3], baseline=(None, 0),
-                    add_eeg_ref=False)
+                    event_id, tmin, tmax, picks=[0, 1, 3])
     for fmin, fmax in [(None, 50), (5, 50), (5, None)]:
         with warnings.catch_warnings(record=True):  # zero papdding
             power, itc = tfr_stockwell(epochs, fmin=fmin, fmax=fmax,
@@ -119,3 +120,5 @@ def test_stockwell_api():
     assert_true(itc.data.max() <= 1.0)
     assert_true(np.log(power.data.max()) * 20 <= 0.0)
     assert_true(np.log(power.data.max()) * 20 <= 0.0)
+
+run_tests_if_main()
diff --git a/mne/time_frequency/tests/test_tfr.py b/mne/time_frequency/tests/test_tfr.py
index c39f672..97f98c1 100644
--- a/mne/time_frequency/tests/test_tfr.py
+++ b/mne/time_frequency/tests/test_tfr.py
@@ -1,18 +1,20 @@
 import numpy as np
 import os.path as op
+import warnings
+
 from numpy.testing import assert_array_almost_equal, assert_array_equal
 from nose.tools import assert_true, assert_false, assert_equal, assert_raises
+import pytest
 
 import mne
 from mne import Epochs, read_events, pick_types, create_info, EpochsArray
 from mne.io import read_raw_fif
-from mne.utils import (_TempDir, run_tests_if_main, slow_test, requires_h5py,
-                       grand_average)
-from mne.time_frequency import single_trial_power
-from mne.time_frequency.tfr import (cwt_morlet, morlet, tfr_morlet,
-                                    _make_dpss, tfr_multitaper, rescale,
-                                    AverageTFR, read_tfrs, write_tfrs,
-                                    combine_tfr, cwt, _compute_tfr)
+from mne.utils import _TempDir, run_tests_if_main, requires_h5py, grand_average
+from mne.time_frequency.tfr import (morlet, tfr_morlet, _make_dpss,
+                                    tfr_multitaper, AverageTFR, read_tfrs,
+                                    write_tfrs, combine_tfr, cwt, _compute_tfr,
+                                    EpochsTFR)
+from mne.time_frequency import tfr_array_multitaper, tfr_array_morlet
 from mne.viz.utils import _fake_click
 from itertools import product
 import matplotlib
@@ -41,7 +43,7 @@ def test_time_frequency():
     tmax = 0.498  # Allows exhaustive decimation testing
 
     # Setup for reading the raw data
-    raw = read_raw_fif(raw_fname, add_eeg_ref=False)
+    raw = read_raw_fif(raw_fname)
     events = read_events(event_fname)
 
     include = []
@@ -52,14 +54,12 @@ def test_time_frequency():
                        stim=False, include=include, exclude=exclude)
 
     picks = picks[:2]
-    epochs = Epochs(raw, events, event_id, tmin, tmax, picks=picks,
-                    baseline=(None, 0), add_eeg_ref=False)
+    epochs = Epochs(raw, events, event_id, tmin, tmax, picks=picks)
     data = epochs.get_data()
     times = epochs.times
     nave = len(data)
 
-    epochs_nopicks = Epochs(raw, events, event_id, tmin, tmax,
-                            baseline=(None, 0), add_eeg_ref=False)
+    epochs_nopicks = Epochs(raw, events, event_id, tmin, tmax)
 
     freqs = np.arange(6, 20, 5)  # define frequencies of interest
     n_cycles = freqs / 4.
@@ -95,6 +95,20 @@ def test_time_frequency():
     assert_array_almost_equal(power.data, power_picks_avg.data)
     assert_array_almost_equal(itc.data, itc_picks.data)
     assert_array_almost_equal(power.data, power_evoked.data)
+    # complex output
+    assert_raises(ValueError, tfr_morlet, epochs, freqs, n_cycles,
+                  return_itc=False, average=True, output="complex")
+    assert_raises(ValueError, tfr_morlet, epochs, freqs, n_cycles,
+                  output="complex", average=False, return_itc=True)
+    epochs_power_complex = tfr_morlet(epochs, freqs, n_cycles,
+                                      output="complex", average=False,
+                                      return_itc=False)
+    epochs_power_2 = abs(epochs_power_complex)
+    epochs_power_3 = epochs_power_2.copy()
+    epochs_power_3.data[:] = np.inf  # test that it's actually copied
+    assert_array_almost_equal(epochs_power_2.data, epochs_power_picks.data)
+    power_2 = epochs_power_2.average()
+    assert_array_almost_equal(power_2.data, power.data)
 
     print(itc)  # test repr
     print(itc.ch_names)  # test property
@@ -146,21 +160,22 @@ def test_time_frequency():
     assert_true(np.sum(itc.data >= 1) == 0)
     assert_true(np.sum(itc.data <= 0) == 0)
 
-    Fs = raw.info['sfreq']  # sampling in Hz
-    tfr = cwt_morlet(data[0], Fs, freqs, use_fft=True, n_cycles=2)
+    tfr = tfr_morlet(epochs[0], freqs, use_fft=True, n_cycles=2, average=False,
+                     return_itc=False).data[0]
     assert_true(tfr.shape == (len(picks), len(freqs), len(times)))
-    tfr2 = cwt_morlet(data[0], Fs, freqs, use_fft=True, n_cycles=2,
-                      decim=slice(0, 2))
+    tfr2 = tfr_morlet(epochs[0], freqs, use_fft=True, n_cycles=2,
+                      decim=slice(0, 2), average=False,
+                      return_itc=False).data[0]
     assert_true(tfr2.shape == (len(picks), len(freqs), 2))
 
-    single_power = single_trial_power(data, Fs, freqs, use_fft=False,
-                                      n_cycles=2)
-    single_power2 = single_trial_power(data, Fs, freqs, use_fft=False,
-                                       n_cycles=2, decim=slice(0, 2))
-    single_power3 = single_trial_power(data, Fs, freqs, use_fft=False,
-                                       n_cycles=2, decim=slice(1, 3))
-    single_power4 = single_trial_power(data, Fs, freqs, use_fft=False,
-                                       n_cycles=2, decim=slice(2, 4))
+    single_power = tfr_morlet(epochs, freqs, 2, average=False,
+                              return_itc=False).data
+    single_power2 = tfr_morlet(epochs, freqs, 2, decim=slice(0, 2),
+                               average=False, return_itc=False).data
+    single_power3 = tfr_morlet(epochs, freqs, 2, decim=slice(1, 3),
+                               average=False, return_itc=False).data
+    single_power4 = tfr_morlet(epochs, freqs, 2, decim=slice(2, 4),
+                               average=False, return_itc=False).data
 
     assert_array_almost_equal(np.mean(single_power, axis=0), power.data)
     assert_array_almost_equal(np.mean(single_power2, axis=0),
@@ -193,11 +208,11 @@ def test_time_frequency():
                                     decim=decim)
             assert_equal(power.data.shape[2],
                          np.ceil(float(len(times)) / decim))
-    freqs = range(50, 55)
+    freqs = list(range(50, 55))
     decim = 2
     _, n_chan, n_time = data.shape
-    tfr = cwt_morlet(data[0, :, :], sfreq=epochs.info['sfreq'],
-                     freqs=freqs, decim=decim)
+    tfr = tfr_morlet(epochs[0], freqs, 2., decim=decim, average=False,
+                     return_itc=False).data[0]
     assert_equal(tfr.shape, (n_chan, len(freqs), n_time // decim))
 
     # Test cwt modes
@@ -205,20 +220,25 @@ def test_time_frequency():
     assert_raises(ValueError, cwt, data[0, :, :], Ws, mode='foo')
     for use_fft in [True, False]:
         for mode in ['same', 'valid', 'full']:
-            # XXX JRK: full wavelet decomposition needs to be implemented
-            if (not use_fft) and mode == 'full':
-                assert_raises(ValueError, cwt, data[0, :, :], Ws,
-                              use_fft=use_fft, mode=mode)
-                continue
-            cwt(data[0, :, :], Ws, use_fft=use_fft, mode=mode)
+            cwt(data[0], Ws, use_fft=use_fft, mode=mode)
 
     # Test decim parameter checks
-    assert_raises(TypeError, single_trial_power, data, Fs, freqs,
-                  use_fft=False, n_cycles=2, decim=None)
     assert_raises(TypeError, tfr_morlet, epochs, freqs=freqs,
                   n_cycles=n_cycles, use_fft=True, return_itc=True,
                   decim='decim')
 
+    # When convolving in time, wavelets must not be longer than the data
+    assert_raises(ValueError, cwt, data[0, :, :Ws[0].size - 1], Ws,
+                  use_fft=False)
+    with warnings.catch_warnings(record=True) as w:
+        cwt(data[0, :, :Ws[0].size - 1], Ws, use_fft=True)
+    assert_equal(len(w), 1)
+
+    # Check for off-by-one errors when using wavelets with an even number of
+    # samples
+    psd = cwt(data[0], [Ws[0][:-1]], use_fft=False, mode='full')
+    assert_equal(psd.shape, (2, 1, 420))
+
 
 def test_dpsswavelet():
     """Test DPSS tapers."""
@@ -234,7 +254,7 @@ def test_dpsswavelet():
     assert_true(len(Ws[0]) == len(freqs))  # As many wavelets as asked for
 
 
- at slow_test
+ at pytest.mark.slowtest
 def test_tfr_multitaper():
     """Test tfr_multitaper."""
     sfreq = 200.0
@@ -284,6 +304,11 @@ def test_tfr_multitaper():
 
     print(power_evoked)  # test repr for EpochsTFR
 
+    # Test channel picking
+    power_epochs_picked = power_epochs.copy().drop_channels(['SIM0002'])
+    assert_equal(power_epochs_picked.data.shape, (3, 1, 7, 200))
+    assert_equal(power_epochs_picked.ch_names, ['SIM0001'])
+
     assert_raises(ValueError, tfr_multitaper, epochs,
                   freqs=freqs, n_cycles=freqs / 2.,
                   return_itc=True, average=False)
@@ -378,6 +403,14 @@ def test_io():
 
     assert_raises(ValueError, read_tfrs, fname, condition='nonono')
 
+    # Test save of EpochsTFR.
+    data = np.zeros((5, 3, 2, 3))
+    tfr = EpochsTFR(info, data=data, times=times, freqs=freqs,
+                    comment='test', method='crazy-tfr')
+    tfr.save(fname, True)
+    read_tfr = read_tfrs(fname)[0]
+    assert_array_equal(tfr.data, read_tfr.data)
+
 
 def test_plot():
     """Test TFR plotting."""
@@ -390,7 +423,7 @@ def test_plot():
                            ['mag', 'mag', 'mag'])
     tfr = AverageTFR(info, data=data, times=times, freqs=freqs,
                      nave=20, comment='test', method='crazy-tfr')
-    tfr.plot([1, 2], title='title')
+    tfr.plot([1, 2], title='title', colorbar=False)
     plt.close('all')
     ax = plt.subplot2grid((2, 2), (0, 0))
     ax2 = plt.subplot2grid((2, 2), (1, 1))
@@ -468,7 +501,7 @@ def test_compute_tfr():
     tmax = 0.498  # Allows exhaustive decimation testing
 
     # Setup for reading the raw data
-    raw = read_raw_fif(raw_fname, add_eeg_ref=False)
+    raw = read_raw_fif(raw_fname)
     events = read_events(event_fname)
 
     exclude = raw.info['bads'] + ['MEG 2443', 'EEG 053']  # bads + 2 more
@@ -478,27 +511,25 @@ def test_compute_tfr():
                        stim=False, include=[], exclude=exclude)
 
     picks = picks[:2]
-    epochs = Epochs(raw, events, event_id, tmin, tmax, picks=picks,
-                    baseline=(None, 0), add_eeg_ref=False)
+    epochs = Epochs(raw, events, event_id, tmin, tmax, picks=picks)
     data = epochs.get_data()
     sfreq = epochs.info['sfreq']
     freqs = np.arange(10, 20, 3).astype(float)
 
     # Check all combination of options
-    for method, use_fft, zero_mean, output in product(
-        ('multitaper', 'morlet'), (False, True), (False, True),
+    for func, use_fft, zero_mean, output in product(
+        (tfr_array_multitaper, tfr_array_morlet), (False, True), (False, True),
         ('complex', 'power', 'phase',
          'avg_power_itc', 'avg_power', 'itc')):
         # Check exception
-        if (method == 'multitaper') and (output == 'phase'):
-            assert_raises(NotImplementedError, _compute_tfr, data, freqs,
-                          sfreq, method=method, output=output)
+        if (func == tfr_array_multitaper) and (output == 'phase'):
+            assert_raises(NotImplementedError, func, data, sfreq=sfreq,
+                          freqs=freqs, output=output)
             continue
 
         # Check runs
-        out = _compute_tfr(data, freqs, sfreq, method=method,
-                           use_fft=use_fft, zero_mean=zero_mean,
-                           n_cycles=2., output=output)
+        out = func(data, sfreq=sfreq, freqs=freqs, use_fft=use_fft,
+                   zero_mean=zero_mean, n_cycles=2., output=output)
         # Check shapes
         shape = np.r_[data.shape[:2], len(freqs), data.shape[2]]
         if ('avg' in output) or ('itc' in output):
@@ -513,23 +544,6 @@ def test_compute_tfr():
             assert_equal(np.float, out.dtype)
         assert_true(np.all(np.isfinite(out)))
 
-    # Check that functions are equivalent to
-    # i) single_trial_power: X, shape (n_signals, n_chans, n_times)
-    old_power = single_trial_power(data, sfreq, freqs, n_cycles=2.)
-    new_power = _compute_tfr(data, freqs, sfreq, n_cycles=2.,
-                             method='morlet', output='power')
-    assert_array_almost_equal(old_power, new_power)
-    old_power = single_trial_power(data, sfreq, freqs, n_cycles=2.,
-                                   times=epochs.times, baseline=(-.100, 0),
-                                   baseline_mode='ratio')
-    new_power = rescale(new_power, epochs.times, (-.100, 0), 'ratio')
-
-    # ii) cwt_morlet: X, shape (n_signals, n_times)
-    old_complex = cwt_morlet(data[0], sfreq, freqs, n_cycles=2.)
-    new_complex = _compute_tfr(data[[0]], freqs, sfreq, n_cycles=2.,
-                               method='morlet', output='complex')
-    assert_array_almost_equal(old_complex, new_complex[0])
-
     # Check errors params
     for _data in (None, 'foo', data[0]):
         assert_raises(ValueError, _compute_tfr, _data, freqs, sfreq)
@@ -566,13 +580,12 @@ def test_compute_tfr():
         shape = np.r_[data.shape[:2], len(freqs), n_time]
         for method in ('multitaper', 'morlet'):
             # Single trials
-            out = _compute_tfr(data, freqs, sfreq, method=method,
-                               decim=decim, n_cycles=2.)
+            out = _compute_tfr(data, freqs, sfreq, method=method, decim=decim,
+                               n_cycles=2.)
             assert_array_equal(shape, out.shape)
             # Averages
-            out = _compute_tfr(data, freqs, sfreq, method=method,
-                               decim=decim, output='avg_power',
-                               n_cycles=2.)
+            out = _compute_tfr(data, freqs, sfreq, method=method, decim=decim,
+                               output='avg_power', n_cycles=2.)
             assert_array_equal(shape[1:], out.shape)
 
 run_tests_if_main()
diff --git a/mne/time_frequency/tfr.py b/mne/time_frequency/tfr.py
index a3b5876..0059565 100644
--- a/mne/time_frequency/tfr.py
+++ b/mne/time_frequency/tfr.py
@@ -12,6 +12,7 @@ Morlet code inspired by Matlab code from Sheraz Khan & Brainstorm & SPM
 from copy import deepcopy
 from functools import partial
 from math import sqrt
+from warnings import warn
 
 import numpy as np
 from scipy import linalg
@@ -19,15 +20,15 @@ from scipy.fftpack import fft, ifft
 
 from ..baseline import rescale
 from ..parallel import parallel_func
-from ..utils import (logger, verbose, _time_mask, check_fname, deprecated,
-                     sizeof_fmt)
+from ..utils import (logger, verbose, _time_mask, check_fname, sizeof_fmt,
+                     _freqs_dep)
 from ..channels.channels import ContainsMixin, UpdateChannelsMixin
 from ..channels.layout import _pair_grad_sensors
 from ..io.pick import pick_info, pick_types
 from ..io.meas_info import Info
 from ..utils import SizeMixin
 from .multitaper import dpss_windows
-from ..viz.utils import figure_nobar, plt_show
+from ..viz.utils import figure_nobar, plt_show, _setup_cmap
 from ..externals.h5io import write_hdf5, read_hdf5
 from ..externals.six import string_types
 
@@ -60,7 +61,6 @@ def morlet(sfreq, freqs, n_cycles=7.0, sigma=None, zero_mean=False):
     -------
     Ws : list of array
         The wavelets time series.
-
     """
     Ws = list()
     n_cycles = np.atleast_1d(n_cycles)
@@ -94,8 +94,7 @@ def morlet(sfreq, freqs, n_cycles=7.0, sigma=None, zero_mean=False):
 
 
 def _make_dpss(sfreq, freqs, n_cycles=7., time_bandwidth=4.0, zero_mean=False):
-    """Compute discrete prolate spheroidal sequences (DPSS) tapers for the
-    given frequency range.
+    """Compute DPSS tapers for the given frequency range.
 
     Parameters
     ----------
@@ -163,7 +162,6 @@ def _make_dpss(sfreq, freqs, n_cycles=7., time_bandwidth=4.0, zero_mean=False):
 
 def _cwt(X, Ws, mode="same", decim=1, use_fft=True):
     """Compute cwt with fft based convolutions or temporal convolutions.
-    Return a generator over signals.
 
     Parameters
     ----------
@@ -192,10 +190,6 @@ def _cwt(X, Ws, mode="same", decim=1, use_fft=True):
     if mode not in ['same', 'valid', 'full']:
         raise ValueError("`mode` must be 'same', 'valid' or 'full', "
                          "got %s instead." % mode)
-    if mode == 'full' and (not use_fft):
-        # XXX JRK: full wavelet decomposition needs to be implemented
-        raise ValueError('`full` decomposition with convolution is currently' +
-                         ' not supported.')
     decim = _check_decim(decim)
     X = np.asarray(X)
 
@@ -212,13 +206,19 @@ def _cwt(X, Ws, mode="same", decim=1, use_fft=True):
     # precompute FFTs of Ws
     if use_fft:
         fft_Ws = np.empty((n_freqs, fsize), dtype=np.complex128)
+
+    warn_me = True
     for i, W in enumerate(Ws):
-        if len(W) > n_times:
-            raise ValueError('At least one of the wavelets is longer than the '
-                             'signal. Use a longer signal or shorter '
-                             'wavelets.')
         if use_fft:
             fft_Ws[i] = fft(W, fsize)
+        if len(W) > n_times and warn_me:
+            msg = ('At least one of the wavelets is longer than the signal. '
+                   'Consider padding the signal or using shorter wavelets.')
+            if use_fft:
+                warn(msg)
+                warn_me = False  # Suppress further warnings
+            else:
+                raise ValueError(msg)
 
     # Make generator looping across signals
     tfr = np.zeros((n_freqs, n_times_out), dtype=np.complex128)
@@ -234,14 +234,19 @@ def _cwt(X, Ws, mode="same", decim=1, use_fft=True):
                 ret = np.convolve(x, W, mode=mode)
 
             # Center and decimate decomposition
-            if mode == "valid":
-                sz = abs(W.size - n_times) + 1
-                offset = (n_times - sz) / 2
+            if mode == 'valid':
+                sz = int(abs(W.size - n_times)) + 1
+                offset = (n_times - sz) // 2
                 this_slice = slice(offset // decim.step,
                                    (offset + sz) // decim.step)
                 if use_fft:
                     ret = _centered(ret, sz)
                 tfr[ii, this_slice] = ret[decim]
+            elif mode == 'full' and not use_fft:
+                start = (W.size - 1) // 2
+                end = len(ret) - (W.size // 2)
+                ret = ret[start:end]
+                tfr[ii, :] = ret[decim]
             else:
                 if use_fft:
                     ret = _centered(ret, n_times)
@@ -252,17 +257,17 @@ def _cwt(X, Ws, mode="same", decim=1, use_fft=True):
 # Loop of convolution: single trial
 
 
-def _compute_tfr(epoch_data, frequencies, sfreq=1.0, method='morlet',
+def _compute_tfr(epoch_data, freqs, sfreq=1.0, method='morlet',
                  n_cycles=7.0, zero_mean=None, time_bandwidth=None,
                  use_fft=True, decim=1, output='complex', n_jobs=1,
-                 verbose=None):
-    """Computes time-frequency transforms.
+                 frequencies=None, verbose=None):
+    """Compute time-frequency transforms.
 
     Parameters
     ----------
     epoch_data : array of shape (n_epochs, n_channels, n_times)
         The epochs.
-    frequencies : array-like of floats, shape (n_freqs)
+    freqs : array-like of floats, shape (n_freqs)
         The frequencies.
     sfreq : float | int, defaults to 1.0
         Sampling frequency of the data.
@@ -306,8 +311,11 @@ def _compute_tfr(epoch_data, frequencies, sfreq=1.0, method='morlet',
     n_jobs : int, defaults to 1
         The number of epochs to process at the same time. The parallelization
         is implemented across channels.
+    frequencies : None
+        Deprecated.
     verbose : bool, str, int, or None, defaults to None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -325,17 +333,17 @@ def _compute_tfr(epoch_data, frequencies, sfreq=1.0, method='morlet',
                          '(n_epochs, n_chans, n_times)')
 
     # Check params
-    frequencies, sfreq, zero_mean, n_cycles, time_bandwidth, decim = \
-        _check_tfr_param(frequencies, sfreq, method, zero_mean, n_cycles,
-                         time_bandwidth, use_fft, decim, output)
+    freqs, sfreq, zero_mean, n_cycles, time_bandwidth, decim = \
+        _check_tfr_param(freqs, sfreq, method, zero_mean, n_cycles,
+                         time_bandwidth, use_fft, decim, output, frequencies)
 
     # Setup wavelet
     if method == 'morlet':
-        W = morlet(sfreq, frequencies, n_cycles=n_cycles, zero_mean=zero_mean)
+        W = morlet(sfreq, freqs, n_cycles=n_cycles, zero_mean=zero_mean)
         Ws = [W]  # to have same dimensionality as the 'multitaper' case
 
     elif method == 'multitaper':
-        Ws = _make_dpss(sfreq, frequencies, n_cycles=n_cycles,
+        Ws = _make_dpss(sfreq, freqs, n_cycles=n_cycles,
                         time_bandwidth=time_bandwidth, zero_mean=zero_mean)
 
     # Check wavelets
@@ -345,7 +353,7 @@ def _compute_tfr(epoch_data, frequencies, sfreq=1.0, method='morlet',
 
     # Initialize output
     decim = _check_decim(decim)
-    n_freqs = len(frequencies)
+    n_freqs = len(freqs)
     n_epochs, n_chans, n_times = epoch_data[:, :, decim].shape
     if output in ('power', 'phase', 'avg_power', 'itc'):
         dtype = np.float
@@ -377,17 +385,18 @@ def _compute_tfr(epoch_data, frequencies, sfreq=1.0, method='morlet',
     return out
 
 
-def _check_tfr_param(frequencies, sfreq, method, zero_mean, n_cycles,
-                     time_bandwidth, use_fft, decim, output):
+def _check_tfr_param(freqs, sfreq, method, zero_mean, n_cycles,
+                     time_bandwidth, use_fft, decim, output, frequencies):
     """Aux. function to _compute_tfr to check the params validity."""
-    # Check frequencies
-    if not isinstance(frequencies, (list, np.ndarray)):
-        raise ValueError('frequencies must be an array-like, got %s '
-                         'instead.' % type(frequencies))
-    frequencies = np.asarray(frequencies, dtype=float)
-    if frequencies.ndim != 1:
-        raise ValueError('frequencies must be of shape (n_freqs,), got %s '
-                         'instead.' % np.array(frequencies.shape))
+    freqs = _freqs_dep(freqs, frequencies)
+    # Check freqs
+    if not isinstance(freqs, (list, np.ndarray)):
+        raise ValueError('freqs must be an array-like, got %s '
+                         'instead.' % type(freqs))
+    freqs = np.asarray(freqs, dtype=float)
+    if freqs.ndim != 1:
+        raise ValueError('freqs must be of shape (n_freqs,), got %s '
+                         'instead.' % np.array(freqs.shape))
 
     # Check sfreq
     if not isinstance(sfreq, (float, int)):
@@ -400,7 +409,7 @@ def _check_tfr_param(frequencies, sfreq, method, zero_mean, n_cycles,
     if not isinstance(zero_mean, bool):
         raise ValueError('zero_mean should be of type bool, got %s. instead'
                          % type(zero_mean))
-    frequencies = np.asarray(frequencies)
+    freqs = np.asarray(freqs)
 
     if (method == 'multitaper') and (output == 'phase'):
         raise NotImplementedError(
@@ -412,10 +421,10 @@ def _check_tfr_param(frequencies, sfreq, method, zero_mean, n_cycles,
         n_cycles = float(n_cycles)
     elif isinstance(n_cycles, (list, np.ndarray)):
         n_cycles = np.array(n_cycles)
-        if len(n_cycles) != len(frequencies):
+        if len(n_cycles) != len(freqs):
             raise ValueError('n_cycles must be a float or an array of length '
                              '%i frequencies, got %i cycles instead.' %
-                             (len(frequencies), len(n_cycles)))
+                             (len(freqs), len(n_cycles)))
     else:
         raise ValueError('n_cycles must be a float or an array, got %s '
                          'instead.' % type(n_cycles))
@@ -449,7 +458,7 @@ def _check_tfr_param(frequencies, sfreq, method, zero_mean, n_cycles,
         raise ValueError('method must be "morlet" or "multitaper", got %s '
                          'instead.' % type(method))
 
-    return frequencies, sfreq, zero_mean, n_cycles, time_bandwidth, decim
+    return freqs, sfreq, zero_mean, n_cycles, time_bandwidth, decim
 
 
 def _time_frequency_loop(X, Ws, output, use_fft, mode, decim):
@@ -538,67 +547,8 @@ def _time_frequency_loop(X, Ws, output, use_fft, mode, decim):
     return tfrs
 
 
- at deprecated("This function will be removed in mne 0.14; use mne.time_frequency"
-            ".tfr_morlet() with average=False instead.")
-def cwt_morlet(X, sfreq, freqs, use_fft=True, n_cycles=7.0, zero_mean=False,
-               decim=1):
-    """Compute time freq decomposition with Morlet wavelets
-
-    This function operates directly on numpy arrays. Consider using
-    `tfr_morlet` to process `Epochs` or `Evoked` instances.
-
-    Parameters
-    ----------
-    X : array, shape (n_signals, n_times)
-        Signals (one per line)
-    sfreq : float
-        Sampling frequency.
-    freqs : array
-        Array of frequencies of interest
-    use_fft : bool
-        Compute convolution with FFT or temoral convolution.
-    n_cycles: float | array of float
-        Number of cycles. Fixed number or one per frequency.
-    zero_mean : bool
-        Make sure the wavelets have a mean of zero.
-    decim : int | slice
-        To reduce memory usage, decimation factor after time-frequency
-        decomposition.
-        If `int`, returns tfr[..., ::decim].
-        If `slice`, returns tfr[..., decim].
-
-        .. note: Decimation may create aliasing artifacts.
-
-        Defaults to 1.
-
-    Returns
-    -------
-    tfr : 3D array
-        Time Frequency Decompositions (n_signals x n_frequencies x n_times)
-
-    See Also
-    --------
-    tfr.cwt : Compute time-frequency decomposition with user-provided wavelets
-    """
-    mode = 'same'
-    # mode = "valid"
-    decim = _check_decim(decim)
-    n_signals, n_times = X[:, decim].shape
-
-    # Precompute wavelets for given frequency range to save time
-    Ws = morlet(sfreq, freqs, n_cycles=n_cycles, zero_mean=zero_mean)
-
-    coefs = cwt(X, Ws, use_fft=use_fft, mode=mode, decim=decim)
-
-    tfrs = np.empty((n_signals, len(freqs), n_times), dtype=np.complex)
-    for k, tfr in enumerate(coefs):
-        tfrs[k] = tfr
-
-    return tfrs
-
-
 def cwt(X, Ws, use_fft=True, mode='same', decim=1):
-    """Compute time freq decomposition with continuous wavelet transform
+    """Compute time freq decomposition with continuous wavelet transform.
 
     Parameters
     ----------
@@ -623,12 +573,12 @@ def cwt(X, Ws, use_fft=True, mode='same', decim=1):
 
     Returns
     -------
-    tfr : array, shape (n_signals, n_frequencies, n_times)
+    tfr : array, shape (n_signals, n_freqs, n_times)
         The time-frequency decompositions.
 
     See Also
     --------
-    mne.time_frequency.cwt_morlet : Compute time-frequency decomposition
+    mne.time_frequency.tfr_morlet : Compute time-frequency decomposition
                                     with Morlet wavelets
     """
     decim = _check_decim(decim)
@@ -643,108 +593,9 @@ def cwt(X, Ws, use_fft=True, mode='same', decim=1):
     return tfrs
 
 
- at deprecated("This function will be removed in mne 0.14; use mne.time_frequency"
-            ".tfr_morlet() with average=False instead.")
- at verbose
-def single_trial_power(data, sfreq, frequencies, use_fft=True, n_cycles=7,
-                       baseline=None, baseline_mode='ratio', times=None,
-                       decim=1, n_jobs=1, zero_mean=False, verbose=None):
-    """Compute time-frequency power on single epochs
-
-    Parameters
-    ----------
-    data : array, shape (n_epochs, n_channels, n_times)
-        The epochs
-    sfreq : float
-        Sampling rate
-    frequencies : array-like
-        The frequencies
-    use_fft : bool
-        Use the FFT for convolutions or not.
-    n_cycles : float | array of float
-        Number of cycles  in the Morlet wavelet. Fixed number
-        or one per frequency.
-    baseline : None (default) or tuple of length 2
-        The time interval to apply baseline correction.
-        If None do not apply it. If baseline is (a, b)
-        the interval is between "a (s)" and "b (s)".
-        If a is None the beginning of the data is used
-        and if b is None then b is set to the end of the interval.
-        If baseline is equal ot (None, None) all the time
-        interval is used.
-    baseline_mode : None | 'ratio' | 'zscore' | 'mean' | 'percent' | 'logratio' | 'zlogratio'
-        Do baseline correction with ratio (power is divided by mean
-        power during baseline) or zscore (power is divided by standard
-        deviation of power during baseline after subtracting the mean,
-        power = [power - mean(power_baseline)] / std(power_baseline)),
-        mean simply subtracts the mean power, percent is the same as
-        applying ratio then mean, logratio is the same as mean but then
-        rendered in log-scale, zlogratio is the same as zscore but data
-        is rendered in log-scale first.
-        If None no baseline correction is applied.
-    times : array
-        Required to define baseline
-    decim : int | slice
-        To reduce memory usage, decimation factor after time-frequency
-        decomposition.
-        If `int`, returns tfr[..., ::decim].
-        If `slice`, returns tfr[..., decim].
-
-        .. note:: Decimation may create aliasing artifacts.
-
-        Defaults to 1.
-    n_jobs : int
-        The number of epochs to process at the same time
-    zero_mean : bool
-        Make sure the wavelets have a mean of zero.
-    verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
-
-    Returns
-    -------
-    power : 4D array
-        Power estimate (Epochs x Channels x Frequencies x Timepoints).
-    """  # noqa
-    decim = _check_decim(decim)
-    mode = 'same'
-    n_frequencies = len(frequencies)
-    n_epochs, n_channels, n_times = data[:, :, decim].shape
-
-    # Precompute wavelets for given frequency range to save time
-    Ws = morlet(sfreq, frequencies, n_cycles=n_cycles, zero_mean=zero_mean)
-
-    parallel, my_cwt, _ = parallel_func(cwt, n_jobs)
-
-    logger.info("Computing time-frequency power on single epochs...")
-
-    power = np.empty((n_epochs, n_channels, n_frequencies, n_times),
-                     dtype=np.float)
-
-    # Package arguments for `cwt` here to minimize omissions where only one of
-    # the two calls below is updated with new function arguments.
-    cwt_kw = dict(Ws=Ws, use_fft=use_fft, mode=mode, decim=decim)
-    if n_jobs == 1:
-        for k, e in enumerate(data):
-            x = cwt(e, **cwt_kw)
-            power[k] = (x * x.conj()).real
-    else:
-        # Precompute tf decompositions in parallel
-        tfrs = parallel(my_cwt(e, **cwt_kw) for e in data)
-        for k, tfr in enumerate(tfrs):
-            power[k] = (tfr * tfr.conj()).real
-
-    # Run baseline correction.  Be sure to decimate the times array as well if
-    # needed.
-    if times is not None:
-        times = times[decim]
-    power = rescale(power, times, baseline, baseline_mode, copy=False)
-    return power
-
-
-# Aux function to reduce redundancy between tfr_morlet and tfr_multitaper
-
 def _tfr_aux(method, inst, freqs, decim, return_itc, picks, average,
-             **tfr_params):
+             output=None, **tfr_params):
+    """Help reduce redundancy between tfr_morlet and tfr_multitaper."""
     decim = _check_decim(decim)
     data = _get_data(inst, return_itc)
     info = inst.info
@@ -753,12 +604,14 @@ def _tfr_aux(method, inst, freqs, decim, return_itc, picks, average,
     data = data[:, picks, :]
 
     if average:
+        if output == 'complex':
+            raise ValueError('output must be "power" if average=True')
         if return_itc:
             output = 'avg_power_itc'
         else:
             output = 'avg_power'
     else:
-        output = 'power'
+        output = 'power' if output is None else output
         if return_itc:
             raise ValueError('Inter-trial coherence is not supported'
                              ' with average=False')
@@ -788,8 +641,8 @@ def _tfr_aux(method, inst, freqs, decim, return_itc, picks, average,
 @verbose
 def tfr_morlet(inst, freqs, n_cycles, use_fft=False, return_itc=True, decim=1,
                n_jobs=1, picks=None, zero_mean=True, average=True,
-               verbose=None):
-    """Compute Time-Frequency Representation (TFR) using Morlet wavelets
+               output='power', frequencies=None, verbose=None):
+    """Compute Time-Frequency Representation (TFR) using Morlet wavelets.
 
     Parameters
     ----------
@@ -815,8 +668,8 @@ def tfr_morlet(inst, freqs, n_cycles, use_fft=False, return_itc=True, decim=1,
     n_jobs : int, defaults to 1
         The number of jobs to run in parallel.
     picks : array-like of int | None, defaults to None
-        The indices of the channels to plot. If None, all available
-        channels are displayed.
+        The indices of the channels to decompose. If None, all available
+        channels are decomposed.
     zero_mean : bool, defaults to True
         Make sure the wavelet has a mean of zero.
 
@@ -825,28 +678,118 @@ def tfr_morlet(inst, freqs, n_cycles, use_fft=False, return_itc=True, decim=1,
         If True average across Epochs.
 
         .. versionadded:: 0.13.0
+    output : str
+        Can be "power" (default) or "complex". If "complex", then
+        average must be False.
+
+        .. versionadded:: 0.15.0
     verbose : bool, str, int, or None, defaults to None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
     power : AverageTFR | EpochsTFR
-        The averaged power.
+        The averaged or single-trial power.
     itc : AverageTFR | EpochsTFR
         The inter-trial coherence (ITC). Only returned if return_itc
         is True.
 
     See Also
     --------
-    tfr_multitaper, tfr_stockwell
+    mne.time_frequency.tfr_array_morlet
+    mne.time_frequency.tfr_multitaper
+    mne.time_frequency.tfr_array_multitaper
+    mne.time_frequency.tfr_stockwell
+    mne.time_frequency.tfr_array_stockwell
     """
     tfr_params = dict(n_cycles=n_cycles, n_jobs=n_jobs, use_fft=use_fft,
-                      zero_mean=zero_mean)
+                      zero_mean=zero_mean, output=output,
+                      frequencies=frequencies)
     return _tfr_aux('morlet', inst, freqs, decim, return_itc, picks,
                     average, **tfr_params)
 
 
 @verbose
+def tfr_array_morlet(epoch_data, sfreq, freqs, n_cycles=7.0,
+                     zero_mean=False, use_fft=True, decim=1, output='complex',
+                     n_jobs=1, frequencies=None, verbose=None):
+    """Compute time-frequency transform using Morlet wavelets.
+
+    Convolves epoch data with selected Morlet wavelets.
+
+    Parameters
+    ----------
+    epoch_data : array of shape (n_epochs, n_channels, n_times)
+        The epochs.
+    sfreq : float | int
+        Sampling frequency of the data.
+    freqs : array-like of floats, shape (n_freqs)
+        The frequencies.
+    n_cycles : float | array of float, defaults to 7.0
+        Number of cycles in the Morlet wavelet. Fixed number or one per
+        frequency.
+    zero_mean : bool | False
+        If True, make sure the wavelets have a mean of zero. Defaults to False.
+    use_fft : bool
+        Use the FFT for convolutions or not. Defaults to True.
+    decim : int | slice
+        To reduce memory usage, decimation factor after time-frequency
+        decomposition. Defaults to 1
+        If `int`, returns tfr[..., ::decim].
+        If `slice`, returns tfr[..., decim].
+
+        .. note::
+            Decimation may create aliasing artifacts, yet decimation
+            is done after the convolutions.
+
+    output : str, defaults to 'complex'
+
+        * 'complex' : single trial complex.
+        * 'power' : single trial power.
+        * 'phase' : single trial phase.
+        * 'avg_power' : average of single trial power.
+        * 'itc' : inter-trial coherence.
+        * 'avg_power_itc' : average of single trial power and inter-trial
+          coherence across trials.
+
+    n_jobs : int
+        The number of epochs to process at the same time. The parallelization
+        is implemented across channels. Defaults to 1
+    verbose : bool, str, int, or None, defaults to None
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
+
+    Returns
+    -------
+    out : array
+        Time frequency transform of epoch_data. If output is in ['complex',
+        'phase', 'power'], then shape of out is (n_epochs, n_chans, n_freqs,
+        n_times), else it is (n_chans, n_freqs, n_times). If output is
+        'avg_power_itc', the real values code for 'avg_power' and the
+        imaginary values code for the 'itc': out = avg_power + i * itc
+
+    See Also
+    --------
+    mne.time_frequency.tfr_morlet
+    mne.time_frequency.tfr_multitaper
+    mne.time_frequency.tfr_array_multitaper
+    mne.time_frequency.tfr_stockwell
+    mne.time_frequency.tfr_array_stockwell
+
+    Notes
+    -----
+    .. versionadded:: 0.14.0
+    """
+    return _compute_tfr(epoch_data=epoch_data, freqs=freqs,
+                        sfreq=sfreq, method='morlet', n_cycles=n_cycles,
+                        zero_mean=zero_mean, time_bandwidth=None,
+                        use_fft=use_fft, decim=decim, output=output,
+                        n_jobs=n_jobs, frequencies=frequencies,
+                        verbose=verbose)
+
+
+ at verbose
 def tfr_multitaper(inst, freqs, n_cycles, time_bandwidth=4.0,
                    use_fft=True, return_itc=True, decim=1,
                    n_jobs=1, picks=None, average=True, verbose=None):
@@ -871,7 +814,8 @@ def tfr_multitaper(inst, freqs, n_cycles, time_bandwidth=4.0,
     use_fft : bool, defaults to True
         The fft based convolution or not.
     return_itc : bool, defaults to True
-        Return inter-trial coherence (ITC) as well as averaged power.
+        Return inter-trial coherence (ITC) as well as averaged (or
+        single-trial) power.
     decim : int | slice, defaults to 1
         To reduce memory usage, decimation factor after time-frequency
         decomposition.
@@ -883,26 +827,31 @@ def tfr_multitaper(inst, freqs, n_cycles, time_bandwidth=4.0,
     n_jobs : int,  defaults to 1
         The number of jobs to run in parallel.
     picks : array-like of int | None, defaults to None
-        The indices of the channels to plot. If None, all available
-        channels are displayed.
+        The indices of the channels to decompose. If None, all available
+        channels are decomposed.
     average : bool, defaults to True
         If True average across Epochs.
 
         .. versionadded:: 0.13.0
     verbose : bool, str, int, or None, defaults to None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
     power : AverageTFR | EpochsTFR
-        The averaged power.
+        The averaged or single-trial power.
     itc : AverageTFR | EpochsTFR
         The inter-trial coherence (ITC). Only returned if return_itc
         is True.
 
     See Also
     --------
-    tfr_multitaper, tfr_stockwell
+    mne.time_frequency.tfr_array_multitaper
+    mne.time_frequency.tfr_stockwell
+    mne.time_frequency.tfr_array_stockwell
+    mne.time_frequency.tfr_morlet
+    mne.time_frequency.tfr_array_morlet
 
     Notes
     -----
@@ -917,12 +866,23 @@ def tfr_multitaper(inst, freqs, n_cycles, time_bandwidth=4.0,
 # TFR(s) class
 
 class _BaseTFR(ContainsMixin, UpdateChannelsMixin, SizeMixin):
+    """Base TFR class."""
+
+    @property
+    def data(self):
+        return self._data
+
+    @data.setter
+    def data(self, data):
+        self._data = data
+
     @property
     def ch_names(self):
+        """Channel names."""
         return self.info['ch_names']
 
     def crop(self, tmin=None, tmax=None):
-        """Crop data to a given time interval in place
+        """Crop data to a given time interval in place.
 
         Parameters
         ----------
@@ -947,7 +907,7 @@ class _BaseTFR(ContainsMixin, UpdateChannelsMixin, SizeMixin):
 
     @verbose
     def apply_baseline(self, baseline, mode='mean', verbose=None):
-        """Baseline correct the data
+        """Baseline correct the data.
 
         Parameters
         ----------
@@ -959,32 +919,50 @@ class _BaseTFR(ContainsMixin, UpdateChannelsMixin, SizeMixin):
             and if b is None then b is set to the end of the interval.
             If baseline is equal to (None, None) all the time
             interval is used.
-        mode : None | 'ratio' | 'zscore' | 'mean' | 'percent' | 'logratio' | 'zlogratio'
-            Do baseline correction with ratio (power is divided by mean
-            power during baseline) or zscore (power is divided by standard
-            deviation of power during baseline after subtracting the mean,
-            power = [power - mean(power_baseline)] / std(power_baseline)),
-            mean simply subtracts the mean power, percent is the same as
-            applying ratio then mean, logratio is the same as mean but then
-            rendered in log-scale, zlogratio is the same as zscore but data
-            is rendered in log-scale first.
+        mode : 'mean' | 'ratio' | 'logratio' | 'percent' | 'zscore' | 'zlogratio' | None
+            Perform baseline correction by
+
+              - subtracting the mean baseline power ('mean')
+              - dividing by the mean baseline power ('ratio')
+              - dividing by the mean baseline power and taking the log
+                ('logratio')
+              - subtracting the mean baseline power followed by dividing by the
+                mean baseline power ('percent')
+              - subtracting the mean baseline power and dividing by the
+                standard deviation of the baseline power ('zscore')
+              - dividing by the mean baseline power, taking the log, and
+                dividing by the standard deviation of the baseline power
+                ('zlogratio')
+
             If None no baseline correction is applied.
         verbose : bool, str, int, or None
-            If not None, override default verbose level (see mne.verbose).
+            If not None, override default verbose level (see
+            :func:`mne.verbose`).
 
         Returns
         -------
         inst : instance of AverageTFR
-            The modified instance.        
-
-        """  # noqa
+            The modified instance.
+        """  # noqa: E501
         self.data = rescale(self.data, self.times, baseline, mode,
                             copy=False)
         return self
 
+    def save(self, fname, overwrite=False):
+        """Save TFR object to hdf5 file.
+
+        Parameters
+        ----------
+        fname : str
+            The file name, which should end with -tfr.h5 .
+        overwrite : bool
+            If True, overwrite file (if it exists). Defaults to false
+        """
+        write_tfrs(fname, self, overwrite=overwrite)
+
 
 class AverageTFR(_BaseTFR):
-    """Container for Time-Frequency data
+    """Container for Time-Frequency data.
 
     Can for example store induced power at sensor level or inter-trial
     coherence.
@@ -1006,16 +984,18 @@ class AverageTFR(_BaseTFR):
     method : str | None, defaults to None
         Comment on the method used to compute the data, e.g., morlet wavelet.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Attributes
     ----------
     ch_names : list
         The names of the channels.
     """
+
     @verbose
     def __init__(self, info, data, times, freqs, nave, comment=None,
-                 method=None, verbose=None):
+                 method=None, verbose=None):  # noqa: D102
         self.info = info
         if data.ndim != 3:
             raise ValueError('data should be 3d. Got %d.' % data.ndim)
@@ -1035,13 +1015,14 @@ class AverageTFR(_BaseTFR):
         self.nave = nave
         self.comment = comment
         self.method = method
+        self.preload = True
 
     @verbose
-    def plot(self, picks, baseline=None, mode='mean', tmin=None,
-             tmax=None, fmin=None, fmax=None, vmin=None, vmax=None,
-             cmap='RdBu_r', dB=False, colorbar=True, show=True,
-             title=None, axes=None, layout=None, verbose=None):
-        """Plot TFRs in a topography with images
+    def plot(self, picks, baseline=None, mode='mean', tmin=None, tmax=None,
+             fmin=None, fmax=None, vmin=None, vmax=None, cmap='RdBu_r',
+             dB=False, colorbar=True, show=True, title=None, axes=None,
+             layout=None, yscale='auto', verbose=None):
+        """Plot TFRs as a two-dimensional image(s).
 
         Parameters
         ----------
@@ -1053,17 +1034,23 @@ class AverageTFR(_BaseTFR):
             the interval is between "a (s)" and "b (s)".
             If a is None the beginning of the data is used
             and if b is None then b is set to the end of the interval.
-            If baseline is equal ot (None, None) all the time
+            If baseline is equal to (None, None) all the time
             interval is used.
-        mode : None | 'ratio' | 'zscore' | 'mean' | 'percent' | 'logratio' | 'zlogratio'
-            Do baseline correction with ratio (power is divided by mean
-            power during baseline) or zscore (power is divided by standard
-            deviation of power during baseline after subtracting the mean,
-            power = [power - mean(power_baseline)] / std(power_baseline)),
-            mean simply subtracts the mean power, percent is the same as
-            applying ratio then mean, logratio is the same as mean but then
-            rendered in log-scale, zlogratio is the same as zscore but data
-            is rendered in log-scale first.
+        mode : 'mean' | 'ratio' | 'logratio' | 'percent' | 'zscore' | 'zlogratio' | None
+            Perform baseline correction by
+
+              - subtracting the mean baseline power ('mean')
+              - dividing by the mean baseline power ('ratio')
+              - dividing by the mean baseline power and taking the log
+                ('logratio')
+              - subtracting the mean baseline power followed by dividing by the
+                mean baseline power ('percent')
+              - subtracting the mean baseline power and dividing by the
+                standard deviation of the baseline power ('zscore')
+              - dividing by the mean baseline power, taking the log, and
+                dividing by the standard deviation of the baseline power
+                ('zlogratio')
+
             If None no baseline correction is applied.
         tmin : None | float
             The first time instant to display. If None the first time point
@@ -1114,14 +1101,22 @@ class AverageTFR(_BaseTFR):
             Layout instance specifying sensor positions. Used for interactive
             plotting of topographies on rectangle selection. If possible, the
             correct layout is inferred from the data.
+        yscale : 'auto' (default) | 'linear' | 'log'
+            The scale of y (frequency) axis. 'linear' gives linear y axis,
+            'log' leads to log-spaced y axis and 'auto' detects if frequencies
+            are log-spaced and only then sets the y axis to 'log'.
+
+            .. versionadded:: 0.14.0
+
         verbose : bool, str, int, or None
-            If not None, override default verbose level (see mne.verbose).
+            If not None, override default verbose level (see
+            :func:`mne.verbose`).
 
         Returns
         -------
         fig : matplotlib.figure.Figure
             The figure containing the topography.
-        """  # noqa
+        """  # noqa: E501
         from ..viz.topo import _imshow_tfr
         import matplotlib.pyplot as plt
         times, freqs = self.times.copy(), self.freqs.copy()
@@ -1144,10 +1139,7 @@ class AverageTFR(_BaseTFR):
                 raise RuntimeError('There must be an axes for each picked '
                                    'channel.')
 
-        if cmap == 'interactive':
-            cmap = ('RdBu_r', True)
-        elif not isinstance(cmap, tuple):
-            cmap = (cmap, True)
+        cmap = _setup_cmap(cmap)
         for idx in range(len(data)):
             if axes is None:
                 fig = plt.figure()
@@ -1159,25 +1151,21 @@ class AverageTFR(_BaseTFR):
                                         mode=mode, layout=layout)
             _imshow_tfr(ax, 0, tmin, tmax, vmin, vmax, onselect_callback,
                         ylim=None, tfr=data[idx: idx + 1], freq=freqs,
-                        x_label='Time (ms)', y_label='Frequency (Hz)',
-                        colorbar=colorbar, picker=False, cmap=cmap)
+                        x_label='Time (s)', y_label='Frequency (Hz)',
+                        colorbar=colorbar, cmap=cmap, yscale=yscale)
             if title:
                 fig.suptitle(title)
-            # Only draw 1 cbar. For interactive mode we pass the ref to cbar.
-            colorbar = ax.CB if cmap[1] else False
 
         plt_show(show)
         return fig
 
     def _onselect(self, eclick, erelease, baseline, mode, layout):
-        """Callback function called by rubber band selector in channel tfr."""
-        import matplotlib.pyplot as plt
+        """Handle rubber band selector in channel tfr."""
         from ..viz import plot_tfr_topomap
         if abs(eclick.x - erelease.x) < .1 or abs(eclick.y - erelease.y) < .1:
             return
-        plt.ion()  # turn interactive mode on
-        tmin = round(min(eclick.xdata, erelease.xdata) / 1000., 5)  # ms to s
-        tmax = round(max(eclick.xdata, erelease.xdata) / 1000., 5)
+        tmin = round(min(eclick.xdata, erelease.xdata), 5)  # s
+        tmax = round(max(eclick.xdata, erelease.xdata), 5)
         fmin = round(min(eclick.ydata, erelease.ydata), 5)  # Hz
         fmax = round(max(eclick.ydata, erelease.ydata), 5)
         tmin = min(self.times, key=lambda x: abs(x - tmin))  # find closest
@@ -1204,7 +1192,7 @@ class AverageTFR(_BaseTFR):
         fig.suptitle('{0:.2f} s - {1:.2f} s, {2:.2f} Hz - {3:.2f} Hz'.format(
             tmin, tmax, fmin, fmax), y=0.04)
         for idx, ch_type in enumerate(types):
-            ax = plt.subplot(1, len(types), idx + 1)
+            ax = fig.add_subplot(1, len(types), idx + 1)
             plot_tfr_topomap(self, ch_type=ch_type, tmin=tmin, tmax=tmax,
                              fmin=fmin, fmax=fmax, layout=layout,
                              baseline=baseline, mode=mode, cmap=None,
@@ -1216,8 +1204,8 @@ class AverageTFR(_BaseTFR):
                   layout=None, cmap='RdBu_r', title=None, dB=False,
                   colorbar=True, layout_scale=0.945, show=True,
                   border='none', fig_facecolor='k', fig_background=None,
-                  font_color='w'):
-        """Plot TFRs in a topography with images
+                  font_color='w', yscale='auto'):
+        """Plot TFRs in a topography with images.
 
         Parameters
         ----------
@@ -1230,17 +1218,23 @@ class AverageTFR(_BaseTFR):
             the interval is between "a (s)" and "b (s)".
             If a is None the beginning of the data is used
             and if b is None then b is set to the end of the interval.
-            If baseline is equal ot (None, None) all the time
+            If baseline is equal to (None, None) all the time
             interval is used.
-        mode : None | 'ratio' | 'zscore' | 'mean' | 'percent' | 'logratio' | 'zlogratio'
-            Do baseline correction with ratio (power is divided by mean
-            power during baseline) or zscore (power is divided by standard
-            deviation of power during baseline after subtracting the mean,
-            power = [power - mean(power_baseline)] / std(power_baseline)),
-            mean simply subtracts the mean power, percent is the same as
-            applying ratio then mean, logratio is the same as mean but then
-            rendered in log-scale, zlogratio is the same as zscore but data
-            is rendered in log-scale first.
+        mode : 'mean' | 'ratio' | 'logratio' | 'percent' | 'zscore' | 'zlogratio' | None
+            Perform baseline correction by
+
+              - subtracting the mean baseline power ('mean')
+              - dividing by the mean baseline power ('ratio')
+              - dividing by the mean baseline power and taking the log
+                ('logratio')
+              - subtracting the mean baseline power followed by dividing by the
+                mean baseline power ('percent')
+              - subtracting the mean baseline power and dividing by the
+                standard deviation of the baseline power ('zscore')
+              - dividing by the mean baseline power, taking the log, and
+                dividing by the standard deviation of the baseline power
+                ('zlogratio')
+
             If None no baseline correction is applied.
         tmin : None | float
             The first time instant to display. If None the first time point
@@ -1285,12 +1279,16 @@ class AverageTFR(_BaseTFR):
             `matplotlib.pyplot.imshow`. Defaults to None.
         font_color: str | obj
             The color of tick labels in the colorbar. Defaults to white.
+        yscale : 'auto' (default) | 'linear' | 'log'
+            The scale of y (frequency) axis. 'linear' gives linear y axis,
+            'log' leads to log-spaced y axis and 'auto' detects if frequencies
+            are log-spaced and only then sets the y axis to 'log'.
 
         Returns
         -------
         fig : matplotlib.figure.Figure
             The figure containing the topography.
-        """  # noqa
+        """  # noqa: E501
         from ..viz.topo import _imshow_tfr, _plot_topo, _imshow_tfr_unified
         from ..viz import add_background_image
         times = self.times.copy()
@@ -1311,7 +1309,7 @@ class AverageTFR(_BaseTFR):
         onselect_callback = partial(self._onselect, baseline=baseline,
                                     mode=mode, layout=layout)
 
-        click_fun = partial(_imshow_tfr, tfr=data, freq=freqs,
+        click_fun = partial(_imshow_tfr, tfr=data, freq=freqs, yscale=yscale,
                             cmap=(cmap, True), onselect=onselect_callback)
         imshow = partial(_imshow_tfr_unified, tfr=data, freq=freqs, cmap=cmap,
                          onselect=onselect_callback)
@@ -1320,7 +1318,7 @@ class AverageTFR(_BaseTFR):
                          click_func=click_fun, layout=layout,
                          colorbar=colorbar, vmin=vmin, vmax=vmax, cmap=cmap,
                          layout_scale=layout_scale, title=title, border=border,
-                         x_label='Time (ms)', y_label='Frequency (Hz)',
+                         x_label='Time (s)', y_label='Frequency (Hz)',
                          fig_facecolor=fig_facecolor, font_color=font_color,
                          unified=True, img=True)
 
@@ -1329,12 +1327,13 @@ class AverageTFR(_BaseTFR):
         return fig
 
     def plot_topomap(self, tmin=None, tmax=None, fmin=None, fmax=None,
-                     ch_type=None, baseline=None, mode='mean',
-                     layout=None, vmin=None, vmax=None, cmap=None,
-                     sensors=True, colorbar=True, unit=None, res=64, size=2,
+                     ch_type=None, baseline=None, mode='mean', layout=None,
+                     vmin=None, vmax=None, cmap=None, sensors=True,
+                     colorbar=True, unit=None, res=64, size=2,
                      cbar_fmt='%1.1e', show_names=False, title=None,
-                     axes=None, show=True, outlines='head', head_pos=None):
-        """Plot topographic maps of time-frequency intervals of TFR data
+                     axes=None, show=True, outlines='head', head_pos=None,
+                     contours=6):
+        """Plot topographic maps of time-frequency intervals of TFR data.
 
         Parameters
         ----------
@@ -1363,15 +1362,21 @@ class AverageTFR(_BaseTFR):
             and if b is None then b is set to the end of the interval.
             If baseline is equal to (None, None) all the time
             interval is used.
-        mode : None | 'ratio' | 'zscore' | 'mean' | 'percent' | 'logratio' | 'zlogratio'
-            Do baseline correction with ratio (power is divided by mean
-            power during baseline) or zscore (power is divided by standard
-            deviation of power during baseline after subtracting the mean,
-            power = [power - mean(power_baseline)] / std(power_baseline)),
-            mean simply subtracts the mean power, percent is the same as
-            applying ratio then mean, logratio is the same as mean but then
-            rendered in log-scale, zlogratio is the same as zscore but data
-            is rendered in log-scale first.
+        mode : 'mean' | 'ratio' | 'logratio' | 'percent' | 'zscore' | 'zlogratio' | None
+            Perform baseline correction by
+
+              - subtracting the mean baseline power ('mean')
+              - dividing by the mean baseline power ('ratio')
+              - dividing by the mean baseline power and taking the log
+                ('logratio')
+              - subtracting the mean baseline power followed by dividing by the
+                mean baseline power ('percent')
+              - subtracting the mean baseline power and dividing by the
+                standard deviation of the baseline power ('zscore')
+              - dividing by the mean baseline power, taking the log, and
+                dividing by the standard deviation of the baseline power
+                ('zlogratio')
+
             If None no baseline correction is applied.
         layout : None | Layout
             Layout instance specifying sensor positions (does not need to
@@ -1441,12 +1446,19 @@ class AverageTFR(_BaseTFR):
             the head circle. If dict, can have entries 'center' (tuple) and
             'scale' (tuple) for what the center and scale of the head should be
             relative to the electrode locations.
+        contours : int | array of float
+            The number of contour lines to draw. If 0, no contours will be
+            drawn. When an integer, matplotlib ticker locator is used to find
+            suitable values for the contour thresholds (may sometimes be
+            inaccurate, use array for accuracy). If an array, the values
+            represent the levels for the contours. If colorbar=True, the ticks
+            in colorbar correspond to the contour levels. Defaults to 6.
 
         Returns
         -------
         fig : matplotlib.figure.Figure
             The figure containing the topography.
-        """  # noqa
+        """  # noqa: E501
         from ..viz import plot_tfr_topomap
         return plot_tfr_topomap(self, tmin=tmin, tmax=tmax, fmin=fmin,
                                 fmax=fmax, ch_type=ch_type, baseline=baseline,
@@ -1455,36 +1467,39 @@ class AverageTFR(_BaseTFR):
                                 unit=unit, res=res, size=size,
                                 cbar_fmt=cbar_fmt, show_names=show_names,
                                 title=title, axes=axes, show=show,
-                                outlines=outlines, head_pos=head_pos)
+                                outlines=outlines, head_pos=head_pos,
+                                contours=contours)
 
     def _check_compat(self, tfr):
-        """checks that self and tfr have the same time-frequency ranges"""
+        """Check that self and tfr have the same time-frequency ranges."""
         assert np.all(tfr.times == self.times)
         assert np.all(tfr.freqs == self.freqs)
 
-    def __add__(self, tfr):
+    def __add__(self, tfr):  # noqa: D105
+        """Add instances."""
         self._check_compat(tfr)
         out = self.copy()
         out.data += tfr.data
         return out
 
-    def __iadd__(self, tfr):
+    def __iadd__(self, tfr):  # noqa: D105
         self._check_compat(tfr)
         self.data += tfr.data
         return self
 
-    def __sub__(self, tfr):
+    def __sub__(self, tfr):  # noqa: D105
+        """Subtract instances."""
         self._check_compat(tfr)
         out = self.copy()
         out.data -= tfr.data
         return out
 
-    def __isub__(self, tfr):
+    def __isub__(self, tfr):  # noqa: D105
         self._check_compat(tfr)
         self.data -= tfr.data
         return self
 
-    def __repr__(self):
+    def __repr__(self):  # noqa: D105
         s = "time : [%f, %f]" % (self.times[0], self.times[-1])
         s += ", freq : [%f, %f]" % (self.freqs[0], self.freqs[-1])
         s += ", nave : %d" % self.nave
@@ -1492,21 +1507,9 @@ class AverageTFR(_BaseTFR):
         s += ', ~%s' % (sizeof_fmt(self._size),)
         return "<AverageTFR  |  %s>" % s
 
-    def save(self, fname, overwrite=False):
-        """Save TFR object to hdf5 file
-
-        Parameters
-        ----------
-        fname : str
-            The file name, which should end with -tfr.h5 .
-        overwrite : bool
-            If True, overwrite file (if it exists). Defaults to false
-        """
-        write_tfrs(fname, self, overwrite=overwrite)
-
 
 class EpochsTFR(_BaseTFR):
-    """Container for Time-Frequency data on epochs
+    """Container for Time-Frequency data on epochs.
 
     Can for example store induced power at sensor level.
 
@@ -1525,7 +1528,8 @@ class EpochsTFR(_BaseTFR):
     method : str | None, defaults to None
         Comment on the method used to compute the data, e.g., morlet wavelet.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Attributes
     ----------
@@ -1536,9 +1540,10 @@ class EpochsTFR(_BaseTFR):
     -----
     .. versionadded:: 0.13.0
     """
+
     @verbose
     def __init__(self, info, data, times, freqs, comment=None,
-                 method=None, verbose=None):
+                 method=None, verbose=None):  # noqa: D102
         self.info = info
         if data.ndim != 4:
             raise ValueError('data should be 4d. Got %d.' % data.ndim)
@@ -1557,8 +1562,9 @@ class EpochsTFR(_BaseTFR):
         self.freqs = np.array(freqs, dtype=float)
         self.comment = comment
         self.method = method
+        self.preload = True
 
-    def __repr__(self):
+    def __repr__(self):  # noqa: D105
         s = "time : [%f, %f]" % (self.times[0], self.times[-1])
         s += ", freq : [%f, %f]" % (self.freqs[0], self.freqs[-1])
         s += ", epochs : %d" % self.data.shape[0]
@@ -1566,12 +1572,37 @@ class EpochsTFR(_BaseTFR):
         s += ', ~%s' % (sizeof_fmt(self._size),)
         return "<EpochsTFR  |  %s>" % s
 
+    def __abs__(self):
+        """Take the absolute value."""
+        return EpochsTFR(info=self.info.copy(), data=np.abs(self.data),
+                         times=self.times.copy(), freqs=self.freqs.copy(),
+                         method=self.method, comment=self.comment)
+
+    def copy(self):
+        """Give a copy of the EpochsTFR.
+
+        Returns
+        -------
+        tfr : instance of EpochsTFR
+            The copy.
+        """
+        return EpochsTFR(info=self.info.copy(), data=self.data.copy(),
+                         times=self.times.copy(), freqs=self.freqs.copy(),
+                         method=self.method, comment=self.comment)
+
     def average(self):
+        """Average the data across epochs.
+
+        Returns
+        -------
+        ave : instance of AverageTFR
+            The averaged data.
+        """
         data = np.mean(self.data, axis=0)
         return AverageTFR(info=self.info.copy(), data=data,
                           times=self.times.copy(), freqs=self.freqs.copy(),
-                          nave=self.data.shape[0],
-                          method=self.method)
+                          nave=self.data.shape[0], method=self.method,
+                          comment=self.comment)
 
 
 def combine_tfr(all_tfr, weights='nave'):
@@ -1639,21 +1670,22 @@ def combine_tfr(all_tfr, weights='nave'):
 
 
 def _get_data(inst, return_itc):
-    """Get data from Epochs or Evoked instance as epochs x ch x time"""
-    from ..epochs import _BaseEpochs
+    """Get data from Epochs or Evoked instance as epochs x ch x time."""
+    from ..epochs import BaseEpochs
     from ..evoked import Evoked
-    if not isinstance(inst, (_BaseEpochs, Evoked)):
+    if not isinstance(inst, (BaseEpochs, Evoked)):
         raise TypeError('inst must be Epochs or Evoked')
-    if isinstance(inst, _BaseEpochs):
+    if isinstance(inst, BaseEpochs):
         data = inst.get_data()
     else:
         if return_itc:
             raise ValueError('return_itc must be False for evoked data')
-        data = inst.data[np.newaxis, ...].copy()
+        data = inst.data[np.newaxis].copy()
     return data
 
 
 def _prepare_picks(info, data, picks):
+    """Prepare the picks."""
     if picks is None:
         picks = pick_types(info, meg=True, eeg=True, ref_meg=False,
                            exclude='bads')
@@ -1666,7 +1698,7 @@ def _prepare_picks(info, data, picks):
 
 
 def _centered(arr, newsize):
-    """Aux Function to center data"""
+    """Aux Function to center data."""
     # Return the center newsize portion of the array.
     newsize = np.asarray(newsize)
     currsize = np.array(arr.shape)
@@ -1678,7 +1710,7 @@ def _centered(arr, newsize):
 
 def _preproc_tfr(data, times, freqs, tmin, tmax, fmin, fmax, mode,
                  baseline, vmin, vmax, dB, sfreq):
-    """Aux Function to prepare tfr computation"""
+    """Aux Function to prepare tfr computation."""
     from ..viz.utils import _setup_vmin_vmax
 
     copy = baseline is not None
@@ -1707,7 +1739,6 @@ def _preproc_tfr(data, times, freqs, tmin, tmax, fmin, fmax, mode,
     # crop data
     data = data[:, ifmin:ifmax, itmin:itmax]
 
-    times *= 1e3
     if dB:
         data = 10 * np.log10((data * data.conj()).real)
 
@@ -1716,7 +1747,7 @@ def _preproc_tfr(data, times, freqs, tmin, tmax, fmin, fmax, mode,
 
 
 def _check_decim(decim):
-    """ aux function checking the decim parameter """
+    """Aux function checking the decim parameter."""
     if isinstance(decim, int):
         decim = slice(None, None, decim)
     elif not isinstance(decim, slice):
@@ -1760,16 +1791,16 @@ def write_tfrs(fname, tfr, overwrite=False):
 
 
 def _prepare_write_tfr(tfr, condition):
-    """Aux function"""
-    return (condition, dict(times=tfr.times, freqs=tfr.freqs,
-                            data=tfr.data, info=tfr.info,
-                            nave=tfr.nave, comment=tfr.comment,
-                            method=tfr.method))
+    """Aux function."""
+    attributes = dict(times=tfr.times, freqs=tfr.freqs, data=tfr.data,
+                      info=tfr.info, comment=tfr.comment, method=tfr.method)
+    if hasattr(tfr, 'nave'):
+        attributes['nave'] = tfr.nave
+    return (condition, attributes)
 
 
 def read_tfrs(fname, condition=None):
-    """
-    Read TFR datasets from hdf5 file.
+    """Read TFR datasets from hdf5 file.
 
     Parameters
     ----------
@@ -1793,15 +1824,17 @@ def read_tfrs(fname, condition=None):
     -----
     .. versionadded:: 0.9.0
     """
-
     check_fname(fname, 'tfr', ('-tfr.h5',))
 
     logger.info('Reading %s ...' % fname)
     tfr_data = read_hdf5(fname, title='mnepython')
     for k, tfr in tfr_data:
         tfr['info'] = Info(tfr['info'])
-
+    is_average = 'nave' in tfr
     if condition is not None:
+        if not is_average:
+            raise NotImplementedError('condition not supported when reading '
+                                      'EpochsTFR.')
         tfr_dict = dict(tfr_data)
         if condition not in tfr_dict:
             keys = ['%s' % k for k in tfr_dict]
@@ -1809,6 +1842,8 @@ def read_tfrs(fname, condition=None):
                              'The file contains "{1}""'
                              .format(condition, " or ".join(keys)))
         out = AverageTFR(**tfr_dict[condition])
-    else:
+    elif is_average:
         out = [AverageTFR(**d) for d in list(zip(*tfr_data))[1]]
+    else:
+        out = [EpochsTFR(**d) for d in list(zip(*tfr_data))[1]]
     return out
diff --git a/mne/transforms.py b/mne/transforms.py
index 30c5529..3da3c66 100644
--- a/mne/transforms.py
+++ b/mne/transforms.py
@@ -1,3 +1,6 @@
+# -*- coding: utf-8 -*-
+"""Helpers for various transformations."""
+
 # Authors: Alexandre Gramfort <alexandre.gramfort at telecom-paristech.fr>
 #          Christian Brodbeck <christianbrodbeck at nyu.edu>
 #
@@ -6,15 +9,19 @@
 import os
 from os import path as op
 import glob
+import copy
+
 import numpy as np
+from copy import deepcopy
 from numpy import sin, cos
 from scipy import linalg
 
+from .fixes import _get_sph_harm
 from .io.constants import FIFF
 from .io.open import fiff_open
 from .io.tag import read_tag
 from .io.write import start_file, end_file, write_coord_trans
-from .utils import check_fname, logger
+from .utils import check_fname, logger, verbose, _ensure_int
 from .externals.six import string_types
 
 
@@ -32,7 +39,8 @@ _str_to_frame = dict(meg=FIFF.FIFFV_COORD_DEVICE,
                      ras=FIFF.FIFFV_MNE_COORD_RAS,
                      fs_tal=FIFF.FIFFV_MNE_COORD_FS_TAL,
                      ctf_head=FIFF.FIFFV_MNE_COORD_CTF_HEAD,
-                     ctf_meg=FIFF.FIFFV_MNE_COORD_CTF_DEVICE)
+                     ctf_meg=FIFF.FIFFV_MNE_COORD_CTF_DEVICE,
+                     unknown=FIFF.FIFFV_COORD_UNKNOWN)
 _frame_to_str = dict((val, key) for key, val in _str_to_frame.items())
 
 _verbose_frames = {FIFF.FIFFV_COORD_UNKNOWN: 'unknown',
@@ -54,18 +62,18 @@ _verbose_frames = {FIFF.FIFFV_COORD_UNKNOWN: 'unknown',
 
 
 def _to_const(cf):
-    """Helper to convert string or int coord frame into int"""
+    """Convert string or int coord frame into int."""
     if isinstance(cf, string_types):
         if cf not in _str_to_frame:
             raise ValueError('Unknown cf %s' % cf)
         cf = _str_to_frame[cf]
-    elif not isinstance(cf, int):
-        raise TypeError('cf must be str or int, not %s' % type(cf))
-    return cf
+    else:
+        cf = _ensure_int(cf, 'coordinate frame', 'a str or int')
+    return int(cf)
 
 
 class Transform(dict):
-    """A transform
+    """A transform.
 
     Parameters
     ----------
@@ -73,15 +81,17 @@ class Transform(dict):
         The starting coordinate frame.
     to : str | int
         The ending coordinate frame.
-    trans : array-like, shape (4, 4)
-        The transformation matrix.
+    trans : array-like, shape (4, 4) | None
+        The transformation matrix. If None, an identity matrix will be
+        used.
     """
-    def __init__(self, fro, to, trans):
+
+    def __init__(self, fro, to, trans=None):  # noqa: D102
         super(Transform, self).__init__()
         # we could add some better sanity checks here
         fro = _to_const(fro)
         to = _to_const(to)
-        trans = np.asarray(trans, dtype=np.float64)
+        trans = np.eye(4) if trans is None else np.asarray(trans, np.float64)
         if trans.shape != (4, 4):
             raise ValueError('Transformation must be shape (4, 4) not %s'
                              % (trans.shape,))
@@ -89,21 +99,65 @@ class Transform(dict):
         self['to'] = to
         self['trans'] = trans
 
-    def __repr__(self):
+    def __repr__(self):  # noqa: D105
         return ('<Transform  |  %s->%s>\n%s'
                 % (_coord_frame_name(self['from']),
                    _coord_frame_name(self['to']), self['trans']))
 
+    def __eq__(self, other, rtol=0., atol=0.):
+        """Check for equality.
+
+        Parameter
+        ---------
+        other : instance of Transform
+            The other transform.
+        rtol : float
+            Relative tolerance.
+        atol : float
+            Absolute tolerance.
+
+        Returns
+        -------
+        eq : bool
+            True if the transforms are equal.
+        """
+        return (isinstance(other, Transform) and
+                self['from'] == other['from'] and
+                self['to'] == other['to'] and
+                np.allclose(self['trans'], other['trans'], rtol=rtol,
+                            atol=atol))
+
+    def __ne__(self, other, rtol=0., atol=0.):
+        """Check for inequality.
+
+        Parameter
+        ---------
+        other : instance of Transform
+            The other transform.
+        rtol : float
+            Relative tolerance.
+        atol : float
+            Absolute tolerance.
+
+        Returns
+        -------
+        eq : bool
+            True if the transforms are not equal.
+        """
+        return not self == other
+
     @property
     def from_str(self):
+        """The "from" frame as a string."""
         return _coord_frame_name(self['from'])
 
     @property
     def to_str(self):
+        """The "to" frame as a string."""
         return _coord_frame_name(self['to'])
 
     def save(self, fname):
-        """Save the transform as -trans.fif file
+        """Save the transform as -trans.fif file.
 
         Parameters
         ----------
@@ -112,9 +166,13 @@ class Transform(dict):
         """
         write_trans(fname, self)
 
+    def copy(self):
+        """Make a copy of the transform."""
+        return copy.deepcopy(self)
+
 
 def _coord_frame_name(cframe):
-    """Map integers to human-readable (verbose) names"""
+    """Map integers to human-readable (verbose) names."""
     return _verbose_frames.get(int(cframe), 'unknown')
 
 
@@ -147,7 +205,7 @@ def _find_trans(subject, subjects_dir=None):
 
 
 def apply_trans(trans, pts, move=True):
-    """Apply a transform matrix to an array of points
+    """Apply a transform matrix to an array of points.
 
     Parameters
     ----------
@@ -165,7 +223,6 @@ def apply_trans(trans, pts, move=True):
     """
     if isinstance(trans, dict):
         trans = trans['trans']
-    trans = np.asarray(trans)
     pts = np.asarray(pts)
     if pts.size == 0:
         return pts.copy()
@@ -173,16 +230,14 @@ def apply_trans(trans, pts, move=True):
     # apply rotation & scale
     out_pts = np.dot(pts, trans[:3, :3].T)
     # apply translation
-    if move is True:
-        transl = trans[:3, 3]
-        if np.any(transl != 0):
-            out_pts += transl
+    if move:
+        out_pts += trans[:3, 3]
 
     return out_pts
 
 
 def rotation(x=0, y=0, z=0):
-    """Create an array with a 4 dimensional rotation matrix
+    """Create an array with a 4 dimensional rotation matrix.
 
     Parameters
     ----------
@@ -210,7 +265,7 @@ def rotation(x=0, y=0, z=0):
 
 
 def rotation3d(x=0, y=0, z=0):
-    """Create an array with a 3 dimensional rotation matrix
+    """Create an array with a 3 dimensional rotation matrix.
 
     Parameters
     ----------
@@ -236,8 +291,49 @@ def rotation3d(x=0, y=0, z=0):
     return r
 
 
+def rotation3d_align_z_axis(target_z_axis):
+    """Compute a rotation matrix to align [ 0 0 1] with supplied target z axis.
+
+    Parameters
+    ----------
+    target_z_axis : array, shape (1, 3)
+        z axis. computed matrix (r) will map [0 0 1] to target_z_axis
+
+    Returns
+    -------
+    r : array, shape (3, 3)
+        The rotation matrix.
+    """
+    target_z_axis = target_z_axis / np.linalg.norm(target_z_axis)
+    r = np.zeros((3, 3))
+    if ((1. + target_z_axis[2]) < 1E-12):
+        r[0, 0] = 1.
+        r[1, 1] = -1.
+        r[2, 2] = -1.
+    else:
+        f = 1. / (1. + target_z_axis[2])
+        r[0, 0] = 1. - 1. * f * target_z_axis[0] * target_z_axis[0]
+        r[0, 1] = -1. * f * target_z_axis[0] * target_z_axis[1]
+        r[0, 2] = target_z_axis[0]
+        r[1, 0] = -1. * f * target_z_axis[0] * target_z_axis[1]
+        r[1, 1] = 1. - 1. * f * target_z_axis[1] * target_z_axis[1]
+        r[1, 2] = target_z_axis[1]
+        r[2, 0] = -target_z_axis[0]
+        r[2, 1] = -target_z_axis[1]
+        r[2, 2] = 1. - f * (target_z_axis[0] * target_z_axis[0] +
+                            target_z_axis[1] * target_z_axis[1])
+
+    # assert that r is a rotation matrix r^t * r = I and det(r) = 1
+    assert(np.any((r.dot(r.T) - np.identity(3)) < 1E-12))
+    assert((linalg.det(r) - 1.0) < 1E-12)
+    # assert that r maps [0 0 1] on the device z axis (target_z_axis)
+    assert(linalg.norm(target_z_axis - r.dot([0, 0, 1])) < 1e-12)
+
+    return r
+
+
 def rotation_angles(m):
-    """Find rotation angles from a transformation matrix
+    """Find rotation angles from a transformation matrix.
 
     Parameters
     ----------
@@ -259,7 +355,7 @@ def rotation_angles(m):
 
 
 def scaling(x=1, y=1, z=1):
-    """Create an array with a scaling matrix
+    """Create an array with a scaling matrix.
 
     Parameters
     ----------
@@ -279,7 +375,7 @@ def scaling(x=1, y=1, z=1):
 
 
 def translation(x=0, y=0, z=0):
-    """Create an array with a translation matrix
+    """Create an array with a translation matrix.
 
     Parameters
     ----------
@@ -299,7 +395,7 @@ def translation(x=0, y=0, z=0):
 
 
 def _ensure_trans(trans, fro='mri', to='head'):
-    """Helper to ensure we have the proper transform"""
+    """Ensure we have the proper transform."""
     if isinstance(fro, string_types):
         from_str = fro
         from_const = _str_to_frame[fro]
@@ -314,20 +410,21 @@ def _ensure_trans(trans, fro='mri', to='head'):
         to_str = _frame_to_str[to]
         to_const = to
     del to
-    err_str = 'trans must go %s<->%s, provided' % (from_str, to_str)
-    if trans is None:
+    err_str = ('trans must be a Transform between %s<->%s, got'
+               % (from_str, to_str))
+    if not isinstance(trans, Transform):
         raise ValueError('%s None' % err_str)
     if set([trans['from'], trans['to']]) != set([from_const, to_const]):
-        raise ValueError('%s trans is %s->%s' % (err_str,
-                                                 _frame_to_str[trans['from']],
-                                                 _frame_to_str[trans['to']]))
+        raise ValueError('%s %s->%s' % (err_str,
+                                        _frame_to_str[trans['from']],
+                                        _frame_to_str[trans['to']]))
     if trans['from'] != from_const:
         trans = invert_transform(trans)
     return trans
 
 
 def _get_trans(trans, fro='mri', to='head'):
-    """Get mri_head_t (from=mri, to=head) from mri filename"""
+    """Get mri_head_t (from=mri, to=head) from mri filename."""
     if isinstance(trans, string_types):
         if not op.isfile(trans):
             raise IOError('trans file "%s" not found' % trans)
@@ -341,11 +438,11 @@ def _get_trans(trans, fro='mri', to='head'):
                 raise RuntimeError('File "%s" did not have 4x4 entries'
                                    % trans)
             fro_to_t = Transform(to, fro, t)
-    elif isinstance(trans, dict):
+    elif isinstance(trans, Transform):
         fro_to_t = trans
-        trans = 'dict'
+        trans = 'instance of Transform'
     elif trans is None:
-        fro_to_t = Transform(fro, to, np.eye(4))
+        fro_to_t = Transform(fro, to)
         trans = 'identity'
     else:
         raise ValueError('transform type %s not known, must be str, dict, '
@@ -356,7 +453,7 @@ def _get_trans(trans, fro='mri', to='head'):
 
 
 def combine_transforms(t_first, t_second, fro, to):
-    """Combine two transforms
+    """Combine two transforms.
 
     Parameters
     ----------
@@ -395,40 +492,45 @@ def combine_transforms(t_first, t_second, fro, to):
     return Transform(fro, to, np.dot(t_second['trans'], t_first['trans']))
 
 
-def read_trans(fname):
-    """Read a -trans.fif file
+def read_trans(fname, return_all=False):
+    """Read a -trans.fif file.
 
     Parameters
     ----------
     fname : str
         The name of the file.
+    return_all : bool
+        If True, return all transformations in the file.
+        False (default) will only return the first.
+
+        .. versionadded:: 0.15
 
     Returns
     -------
-    trans : dict
+    trans : dict | list of dict
         The transformation dictionary from the fif file.
 
     See Also
     --------
     write_trans
-    Transform
+    mne.transforms.Transform
     """
     fid, tree, directory = fiff_open(fname)
 
+    trans = list()
     with fid:
         for t in directory:
             if t.kind == FIFF.FIFF_COORD_TRANS:
-                tag = read_tag(fid, t.pos)
-                break
-        else:
-            raise IOError('This does not seem to be a -trans.fif file.')
-
-    trans = tag.data
-    return trans
+                trans.append(read_tag(fid, t.pos).data)
+                if not return_all:
+                    break
+    if len(trans) == 0:
+        raise IOError('This does not seem to be a -trans.fif file.')
+    return trans if return_all else trans[0]
 
 
 def write_trans(fname, trans):
-    """Write a -trans.fif file
+    """Write a -trans.fif file.
 
     Parameters
     ----------
@@ -448,7 +550,7 @@ def write_trans(fname, trans):
 
 
 def invert_transform(trans):
-    """Invert a transformation between coordinate systems
+    """Invert a transformation between coordinate systems.
 
     Parameters
     ----------
@@ -463,8 +565,8 @@ def invert_transform(trans):
     return Transform(trans['to'], trans['from'], linalg.inv(trans['trans']))
 
 
-def transform_surface_to(surf, dest, trans):
-    """Transform surface to the desired coordinate system
+def transform_surface_to(surf, dest, trans, copy=False):
+    """Transform surface to the desired coordinate system.
 
     Parameters
     ----------
@@ -475,12 +577,15 @@ def transform_surface_to(surf, dest, trans):
         FIFF types.
     trans : dict
         Transformation.
+    copy : bool
+        If False (default), operate in-place.
 
     Returns
     -------
     res : dict
-        Transformed source space. Data are modified in-place.
+        Transformed source space.
     """
+    surf = deepcopy(surf) if copy else surf
     if isinstance(dest, string_types):
         if dest not in _str_to_frame:
             raise KeyError('dest must be one of %s, not "%s"'
@@ -497,7 +602,7 @@ def transform_surface_to(surf, dest, trans):
 
 
 def get_ras_to_neuromag_trans(nasion, lpa, rpa):
-    """Construct a transformation matrix to the MNE head coordinate system
+    """Construct a transformation matrix to the MNE head coordinate system.
 
     Construct a transformation matrix from an arbitrary RAS coordinate system
     to the MNE head coordinate system, in which the x axis passes through the
@@ -548,40 +653,468 @@ def get_ras_to_neuromag_trans(nasion, lpa, rpa):
     return trans
 
 
-def _sphere_to_cartesian(theta, phi, r):
-    """Transform spherical coordinates to cartesian"""
-    z = r * np.sin(phi)
-    rcos_phi = r * np.cos(phi)
-    x = rcos_phi * np.cos(theta)
-    y = rcos_phi * np.sin(theta)
-    return x, y, z
+###############################################################################
+# Spherical coordinates and harmonics
+
+def _cart_to_sph(cart):
+    """Convert Cartesian coordinates to spherical coordinates.
+
+    Parameters
+    ----------
+    cart_pts : ndarray, shape (n_points, 3)
+        Array containing points in Cartesian coordinates (x, y, z)
+
+    Returns
+    -------
+    sph_pts : ndarray, shape (n_points, 3)
+        Array containing points in spherical coordinates (rad, azimuth, polar)
+    """
+    assert cart.ndim == 2 and cart.shape[1] == 3
+    cart = np.atleast_2d(cart)
+    out = np.empty((len(cart), 3))
+    out[:, 0] = np.sqrt(np.sum(cart * cart, axis=1))
+    out[:, 1] = np.arctan2(cart[:, 1], cart[:, 0])
+    out[:, 2] = np.arccos(cart[:, 2] / out[:, 0])
+    out = np.nan_to_num(out)
+    return out
+
+
+def _sph_to_cart(sph):
+    """Convert spherical coordinates to Cartesion coordinates."""
+    assert sph.ndim == 2 and sph.shape[1] == 3
+    sph = np.atleast_2d(sph)
+    out = np.empty((len(sph), 3))
+    out[:, 2] = sph[:, 0] * np.cos(sph[:, 2])
+    xy = sph[:, 0] * np.sin(sph[:, 2])
+    out[:, 0] = xy * np.cos(sph[:, 1])
+    out[:, 1] = xy * np.sin(sph[:, 1])
+    return out
+
+
+def _get_n_moments(order):
+    """Compute the number of multipolar moments (spherical harmonics).
+
+    Equivalent to [1]_ Eq. 32.
+
+    .. note:: This count excludes ``degree=0`` (for ``order=0``).
+
+    Parameters
+    ----------
+    order : array-like
+        Expansion orders, often ``[int_order, ext_order]``.
+
+    Returns
+    -------
+    M : ndarray
+        Number of moments due to each order.
+    """
+    order = np.asarray(order, int)
+    return (order + 2) * order
+
+
+def _sph_to_cart_partials(az, pol, g_rad, g_az, g_pol):
+    """Convert spherical partial derivatives to cartesian coords.
+
+    Note: Because we are dealing with partial derivatives, this calculation is
+    not a static transformation. The transformation matrix itself is dependent
+    on azimuth and polar coord.
+
+    See the 'Spherical coordinate sytem' section here:
+    wikipedia.org/wiki/Vector_fields_in_cylindrical_and_spherical_coordinates
+
+    Parameters
+    ----------
+    az : ndarray, shape (n_points,)
+        Array containing spherical coordinates points (azimuth).
+    pol : ndarray, shape (n_points,)
+        Array containing spherical coordinates points (polar).
+    sph_grads : ndarray, shape (n_points, 3)
+        Array containing partial derivatives at each spherical coordinate
+        (radius, azimuth, polar).
+
+    Returns
+    -------
+    cart_grads : ndarray, shape (n_points, 3)
+        Array containing partial derivatives in Cartesian coordinates (x, y, z)
+    """
+    sph_grads = np.c_[g_rad, g_az, g_pol]
+    cart_grads = np.zeros_like(sph_grads)
+    c_as, s_as = np.cos(az), np.sin(az)
+    c_ps, s_ps = np.cos(pol), np.sin(pol)
+    trans = np.array([[c_as * s_ps, -s_as, c_as * c_ps],
+                      [s_as * s_ps, c_as, c_ps * s_as],
+                      [c_ps, np.zeros_like(c_as), -s_ps]])
+    cart_grads = np.einsum('ijk,kj->ki', trans, sph_grads)
+    return cart_grads
+
+
+def _deg_ord_idx(deg, order):
+    """Get the index into S_in or S_out given a degree and order."""
+    # The -1 here is because we typically exclude the degree=0 term
+    return deg * deg + deg + order - 1
+
+
+def _sh_negate(sh, order):
+    """Get the negative spherical harmonic from a positive one."""
+    assert order >= 0
+    return sh.conj() * (-1. if order % 2 else 1.)  # == (-1) ** order
+
+
+def _sh_complex_to_real(sh, order):
+    """Convert complex to real basis functions.
+
+    Parameters
+    ----------
+    sh : array-like
+        Spherical harmonics. Must be from order >=0 even if negative orders
+        are used.
+    order : int
+        Order (usually 'm') of multipolar moment.
+
+    Returns
+    -------
+    real_sh : array-like
+        The real version of the spherical harmonics.
+
+    Notes
+    -----
+    This does not include the Condon-Shortely phase.
+    """
+    if order == 0:
+        return np.real(sh)
+    else:
+        return np.sqrt(2.) * (np.real if order > 0 else np.imag)(sh)
+
+
+def _sh_real_to_complex(shs, order):
+    """Convert real spherical harmonic pair to complex.
+
+    Parameters
+    ----------
+    shs : ndarray, shape (2, ...)
+        The real spherical harmonics at ``[order, -order]``.
+    order : int
+        Order (usually 'm') of multipolar moment.
+
+    Returns
+    -------
+    sh : array-like, shape (...)
+        The complex version of the spherical harmonics.
+    """
+    if order == 0:
+        return shs[0]
+    else:
+        return (shs[0] + 1j * np.sign(order) * shs[1]) / np.sqrt(2.)
+
+
+def _compute_sph_harm(order, az, pol):
+    """Compute complex spherical harmonics of spherical coordinates."""
+    sph_harm = _get_sph_harm()
+    out = np.empty((len(az), _get_n_moments(order) + 1))
+    # _deg_ord_idx(0, 0) = -1 so we're actually okay to use it here
+    for degree in range(order + 1):
+        for order_ in range(degree + 1):
+            sph = sph_harm(order_, degree, az, pol)
+            out[:, _deg_ord_idx(degree, order_)] = \
+                _sh_complex_to_real(sph, order_)
+            if order_ > 0:
+                out[:, _deg_ord_idx(degree, -order_)] = \
+                    _sh_complex_to_real(_sh_negate(sph, order_), -order_)
+    return out
+
+
+###############################################################################
+# Thin-plate spline transformations
+
+# Adapted from code from the MATLAB file exchange:
+#    https://www.mathworks.com/matlabcentral/fileexchange/
+#            53867-3d-point-set-warping-by-thin-plate-rbf-function
+#    https://www.mathworks.com/matlabcentral/fileexchange/
+#            53828-rbf-or-thin-plate-splines-image-warping
+# Associated (BSD 2-clause) license:
+#
+# Copyright (c) 2015, Wang Lin
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+#       notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above copyright
+#       notice, this list of conditions and the following disclaimer in
+#       the documentation and/or other materials provided with the distribution
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+class _TPSWarp(object):
+    """Transform points using thin-plate spline (TPS) warping.
+
+    Notes
+    -----
+    Adapted from code by `Wang Lin <wanglin193 at hotmail.com>`_.
+
+    References
+    ----------
+    .. [1] Bookstein, F. L. "Principal Warps: Thin Plate Splines and the
+           Decomposition of Deformations." IEEE Trans. Pattern Anal. Mach.
+           Intell. 11, 567-585, 1989.
+    """
+
+    def fit(self, source, destination, reg=1e-3):
+        from scipy.spatial.distance import cdist
+        assert source.shape[1] == destination.shape[1] == 3
+        assert source.shape[0] == destination.shape[0]
+        # Forward warping, different from image warping, use |dist|**2
+        dists = _tps(cdist(source, destination, 'sqeuclidean'))
+        # Y = L * w
+        # L: RBF matrix about source
+        # Y: Points matrix about destination
+        P = np.concatenate((np.ones((source.shape[0], 1)), source), axis=-1)
+        L = np.vstack([np.hstack([dists, P]),
+                       np.hstack([P.T, np.zeros((4, 4))])])
+        Y = np.concatenate((destination, np.zeros((4, 3))), axis=0)
+        # Regularize it a bit
+        L += reg * np.eye(L.shape[0])
+        self._destination = destination.copy()
+        self._weights = linalg.lstsq(L, Y)[0]
+        return self
+
+    @verbose
+    def transform(self, pts, verbose=None):
+        """Apply the warp.
+
+        Parameters
+        ----------
+        pts : shape (n_transform, 3)
+            Source points to warp to the destination.
+
+        Returns
+        -------
+        dest : shape (n_transform, 3)
+            The transformed points.
+        """
+        logger.info('Transforming %s points' % (len(pts),))
+        from scipy.spatial.distance import cdist
+        assert pts.shape[1] == 3
+        # for memory reasons, we should do this in ~100 MB chunks
+        out = np.zeros_like(pts)
+        n_splits = max(int((pts.shape[0] * self._destination.shape[0]) /
+                           (100e6 / 8.)), 1)
+        for this_out, this_pts in zip(np.array_split(out, n_splits),
+                                      np.array_split(pts, n_splits)):
+            dists = _tps(cdist(this_pts, self._destination, 'sqeuclidean'))
+            L = np.hstack((dists, np.ones((dists.shape[0], 1)), this_pts))
+            this_out[:] = np.dot(L, self._weights)
+        assert not (out == 0).any()
+        return out
+
+
+def _tps(distsq):
+    """Thin-plate function (r ** 2) * np.log(r)."""
+    # NOTE: For our warping functions, a radial basis like
+    # exp(-distsq / radius ** 2) could also be used
+    out = np.zeros_like(distsq)
+    mask = distsq > 0  # avoid log(0)
+    valid = distsq[mask]
+    out[mask] = valid * np.log(valid)
+    return out
+
+
+###############################################################################
+# Spherical harmonic approximation + TPS warp
+
+class _SphericalSurfaceWarp(object):
+    """Warp surfaces via spherical harmonic smoothing and thin-plate splines.
+
+    Notes
+    -----
+    This class can be used to warp data from a source subject to
+    a destination subject, as described in [1]_. The procedure is:
+
+        1. Perform a spherical harmonic approximation to the source and
+           destination surfaces, which smooths them and allows arbitrary
+           interpolation.
+        2. Choose a set of matched points on the two surfaces.
+        3. Use thin-plate spline warping (common in 2D image manipulation)
+           to generate transformation coefficients.
+        4. Warp points from the source subject (which should be inside the
+           original surface) to the destination subject.
+
+    .. versionadded:: 0.14
+
+    References
+    ----------
+    .. [1] Darvas F, Ermer JJ, Mosher JC, Leahy RM (2006). "Generic head
+           models for atlas-based EEG source analysis."
+           Human Brain Mapping 27:129-143
+    """
+
+    def __repr__(self):
+        rep = '<SphericalSurfaceWarp : '
+        if not hasattr(self, '_warp'):
+            rep += 'no fitting done >'
+        else:
+            rep += ('fit %d->%d pts using match=%s (%d pts), order=%s, reg=%s>'
+                    % tuple(self._fit_params[key]
+                            for key in ['n_src', 'n_dest', 'match', 'n_match',
+                                        'order', 'reg']))
+        return rep
+
+    @verbose
+    def fit(self, source, destination, order=4, reg=1e-5, center=True,
+            match='oct5', verbose=None):
+        """Fit the warp from source points to destination points.
+
+        Parameters
+        ----------
+        source : array, shape (n_src, 3)
+            The source points.
+        destination : array, shape (n_dest, 3)
+            The destination points.
+        order : int
+            Order of the spherical harmonic fit.
+        reg : float
+            Regularization of the TPS warp.
+        center : bool
+            If True, center the points by fitting a sphere to points
+            that are in a reasonable region for head digitization.
+        match : str
+            The uniformly-spaced points to match on the two surfaces.
+            Can be "ico#" or "oct#" where "#" is an integer.
+            The default is "oct5".
+        verbose : bool, str, int, or None
+            If not None, override default verbose level (see
+            :func:`mne.verbose` and :ref:`Logging documentation <tut_logging>`
+            for more).
+
+        Returns
+        -------
+        inst : instance of SphericalSurfaceWarp
+            The warping object (for chaining).
+        """
+        from .bem import _fit_sphere
+        from .source_space import _check_spacing
+        match_rr = _check_spacing(match, verbose=False)[2]['rr']
+        logger.info('Computing TPS warp')
+        src_center = dest_center = np.zeros(3)
+        if center:
+            logger.info('    Centering data')
+            hsp = np.array([p for p in source
+                            if not (p[2] < -1e-6 and p[1] > 1e-6)])
+            src_center = _fit_sphere(hsp, disp=False)[1]
+            source = source - src_center
+            hsp = np.array([p for p in destination
+                            if not (p[2] < 0 and p[1] > 0)])
+            dest_center = _fit_sphere(hsp, disp=False)[1]
+            destination = destination - dest_center
+            logger.info('    Using centers %s -> %s'
+                        % (np.array_str(src_center, None, 3),
+                           np.array_str(dest_center, None, 3)))
+        self._fit_params = dict(
+            n_src=len(source), n_dest=len(destination), match=match,
+            n_match=len(match_rr), order=order, reg=reg)
+        assert source.shape[1] == destination.shape[1] == 3
+        self._destination = destination.copy()
+        # 1. Compute spherical coordinates of source and destination points
+        logger.info('    Converting to spherical coordinates')
+        src_rad_az_pol = _cart_to_sph(source).T
+        dest_rad_az_pol = _cart_to_sph(destination).T
+        match_rad_az_pol = _cart_to_sph(match_rr).T
+        del match_rr
+        # 2. Compute spherical harmonic coefficients for all points
+        logger.info('    Computing spherical harmonic approximation with '
+                    'order %s' % order)
+        src_sph = _compute_sph_harm(order, *src_rad_az_pol[1:])
+        dest_sph = _compute_sph_harm(order, *dest_rad_az_pol[1:])
+        match_sph = _compute_sph_harm(order, *match_rad_az_pol[1:])
+        # 3. Fit spherical harmonics to both surfaces to smooth them
+        src_coeffs = linalg.lstsq(src_sph, src_rad_az_pol[0])[0]
+        dest_coeffs = linalg.lstsq(dest_sph, dest_rad_az_pol[0])[0]
+        # 4. Smooth both surfaces using these coefficients, and evaluate at
+        #     the "shape" points
+        logger.info('    Matching %d points (%s) on smoothed surfaces'
+                    % (len(match_sph), match))
+        src_rad_az_pol = match_rad_az_pol.copy()
+        src_rad_az_pol[0] = np.abs(np.dot(match_sph, src_coeffs))
+        dest_rad_az_pol = match_rad_az_pol.copy()
+        dest_rad_az_pol[0] = np.abs(np.dot(match_sph, dest_coeffs))
+        # 5. Convert matched points to Cartesion coordinates and put back
+        source = _sph_to_cart(src_rad_az_pol.T)
+        source += src_center
+        destination = _sph_to_cart(dest_rad_az_pol.T)
+        destination += dest_center
+        # 6. Compute TPS warp of matched points from smoothed surfaces
+        self._warp = _TPSWarp().fit(source, destination, reg)
+        self._matched = np.array([source, destination])
+        logger.info('[done]')
+        return self
+
+    @verbose
+    def transform(self, source, verbose=None):
+        """Transform arbitrary source points to the destination.
+
+        Parameters
+        ----------
+        source : ndarray, shape (n_pts, 3)
+            Source points to transform. They do not need to be the same
+            points that were used to generate the model, although ideally
+            they will be inside the convex hull formed by the original
+            source points.
+        verbose : bool, str, int, or None
+            If not None, override default verbose level (see
+            :func:`mne.verbose` and :ref:`Logging documentation <tut_logging>`
+            for more).
+
+        Returns
+        -------
+        destination : ndarray, shape (n_pts, 3)
+            The points transformed to the destination space.
+        """
+        return self._warp.transform(source)
 
 
-def _polar_to_cartesian(theta, r):
-    """Transform polar coordinates to cartesian"""
-    x = r * np.cos(theta)
-    y = r * np.sin(theta)
-    return x, y
+###############################################################################
+# Other transforms
 
+def _pol_to_cart(pol):
+    """Transform polar coordinates to cartesian."""
+    out = np.empty((len(pol), 2))
+    if pol.shape[1] == 2:  # phi, theta
+        out[:, 0] = pol[:, 0] * np.cos(pol[:, 1])
+        out[:, 1] = pol[:, 0] * np.sin(pol[:, 1])
+    else:  # radial distance, theta, phi
+        d = pol[:, 0] * np.sin(pol[:, 2])
+        out[:, 0] = d * np.cos(pol[:, 1])
+        out[:, 1] = d * np.sin(pol[:, 1])
+    return out
 
-def _cartesian_to_sphere(x, y, z):
-    """Transform cartesian coordinates to spherical"""
-    hypotxy = np.hypot(x, y)
-    r = np.hypot(hypotxy, z)
-    elev = np.arctan2(z, hypotxy)
-    az = np.arctan2(y, x)
-    return az, elev, r
 
+def _topo_to_sph(topo):
+    """Convert 2D topo coordinates to spherical coordinates."""
+    assert topo.ndim == 2 and topo.shape[1] == 2
+    sph = np.ones((len(topo), 3))
+    sph[:, 1] = -np.deg2rad(topo[:, 0])
+    sph[:, 2] = np.pi * topo[:, 1]
+    return sph
 
-def _topo_to_sphere(theta, radius):
-    """Convert 2D topo coordinates to spherical."""
-    sph_phi = (0.5 - radius) * 180
-    sph_theta = -theta
-    return sph_phi, sph_theta
 
+###############################################################################
+# Quaternions
 
 def quat_to_rot(quat):
-    """Convert a set of quaternions to rotations
+    """Convert a set of quaternions to rotations.
 
     Parameters
     ----------
@@ -620,7 +1153,7 @@ def quat_to_rot(quat):
 
 
 def _one_rot_to_quat(rot):
-    """Convert a rotation matrix to quaternions"""
+    """Convert a rotation matrix to quaternions."""
     # see e.g. http://www.euclideanspace.com/maths/geometry/rotations/
     #                 conversions/matrixToQuaternion/
     t = 1. + rot[0] + rot[4] + rot[8]
@@ -648,11 +1181,11 @@ def _one_rot_to_quat(rot):
         qy = (rot[5] + rot[7]) / s
         qz = 0.25 * s
         # qw = (rot[3] - rot[1]) / s
-    return qx, qy, qz
+    return np.array((qx, qy, qz))
 
 
 def rot_to_quat(rot):
-    """Convert a set of rotations to quaternions
+    """Convert a set of rotations to quaternions.
 
     Parameters
     ----------
@@ -674,7 +1207,7 @@ def rot_to_quat(rot):
 
 
 def _angle_between_quats(x, y):
-    """Compute the angle between two quaternions w/3-element representations"""
+    """Compute the ang between two quaternions w/3-element representations."""
     # convert to complete quaternion representation
     # use max() here to be safe in case roundoff errs put us over
     x0 = np.sqrt(np.maximum(1. - x[..., 0] ** 2 -
@@ -687,12 +1220,12 @@ def _angle_between_quats(x, y):
 
 
 def _skew_symmetric_cross(a):
-    """The skew-symmetric cross product of a vector"""
+    """Compute the skew-symmetric cross product of a vector."""
     return np.array([[0., -a[2], a[1]], [a[2], 0., -a[0]], [-a[1], a[0], 0.]])
 
 
 def _find_vector_rotation(a, b):
-    """Find the rotation matrix that maps unit vector a to b"""
+    """Find the rotation matrix that maps unit vector a to b."""
     # Rodrigues' rotation formula:
     #   https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula
     #   http://math.stackexchange.com/a/476311
diff --git a/mne/utils.py b/mne/utils.py
index d8773a4..52d3c11 100644
--- a/mne/utils.py
+++ b/mne/utils.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-"""Some utility functions"""
+"""Some utility functions."""
 from __future__ import print_function
 
 # Authors: Alexandre Gramfort <alexandre.gramfort at telecom-paristech.fr>
@@ -7,6 +7,7 @@ from __future__ import print_function
 # License: BSD (3-clause)
 
 import atexit
+from collections import Iterable
 from distutils.version import LooseVersion
 from functools import wraps
 import ftplib
@@ -16,6 +17,8 @@ import inspect
 import json
 import logging
 from math import log, ceil
+import multiprocessing
+import operator
 import os
 import os.path as op
 import platform
@@ -27,13 +30,15 @@ import sys
 import tempfile
 import time
 import traceback
+from unittest import SkipTest
 import warnings
+import webbrowser
 
 import numpy as np
 from scipy import linalg, sparse
 
 from .externals.six.moves import urllib
-from .externals.six import string_types, StringIO, BytesIO
+from .externals.six import string_types, StringIO, BytesIO, integer_types
 from .externals.decorator import decorator
 
 from .fixes import _get_args
@@ -49,6 +54,7 @@ def _memory_usage(*args, **kwargs):
         args[0]()
     return [-1]
 
+
 try:
     from memory_profiler import memory_usage
 except ImportError:
@@ -56,7 +62,7 @@ except ImportError:
 
 
 def nottest(f):
-    """Decorator to mark a function as not a test"""
+    """Mark a function as not a test (decorator)."""
     f.__test__ = False
     return f
 
@@ -72,6 +78,23 @@ _doc_special_members = ('__contains__', '__getitem__', '__iter__', '__len__',
 # RANDOM UTILITIES
 
 
+def _ensure_int(x, name='unknown', must_be='an int'):
+    """Ensure a variable is an integer."""
+    # This is preferred over numbers.Integral, see:
+    # https://github.com/scipy/scipy/pull/7351#issuecomment-299713159
+    try:
+        x = int(operator.index(x))
+    except TypeError:
+        raise TypeError('%s must be %s, got %s' % (name, must_be, type(x)))
+    return x
+
+
+def _pl(x, non_pl=''):
+    """Determine if plural should be used."""
+    len_x = x if isinstance(x, (integer_types, np.generic)) else len(x)
+    return non_pl if len_x == 1 else 's'
+
+
 def _explain_exception(start=-1, stop=None, prefix='> '):
     """Explain an exception."""
     # start=-1 means "only the most recent caller"
@@ -83,22 +106,8 @@ def _explain_exception(start=-1, stop=None, prefix='> '):
     return string
 
 
-def _check_copy_dep(inst, copy, kind='inst'):
-    """Check for copy deprecation for 0.14"""
-    # For methods with copy=False default, we only need one release cycle
-    # for deprecation (0.13). For copy=True, we first need to go to copy=False
-    # (one cycle; 0.13) then remove copy altogether (one cycle; 0.14).
-    if copy:
-        warn('The copy parameter is deprecated and will be removed in 0.14. '
-             'In 0.13 the default is copy=False. Use %s.copy() if necessary.'
-             % (kind,), DeprecationWarning)
-    elif copy is None:
-        copy = False
-    return inst.copy() if copy else inst
-
-
 def _get_call_line(in_verbose=False):
-    """Helper to get the call line from within a function"""
+    """Get the call line from within a function."""
     # XXX Eventually we could auto-triage whether in a `verbose` decorated
     # function or not.
     # NB This probably only works for functions that are undecorated,
@@ -111,7 +120,7 @@ def _get_call_line(in_verbose=False):
 
 
 def _sort_keys(x):
-    """Sort and return keys of dict"""
+    """Sort and return keys of dict."""
     keys = list(x.keys())  # note: not thread-safe
     idx = np.argsort([str(k) for k in keys])
     keys = [keys[ii] for ii in idx]
@@ -119,7 +128,7 @@ def _sort_keys(x):
 
 
 def object_hash(x, h=None):
-    """Hash a reasonable python object
+    """Hash a reasonable python object.
 
     Parameters
     ----------
@@ -164,7 +173,7 @@ def object_hash(x, h=None):
 
 
 def object_size(x):
-    """Estimate the size of a reasonable python object
+    """Estimate the size of a reasonable python object.
 
     Parameters
     ----------
@@ -204,7 +213,7 @@ def object_size(x):
 
 
 def object_diff(a, b, pre=''):
-    """Compute all differences between two python variables
+    """Compute all differences between two python variables.
 
     Parameters
     ----------
@@ -270,7 +279,7 @@ def object_diff(a, b, pre=''):
 
 
 def check_random_state(seed):
-    """Turn seed into a np.random.RandomState instance
+    """Turn seed into a np.random.RandomState instance.
 
     If seed is None, return the RandomState singleton used by np.random.
     If seed is an int, return a new RandomState instance seeded with seed.
@@ -288,7 +297,7 @@ def check_random_state(seed):
 
 
 def split_list(l, n):
-    """split list in n (approx) equal pieces"""
+    """Split list in n (approx) equal pieces."""
     n = int(n)
     sz = len(l) // n
     for i in range(n - 1):
@@ -297,7 +306,7 @@ def split_list(l, n):
 
 
 def create_chunks(sequence, size):
-    """Generate chunks from a sequence
+    """Generate chunks from a sequence.
 
     Parameters
     ----------
@@ -310,7 +319,7 @@ def create_chunks(sequence, size):
 
 
 def sum_squared(X):
-    """Compute norm of an array
+    """Compute norm of an array.
 
     Parameters
     ----------
@@ -327,7 +336,7 @@ def sum_squared(X):
 
 
 def warn(message, category=RuntimeWarning):
-    """Emit a warning with trace outside the mne namespace
+    """Emit a warning with trace outside the mne namespace.
 
     This function takes arguments like warnings.warn, and sends messages
     using both ``warnings.warn`` and ``logger.warn``. Warnings can be
@@ -345,21 +354,26 @@ def warn(message, category=RuntimeWarning):
     import mne
     root_dir = op.dirname(mne.__file__)
     frame = None
-    stack = inspect.stack()
-    last_fname = ''
-    for fi, frame in enumerate(stack):
-        fname, lineno = frame[1:3]
-        if fname == '<string>' and last_fname == 'utils.py':  # in verbose dec
-            last_fname = fname
-            continue
-        # treat tests as scripts
-        # and don't capture unittest/case.py (assert_raises)
-        if not (fname.startswith(root_dir) or
-                ('unittest' in fname and 'case' in fname)) or \
-                op.basename(op.dirname(fname)) == 'tests':
-            break
-        last_fname = op.basename(fname)
     if logger.level <= logging.WARN:
+        last_fname = ''
+        frame = inspect.currentframe()
+        while frame:
+            fname = frame.f_code.co_filename
+            lineno = frame.f_lineno
+            # in verbose dec
+            if fname == '<string>' and last_fname == 'utils.py':
+                last_fname = fname
+                frame = frame.f_back
+                continue
+            # treat tests as scripts
+            # and don't capture unittest/case.py (assert_raises)
+            if not (fname.startswith(root_dir) or
+                    ('unittest' in fname and 'case' in fname)) or \
+                    op.basename(op.dirname(fname)) == 'tests':
+                break
+            last_fname = op.basename(fname)
+            frame = frame.f_back
+        del frame
         # We need to use this instead of warn(message, category, stacklevel)
         # because we move out of the MNE stack, so warnings won't properly
         # recognize the module name (and our warnings.simplefilter will fail)
@@ -369,7 +383,7 @@ def warn(message, category=RuntimeWarning):
 
 
 def check_fname(fname, filetype, endings, endings_err=()):
-    """Enforce MNE filename conventions
+    """Enforce MNE filename conventions.
 
     Parameters
     ----------
@@ -395,11 +409,13 @@ def check_fname(fname, filetype, endings, endings_err=()):
 
 
 class WrapStdOut(object):
-    """Dynamically wrap to sys.stdout
+    """Dynamically wrap to sys.stdout.
 
     This makes packages that monkey-patch sys.stdout (e.g.doctest,
-    sphinx-gallery) work properly."""
-    def __getattr__(self, name):
+    sphinx-gallery) work properly.
+    """
+
+    def __getattr__(self, name):  # noqa: D105
         # Even more ridiculous than this class, this must be sys.stdout (not
         # just stdout) in order for this to work (tested on OSX and Linux)
         if hasattr(sys.stdout, name):
@@ -409,7 +425,7 @@ class WrapStdOut(object):
 
 
 class _TempDir(str):
-    """Class for creating and auto-destroying temp dir
+    """Create and auto-destroy temp dir.
 
     This is designed to be used with testing modules. Instances should be
     defined inside test functions. Instances defined at module level can not
@@ -419,20 +435,20 @@ class _TempDir(str):
     cleanup can fail because the rmtree function may be cleaned up before this
     object (an alternative could be using the atexit module instead).
     """
-    def __new__(self):
-        new = str.__new__(self, tempfile.mkdtemp())
+
+    def __new__(self):  # noqa: D105
+        new = str.__new__(self, tempfile.mkdtemp(prefix='tmp_mne_tempdir_'))
         return new
 
-    def __init__(self):
+    def __init__(self):  # noqa: D102
         self._path = self.__str__()
 
-    def __del__(self):
+    def __del__(self):  # noqa: D105
         rmtree(self._path, ignore_errors=True)
 
 
-def estimate_rank(data, tol='auto', return_singular=False,
-                  norm=True, copy=None):
-    """Helper to estimate the rank of data
+def estimate_rank(data, tol='auto', return_singular=False, norm=True):
+    """Estimate the rank of data.
 
     This function will normalize the rows of the data (typically
     channels or vertices) such that non-zero singular values
@@ -454,9 +470,6 @@ def estimate_rank(data, tol='auto', return_singular=False,
     norm : bool
         If True, data will be scaled by their estimated row-wise norm.
         Else data are assumed to be scaled. Defaults to True.
-    copy : bool
-        This parameter has been deprecated and will be removed in 0.13.
-        It is ignored in 0.12.
 
     Returns
     -------
@@ -466,8 +479,6 @@ def estimate_rank(data, tol='auto', return_singular=False,
         If return_singular is True, the singular values that were
         thresholded to determine the rank are also returned.
     """
-    if copy is not None:
-        warn('copy is deprecated and ignored. It will be removed in 0.13.')
     data = data.copy()  # operate on a copy
     if norm is True:
         norms = _compute_row_norms(data)
@@ -487,15 +498,14 @@ def estimate_rank(data, tol='auto', return_singular=False,
 
 
 def _compute_row_norms(data):
-    """Compute scaling based on estimated norm"""
+    """Compute scaling based on estimated norm."""
     norms = np.sqrt(np.sum(data ** 2, axis=1))
     norms[norms == 0] = 1.0
     return norms
 
 
 def _reject_data_segments(data, reject, flat, decim, info, tstep):
-    """Reject data segments using peak-to-peak amplitude
-    """
+    """Reject data segments using peak-to-peak amplitude."""
     from .epochs import _is_good
     from .io.pick import channel_indices_by_type
 
@@ -529,19 +539,16 @@ def _reject_data_segments(data, reject, flat, decim, info, tstep):
 
 
 def _get_inst_data(inst):
-    """get data from MNE object instance like Raw, Epochs or Evoked.
-    Returns a view, not a copy!"""
-    from .io.base import _BaseRaw
-    from .epochs import _BaseEpochs
+    """Get data view from MNE object instance like Raw, Epochs or Evoked."""
+    from .io.base import BaseRaw
+    from .epochs import BaseEpochs
     from . import Evoked
     from .time_frequency.tfr import _BaseTFR
 
-    if isinstance(inst, (_BaseRaw, _BaseEpochs)):
+    if isinstance(inst, (BaseRaw, BaseEpochs, Evoked, _BaseTFR)):
         if not inst.preload:
             inst.load_data()
         return inst._data
-    elif isinstance(inst, (Evoked, _BaseTFR)):
-        return inst.data
     else:
         raise TypeError('The argument must be an instance of Raw, Epochs, '
                         'Evoked, EpochsTFR or AverageTFR, got {0}.'.format(
@@ -549,13 +556,14 @@ def _get_inst_data(inst):
 
 
 class _FormatDict(dict):
-    """Helper for pformat()"""
+    """Help pformat() work properly."""
+
     def __missing__(self, key):
         return "{" + key + "}"
 
 
 def pformat(temp, **fmt):
-    """Partially format a template string.
+    """Format a template string partially.
 
     Examples
     --------
@@ -567,12 +575,6 @@ def pformat(temp, **fmt):
     return formatter.vformat(temp, (), mapping)
 
 
-def trait_wraith(*args, **kwargs):
-    # Stand in for traits to allow importing traits based modules when the
-    # traits library is not installed
-    return lambda x: x
-
-
 ###############################################################################
 # DECORATORS
 
@@ -583,7 +585,7 @@ warnings.filterwarnings('always', category=DeprecationWarning, module='mne')
 
 
 class deprecated(object):
-    """Decorator to mark a function or class as deprecated.
+    """Mark a function or class as deprecated (decorator).
 
     Issue a warning when the function is called/the class is instantiated and
     adds a warning to the docstring.
@@ -605,17 +607,18 @@ class deprecated(object):
     extra: string
         To be added to the deprecation messages.
     """
+
     # Adapted from http://wiki.python.org/moin/PythonDecoratorLibrary,
     # but with many changes.
 
     # scikit-learn will not import on all platforms b/c it can be
     # sklearn or scikits.learn, so a self-contained example is used above
 
-    def __init__(self, extra=''):
+    def __init__(self, extra=''):  # noqa: D102
         self.extra = extra
 
-    def __call__(self, obj):
-        """Call
+    def __call__(self, obj):  # noqa: D105
+        """Call.
 
         Parameters
         ----------
@@ -647,8 +650,7 @@ class deprecated(object):
         return cls
 
     def _decorate_fun(self, fun):
-        """Decorate function fun"""
-
+        """Decorate function fun."""
         msg = "Function %s is deprecated" % fun.__name__
         if self.extra:
             msg += "; %s" % self.extra
@@ -674,10 +676,13 @@ class deprecated(object):
 
 @decorator
 def verbose(function, *args, **kwargs):
-    """Improved verbose decorator to allow functions to override log-level
+    """Verbose decorator to allow functions to override log-level.
 
-    Do not call this directly to set global verbosity level, instead use
-    set_log_level().
+    This decorator is used to set the verbose level during a function or method
+    call, such as :func:`mne.compute_covariance`. The `verbose` keyword
+    argument can be 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL', True (an
+    alias for 'INFO'), or False (an alias for 'WARNING'). To set the global
+    verbosity level for all functions, use :func:`mne.set_log_level`.
 
     Parameters
     ----------
@@ -688,7 +693,22 @@ def verbose(function, *args, **kwargs):
     -------
     dec : function
         The decorated function
-    """
+
+    Examples
+    --------
+    You can use the ``verbose`` argument to set the verbose level on the fly::
+        >>> import mne
+        >>> cov = mne.compute_raw_covariance(raw, verbose='WARNING')  # doctest: +SKIP
+        >>> cov = mne.compute_raw_covariance(raw, verbose='INFO')  # doctest: +SKIP
+        Using up to 49 segments
+        Number of samples used : 5880
+        [done]
+
+    See Also
+    --------
+    set_log_level
+    set_config
+    """  # noqa: E501
     arg_names = _get_args(function)
     default_level = verbose_level = None
     if len(arg_names) > 0 and arg_names[0] == 'self':
@@ -709,40 +729,26 @@ def verbose(function, *args, **kwargs):
 
 
 class use_log_level(object):
-    """Context handler for logging level
+    """Context handler for logging level.
 
     Parameters
     ----------
     level : int
         The level to use.
     """
-    def __init__(self, level):
+
+    def __init__(self, level):  # noqa: D102
         self.level = level
 
-    def __enter__(self):
+    def __enter__(self):  # noqa: D105
         self.old_level = set_log_level(self.level, True)
 
-    def __exit__(self, *args):
+    def __exit__(self, *args):  # noqa: D105
         set_log_level(self.old_level)
 
 
- at nottest
-def slow_test(f):
-    """Decorator for slow tests"""
-    f.slow_test = True
-    return f
-
-
- at nottest
-def ultra_slow_test(f):
-    """Decorator for ultra slow tests"""
-    f.ultra_slow_test = True
-    f.slow_test = True
-    return f
-
-
 def has_nibabel(vox2ras_tkr=False):
-    """Determine if nibabel is installed
+    """Determine if nibabel is installed.
 
     Parameters
     ----------
@@ -767,31 +773,31 @@ def has_nibabel(vox2ras_tkr=False):
 
 
 def has_mne_c():
-    """Aux function"""
+    """Check for MNE-C."""
     return 'MNE_ROOT' in os.environ
 
 
 def has_freesurfer():
-    """Aux function"""
+    """Check for Freesurfer."""
     return 'FREESURFER_HOME' in os.environ
 
 
 def requires_nibabel(vox2ras_tkr=False):
-    """Aux function"""
+    """Check for nibabel."""
+    import pytest
     extra = ' with vox2ras_tkr support' if vox2ras_tkr else ''
-    return np.testing.dec.skipif(not has_nibabel(vox2ras_tkr),
-                                 'Requires nibabel%s' % extra)
+    return pytest.mark.skipif(not has_nibabel(vox2ras_tkr),
+                              reason='Requires nibabel%s' % extra)
 
 
 def buggy_mkl_svd(function):
-    """Decorator for tests that make calls to SVD and intermittently fail"""
+    """Decorate tests that make calls to SVD and intermittently fail."""
     @wraps(function)
     def dec(*args, **kwargs):
         try:
             return function(*args, **kwargs)
         except np.linalg.LinAlgError as exp:
             if 'SVD did not converge' in str(exp):
-                from nose.plugins.skip import SkipTest
                 msg = 'Intel MKL SVD convergence error detected, skipping test'
                 warn(msg)
                 raise SkipTest(msg)
@@ -800,36 +806,32 @@ def buggy_mkl_svd(function):
 
 
 def requires_version(library, min_version):
-    """Helper for testing"""
-    return np.testing.dec.skipif(not check_version(library, min_version),
-                                 'Requires %s version >= %s'
-                                 % (library, min_version))
+    """Check for a library version."""
+    import pytest
+    return pytest.mark.skipif(not check_version(library, min_version),
+                              reason=('Requires %s version >= %s'
+                                      % (library, min_version)))
 
 
 def requires_module(function, name, call=None):
-    """Decorator to skip test if package is not available"""
+    """Skip a test if package is not available (decorator)."""
     call = ('import %s' % name) if call is None else call
-    try:
-        from nose.plugins.skip import SkipTest
-    except ImportError:
-        SkipTest = AssertionError
 
     @wraps(function)
-    def dec(*args, **kwargs):
-        skip = False
+    def dec(*args, **kwargs):  # noqa: D102
         try:
             exec(call) in globals(), locals()
-        except Exception:
-            skip = True
-        if skip is True:
-            raise SkipTest('Test %s skipped, requires %s'
-                           % (function.__name__, name))
+        except Exception as exc:
+            msg = 'Test %s skipped, requires %s.' % (function.__name__, name)
+            if len(str(exc)) > 0:
+                msg += ' Got exception (%s)' % (exc,)
+            raise SkipTest(msg)
         return function(*args, **kwargs)
     return dec
 
 
 def copy_doc(source):
-    """Decorator to copy the docstring from another function.
+    """Copy the docstring from another function (decorator).
 
     The docstring of the source function is prepepended to the docstring of the
     function wrapped by this decorator.
@@ -1014,16 +1016,9 @@ if version < required_version:
     raise ImportError
 """
 
-_sklearn_0_15_call = """
-required_version = '0.15'
-import sklearn
-version = LooseVersion(sklearn.__version__)
-if version < required_version:
-    raise ImportError
-"""
-
 _mayavi_call = """
-from mayavi import mlab
+with warnings.catch_warnings(record=True):  # traits
+    from mayavi import mlab
 mlab.options.backend = 'test'
 """
 
@@ -1049,8 +1044,6 @@ if not has_nibabel() and not has_freesurfer():
 
 requires_pandas = partial(requires_module, name='pandas', call=_pandas_call)
 requires_sklearn = partial(requires_module, name='sklearn', call=_sklearn_call)
-requires_sklearn_0_15 = partial(requires_module, name='sklearn',
-                                call=_sklearn_0_15_call)
 requires_mayavi = partial(requires_module, name='mayavi', call=_mayavi_call)
 requires_mne = partial(requires_module, name='MNE-C', call=_mne_call)
 requires_freesurfer = partial(requires_module, name='Freesurfer',
@@ -1062,10 +1055,10 @@ requires_fs_or_nibabel = partial(requires_module, name='nibabel or Freesurfer',
 
 requires_tvtk = partial(requires_module, name='TVTK',
                         call='from tvtk.api import tvtk')
-requires_statsmodels = partial(requires_module, name='statsmodels')
-requires_patsy = partial(requires_module, name='patsy')
 requires_pysurfer = partial(requires_module, name='PySurfer',
-                            call='from surfer import Brain')
+                            call="""import warnings
+with warnings.catch_warnings(record=True):
+    from surfer import Brain""")
 requires_PIL = partial(requires_module, name='PIL',
                        call='from PIL import Image')
 requires_good_network = partial(
@@ -1077,13 +1070,12 @@ requires_ftp = partial(
     call='if int(os.environ.get("MNE_SKIP_FTP_TESTS", 0)):\n'
          '    raise ImportError')
 requires_nitime = partial(requires_module, name='nitime')
-requires_traits = partial(requires_module, name='traits')
 requires_h5py = partial(requires_module, name='h5py')
 requires_numpydoc = partial(requires_module, name='numpydoc')
 
 
 def check_version(library, min_version):
-    """Check minimum library version required
+    r"""Check minimum library version required.
 
     Parameters
     ----------
@@ -1091,7 +1083,8 @@ def check_version(library, min_version):
         The library name to import. Must have a ``__version__`` property.
     min_version : str
         The minimum version string. Anything that matches
-        ``'(\\d+ | [a-z]+ | \\.)'``
+        ``'(\d+ | [a-z]+ | \.)'``. Can also be empty to skip version
+        check (just check for library presence).
 
     Returns
     -------
@@ -1104,20 +1097,21 @@ def check_version(library, min_version):
     except ImportError:
         ok = False
     else:
-        this_version = LooseVersion(library.__version__)
-        if this_version < min_version:
-            ok = False
+        if min_version:
+            this_version = LooseVersion(library.__version__)
+            if this_version < min_version:
+                ok = False
     return ok
 
 
 def _check_mayavi_version(min_version='4.3.0'):
-    """Helper for mayavi"""
+    """Check mayavi version."""
     if not check_version('mayavi', min_version):
         raise RuntimeError("Need mayavi >= %s" % min_version)
 
 
 def _check_pyface_backend():
-    """Check the currently selected Pyface backend
+    """Check the currently selected Pyface backend.
 
     Returns
     -------
@@ -1130,7 +1124,7 @@ def _check_pyface_backend():
 
     Notes
     -----
-    See also: http://docs.enthought.com/pyface/
+    See also http://docs.enthought.com/pyface/.
     """
     try:
         from traits.trait_base import ETSConfig
@@ -1145,9 +1139,16 @@ def _check_pyface_backend():
     return backend, status
 
 
+def _import_mlab():
+    """Quietly import mlab."""
+    with warnings.catch_warnings(record=True):
+        from mayavi import mlab
+    return mlab
+
+
 @verbose
 def run_subprocess(command, verbose=None, *args, **kwargs):
-    """Run command using subprocess.Popen
+    """Run command using subprocess.Popen.
 
     Run command and wait for command to complete. If the return code was zero
     then return, otherwise raise CalledProcessError.
@@ -1159,8 +1160,9 @@ def run_subprocess(command, verbose=None, *args, **kwargs):
     command : list of str | str
         Command to run as subprocess (see subprocess.Popen documentation).
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
-        Defaults to self.verbose.
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more). Defaults to
+        self.verbose.
     *args, **kwargs : arguments
         Additional arguments to pass to subprocess.Popen.
 
@@ -1171,11 +1173,12 @@ def run_subprocess(command, verbose=None, *args, **kwargs):
     stderr : str
         Stderr returned by the process.
     """
-    for stdxxx, sys_stdxxx in (['stderr', sys.stderr],
-                               ['stdout', sys.stdout]):
-        if stdxxx not in kwargs:
+    for stdxxx, sys_stdxxx, thresh in (
+            ['stderr', sys.stderr, logging.ERROR],
+            ['stdout', sys.stdout, logging.WARNING]):
+        if stdxxx not in kwargs and logger.level >= thresh:
             kwargs[stdxxx] = subprocess.PIPE
-        elif kwargs[stdxxx] is sys_stdxxx:
+        elif kwargs.get(stdxxx, sys_stdxxx) is sys_stdxxx:
             if isinstance(sys_stdxxx, StringIO):
                 # nose monkey patches sys.stderr and sys.stdout to StringIO
                 kwargs[stdxxx] = subprocess.PIPE
@@ -1205,15 +1208,10 @@ def run_subprocess(command, verbose=None, *args, **kwargs):
         logger.error('Command not found: %s' % command_name)
         raise
     stdout_, stderr = p.communicate()
-    stdout_ = '' if stdout_ is None else stdout_.decode('utf-8')
-    stderr = '' if stderr is None else stderr.decode('utf-8')
-
-    if stdout_.strip():
-        logger.info("stdout:\n%s" % stdout_)
-    if stderr.strip():
-        logger.info("stderr:\n%s" % stderr)
-
+    stdout_ = u'' if stdout_ is None else stdout_.decode('utf-8')
+    stderr = u'' if stderr is None else stderr.decode('utf-8')
     output = (stdout_, stderr)
+
     if p.returncode:
         print(output)
         err_fun = subprocess.CalledProcessError.__init__
@@ -1229,7 +1227,7 @@ def run_subprocess(command, verbose=None, *args, **kwargs):
 # LOGGING
 
 def set_log_level(verbose=None, return_old_level=False):
-    """Convenience function for setting the logging level
+    """Set the logging level.
 
     Parameters
     ----------
@@ -1265,7 +1263,7 @@ def set_log_level(verbose=None, return_old_level=False):
 
 
 def set_log_file(fname=None, output_format='%(message)s', overwrite=None):
-    """Convenience function for setting the log to print to a file
+    """Set the log to print to a file.
 
     Parameters
     ----------
@@ -1315,12 +1313,13 @@ def set_log_file(fname=None, output_format='%(message)s', overwrite=None):
 
 
 class catch_logging(object):
-    """Helper to store logging
+    """Store logging.
 
     This will remove all other logging handlers, and return the handler to
     stdout when complete.
     """
-    def __enter__(self):
+
+    def __enter__(self):  # noqa: D105
         self._data = StringIO()
         self._lh = logging.StreamHandler(self._data)
         self._lh.setFormatter(logging.Formatter('%(message)s'))
@@ -1329,7 +1328,7 @@ class catch_logging(object):
         logger.addHandler(self._lh)
         return self._data
 
-    def __exit__(self, *args):
+    def __exit__(self, *args):  # noqa: D105
         logger.removeHandler(self._lh)
         set_log_file(None)
 
@@ -1338,7 +1337,7 @@ class catch_logging(object):
 # CONFIG / PREFS
 
 def get_subjects_dir(subjects_dir=None, raise_error=False):
-    """Safely use subjects_dir input to return SUBJECTS_DIR
+    """Safely use subjects_dir input to return SUBJECTS_DIR.
 
     Parameters
     ----------
@@ -1363,7 +1362,7 @@ _temp_home_dir = None
 
 
 def _get_extra_data_path(home_dir=None):
-    """Get path to extra data (config, tables, etc.)"""
+    """Get path to extra data (config, tables, etc.)."""
     global _temp_home_dir
     if home_dir is None:
         home_dir = os.environ.get('_MNE_FAKE_HOME_DIR')
@@ -1396,7 +1395,7 @@ def _get_extra_data_path(home_dir=None):
 
 
 def get_config_path(home_dir=None):
-    """Get path to standard mne-python config file
+    r"""Get path to standard mne-python config file.
 
     Parameters
     ----------
@@ -1436,7 +1435,7 @@ def set_cache_dir(cache_dir):
 
 
 def set_memmap_min_size(memmap_min_size):
-    """Set the minimum size for memmaping of arrays for parallel processing
+    """Set the minimum size for memmaping of arrays for parallel processing.
 
     Parameters
     ----------
@@ -1459,19 +1458,36 @@ def set_memmap_min_size(memmap_min_size):
 known_config_types = (
     'MNE_BROWSE_RAW_SIZE',
     'MNE_CACHE_DIR',
+    'MNE_COREG_COPY_ANNOT',
+    'MNE_COREG_GUESS_MRI_SUBJECT',
+    'MNE_COREG_HEAD_HIGH_RES',
+    'MNE_COREG_HEAD_OPACITY',
+    'MNE_COREG_PREPARE_BEM',
+    'MNE_COREG_SCALE_LABELS',
+    'MNE_COREG_SCENE_HEIGHT',
+    'MNE_COREG_SCENE_WIDTH',
+    'MNE_COREG_SUBJECTS_DIR',
     'MNE_CUDA_IGNORE_PRECISION',
     'MNE_DATA',
     'MNE_DATASETS_BRAINSTORM_PATH',
     'MNE_DATASETS_EEGBCI_PATH',
+    'MNE_DATASETS_HF_SEF_PATH',
     'MNE_DATASETS_MEGSIM_PATH',
     'MNE_DATASETS_MISC_PATH',
+    'MNE_DATASETS_MTRF_PATH',
     'MNE_DATASETS_SAMPLE_PATH',
     'MNE_DATASETS_SOMATO_PATH',
     'MNE_DATASETS_MULTIMODAL_PATH',
     'MNE_DATASETS_SPM_FACE_DATASETS_TESTS',
     'MNE_DATASETS_SPM_FACE_PATH',
     'MNE_DATASETS_TESTING_PATH',
+    'MNE_DATASETS_VISUAL_92_CATEGORIES_PATH',
+    'MNE_DATASETS_FIELDTRIP_CMC_PATH',
     'MNE_FORCE_SERIAL',
+    'MNE_KIT2FIFF_STIM_CHANNELS',
+    'MNE_KIT2FIFF_STIM_CHANNEL_CODING',
+    'MNE_KIT2FIFF_STIM_CHANNEL_SLOPE',
+    'MNE_KIT2FIFF_STIM_CHANNEL_THRESHOLD',
     'MNE_LOGGING_LEVEL',
     'MNE_MEMMAP_MIN_SIZE',
     'MNE_SKIP_FTP_TESTS',
@@ -1490,7 +1506,7 @@ known_config_wildcards = (
 
 
 def _load_config(config_path, raise_error=False):
-    """Helper to safely load a config file"""
+    """Safely load a config file."""
     with open(config_path, 'r') as fid:
         try:
             config = json.load(fid)
@@ -1506,14 +1522,15 @@ def _load_config(config_path, raise_error=False):
 
 
 def get_config(key=None, default=None, raise_error=False, home_dir=None):
-    """Read mne(-python) preference from env, then mne-python config
+    """Read MNE-Python preferences from environment or config file.
 
     Parameters
     ----------
     key : None | str
         The preference key to look for. The os evironment is searched first,
         then the mne-python config file is parsed.
-        If None, all the config parameters present in the path are returned.
+        If None, all the config parameters present in environment variables or
+        the path are returned.
     default : str | None
         Value to return if the key is not found.
     raise_error : bool
@@ -1532,7 +1549,6 @@ def get_config(key=None, default=None, raise_error=False, home_dir=None):
     --------
     set_config
     """
-
     if key is not None and not isinstance(key, string_types):
         raise TypeError('key must be a string')
 
@@ -1543,15 +1559,17 @@ def get_config(key=None, default=None, raise_error=False, home_dir=None):
     # second, look for it in mne-python config file
     config_path = get_config_path(home_dir=home_dir)
     if not op.isfile(config_path):
-        key_found = False
-        val = default
+        config = {}
     else:
         config = _load_config(config_path)
-        if key is None:
-            return config
-        key_found = key in config
-        val = config.get(key, default)
-    if not key_found and raise_error is True:
+
+    if key is None:
+        # update config with environment variables
+        env_keys = (set(config).union(known_config_types).
+                    intersection(os.environ))
+        config.update({key: os.environ[key] for key in env_keys})
+        return config
+    elif raise_error is True and key not in config:
         meth_1 = 'os.environ["%s"] = VALUE' % key
         meth_2 = 'mne.utils.set_config("%s", VALUE, set_env=True)' % key
         raise KeyError('Key "%s" not found in environment or in the '
@@ -1562,11 +1580,12 @@ def get_config(key=None, default=None, raise_error=False, home_dir=None):
                        'set the environment variable before '
                        'running python.'
                        % (key, config_path, meth_1, meth_2))
-    return val
+    else:
+        return config.get(key, default)
 
 
-def set_config(key, value, home_dir=None, set_env=None):
-    """Set mne-python preference in config
+def set_config(key, value, home_dir=None, set_env=True):
+    """Set a MNE-Python preference key in the config file and environment.
 
     Parameters
     ----------
@@ -1580,8 +1599,8 @@ def set_config(key, value, home_dir=None, set_env=None):
         The folder that contains the .mne config folder.
         If None, it is found automatically.
     set_env : bool
-        If True, update :data:`os.environ` in addition to updating the
-        MNE-Python config file.
+        If True (default), update :data:`os.environ` in addition to
+        updating the MNE-Python config file.
 
     See Also
     --------
@@ -1598,11 +1617,6 @@ def set_config(key, value, home_dir=None, set_env=None):
     if key not in known_config_types and not \
             any(k in key for k in known_config_wildcards):
         warn('Setting non-standard config type: "%s"' % key)
-    if set_env is None:
-        warnings.warn('set_env defaults to False in 0.13 but will change '
-                      'to True in 0.14, set it explicitly to avoid this '
-                      'warning', DeprecationWarning)
-        set_env = False
 
     # Read all previous values
     config_path = get_config_path(home_dir=home_dir)
@@ -1631,13 +1645,14 @@ def set_config(key, value, home_dir=None, set_env=None):
 
 
 class ProgressBar(object):
-    """Class for generating a command-line progressbar
+    """Generate a command-line progressbar.
 
     Parameters
     ----------
-    max_value : int
+    max_value : int | iterable
         Maximum value of process (e.g. number of samples to process, bytes to
-        download, etc.).
+        download, etc.). If an iterable is given, then `max_value` will be set
+        to the length of this iterable.
     initial_value : int
         Initial value of process, useful when resuming process from a specific
         value, defaults to 0.
@@ -1672,9 +1687,15 @@ class ProgressBar(object):
     template = '\r[{0}{1}] {2:.05f} {3} {4}   '
 
     def __init__(self, max_value, initial_value=0, mesg='', max_chars=40,
-                 progress_character='.', spinner=False, verbose_bool=True):
+                 progress_character='.', spinner=False,
+                 verbose_bool=True):  # noqa: D102
         self.cur_value = initial_value
-        self.max_value = max_value
+        if isinstance(max_value, Iterable):
+            self.max_value = len(max_value)
+            self.iterable = max_value
+        else:
+            self.max_value = float(max_value)
+            self.iterable = None
         self.mesg = mesg
         self.max_chars = max_chars
         self.progress_character = progress_character
@@ -1682,9 +1703,11 @@ class ProgressBar(object):
         self.spinner_index = 0
         self.n_spinner = len(self.spinner_symbols)
         self._do_print = verbose_bool
+        self.cur_time = time.time()
+        self.cur_rate = 0
 
     def update(self, cur_value, mesg=None):
-        """Update progressbar with current value of process
+        """Update progressbar with current value of process.
 
         Parameters
         ----------
@@ -1697,9 +1720,15 @@ class ProgressBar(object):
             last message provided will be used.  To clear the current message,
             pass a null string, ''.
         """
+        cur_time = time.time()
+        cur_rate = ((cur_value - self.cur_value) /
+                    max(float(cur_time - self.cur_time), 1e-6))
+        # cur_rate += 0.9 * self.cur_rate
         # Ensure floating-point division so we can get fractions of a percent
         # for the progressbar.
+        self.cur_time = cur_time
         self.cur_value = cur_value
+        self.cur_rate = cur_rate
         progress = min(float(self.cur_value) / self.max_value, 1.)
         num_chars = int(progress * self.max_chars)
         num_left = self.max_chars - num_chars
@@ -1707,8 +1736,10 @@ class ProgressBar(object):
         # Update the message
         if mesg is not None:
             if mesg == 'file_sizes':
-                mesg = '(%s / %s)' % (sizeof_fmt(self.cur_value),
-                                      sizeof_fmt(self.max_value))
+                mesg = '(%s / %s, %s/s)' % (
+                    sizeof_fmt(self.cur_value).rjust(8),
+                    sizeof_fmt(self.max_value).rjust(8),
+                    sizeof_fmt(cur_rate).rjust(8))
             self.mesg = mesg
 
         # The \r tells the cursor to return to the beginning of the line rather
@@ -1729,8 +1760,7 @@ class ProgressBar(object):
             self.spinner_index = (self.spinner_index + 1) % self.n_spinner
 
     def update_with_increment_value(self, increment_value, mesg=None):
-        """Update progressbar with the value of the increment instead of the
-        current value of process as in update()
+        """Update progressbar with an increment.
 
         Parameters
         ----------
@@ -1743,12 +1773,19 @@ class ProgressBar(object):
             last message provided will be used.  To clear the current message,
             pass a null string, ''.
         """
-        self.cur_value += increment_value
-        self.update(self.cur_value, mesg)
+        self.update(self.cur_value + increment_value, mesg)
+
+    def __iter__(self):
+        """Iterate to auto-increment the pbar with 1."""
+        if self.iterable is None:
+            raise ValueError("Must give an iterable to be used in a loop.")
+        for obj in self.iterable:
+            yield obj
+            self.update_with_increment_value(1)
 
 
 def _get_ftp(url, temp_file_name, initial_size, file_size, verbose_bool):
-    """Safely (resume a) download to a file from FTP"""
+    """Safely (resume a) download to a file from FTP."""
     # Adapted from: https://pypi.python.org/pypi/fileDownloader.py
     # but with changes
 
@@ -1786,7 +1823,7 @@ def _get_ftp(url, temp_file_name, initial_size, file_size, verbose_bool):
 
 
 def _get_http(url, temp_file_name, initial_size, file_size, verbose_bool):
-    """Safely (resume a) download to a file from http(s)"""
+    """Safely (resume a) download to a file from http(s)."""
     # Actually do the reading
     req = urllib.request.Request(url)
     if initial_size > 0:
@@ -1810,7 +1847,9 @@ def _get_http(url, temp_file_name, initial_size, file_size, verbose_bool):
         initial_size = 0
     total_size += initial_size
     if total_size != file_size:
-        raise RuntimeError('URL could not be parsed properly')
+        raise RuntimeError('URL could not be parsed properly '
+                           '(total size %s != file size %s)'
+                           % (total_size, file_size))
     mode = 'ab' if initial_size > 0 else 'wb'
     progress = ProgressBar(total_size, initial_value=initial_size,
                            max_chars=40, spinner=True, mesg='file_sizes',
@@ -1836,7 +1875,7 @@ def _get_http(url, temp_file_name, initial_size, file_size, verbose_bool):
 
 
 def _chunk_write(chunk, local_file, progress):
-    """Write a chunk to file and update the progress bar"""
+    """Write a chunk to file and update the progress bar."""
     local_file.write(chunk)
     progress.update_with_increment_value(len(chunk))
 
@@ -1844,7 +1883,7 @@ def _chunk_write(chunk, local_file, progress):
 @verbose
 def _fetch_file(url, file_name, print_destination=True, resume=True,
                 hash_=None, timeout=10., verbose=None):
-    """Load requested file, downloading it if needed or requested
+    """Load requested file, downloading it if needed or requested.
 
     Parameters
     ----------
@@ -1863,7 +1902,8 @@ def _fetch_file(url, file_name, print_destination=True, resume=True,
     timeout : float
         The URL open timeout.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
     """
     # Adapted from NISL:
     # https://github.com/nisl/tutorial/blob/master/nisl/datasets.py
@@ -1885,7 +1925,7 @@ def _fetch_file(url, file_name, print_destination=True, resume=True,
         finally:
             u.close()
             del u
-        logger.info('Downloading data from %s (%s)\n'
+        logger.info('Downloading %s (%s)'
                     % (url, sizeof_fmt(file_size)))
 
         # Triage resume
@@ -1904,14 +1944,20 @@ def _fetch_file(url, file_name, print_destination=True, resume=True,
                                'file (%s), cannot resume download'
                                % (sizeof_fmt(initial_size),
                                   sizeof_fmt(file_size)))
-
-        scheme = urllib.parse.urlparse(url).scheme
-        fun = _get_http if scheme in ('http', 'https') else _get_ftp
-        fun(url, temp_file_name, initial_size, file_size, verbose_bool)
+        elif initial_size == file_size:
+            # This should really only happen when a hash is wrong
+            # during dev updating
+            warn('Local file appears to be complete (file_size == '
+                 'initial_size == %s)' % (file_size,))
+        else:
+            # Need to resume or start over
+            scheme = urllib.parse.urlparse(url).scheme
+            fun = _get_http if scheme in ('http', 'https') else _get_ftp
+            fun(url, temp_file_name, initial_size, file_size, verbose_bool)
 
         # check md5sum
         if hash_ is not None:
-            logger.info('Verifying download hash.')
+            logger.info('Verifying hash %s.' % (hash_,))
             md5 = md5sum(temp_file_name)
             if hash_ != md5:
                 raise RuntimeError('Hash mismatch for downloaded file %s, '
@@ -1927,10 +1973,20 @@ def _fetch_file(url, file_name, print_destination=True, resume=True,
 
 
 def sizeof_fmt(num):
-    """Turn number of bytes into human-readable str"""
+    """Turn number of bytes into human-readable str.
+
+    Parameters
+    ----------
+    num : int
+        The number of bytes.
+
+    Returns
+    -------
+    size : str
+        The size in human-readable format.
+    """
     units = ['bytes', 'kB', 'MB', 'GB', 'TB', 'PB']
     decimals = [0, 0, 1, 2, 2, 2]
-    """Human friendly file size"""
     if num > 1:
         exponent = min(int(log(num, 1024)), len(units) - 1)
         quotient = float(num) / 1024 ** exponent
@@ -1945,10 +2001,11 @@ def sizeof_fmt(num):
 
 
 class SizeMixin(object):
-    """Class to estimate MNE object sizes"""
+    """Estimate MNE object sizes."""
+
     @property
     def _size(self):
-        """Estimate of the object size"""
+        """Estimate the object size."""
         try:
             size = object_size(self.info)
         except Exception:
@@ -1961,7 +2018,7 @@ class SizeMixin(object):
         return size
 
     def __hash__(self):
-        """Hash the object
+        """Hash the object.
 
         Returns
         -------
@@ -1969,11 +2026,11 @@ class SizeMixin(object):
             The hash
         """
         from .evoked import Evoked
-        from .epochs import _BaseEpochs
-        from .io.base import _BaseRaw
+        from .epochs import BaseEpochs
+        from .io.base import BaseRaw
         if isinstance(self, Evoked):
             return object_hash(dict(info=self.info, data=self.data))
-        elif isinstance(self, (_BaseEpochs, _BaseRaw)):
+        elif isinstance(self, (BaseEpochs, BaseRaw)):
             if not self.preload:
                 raise RuntimeError('Cannot hash %s unless data are loaded'
                                    % self.__class__.__name__)
@@ -1983,7 +2040,7 @@ class SizeMixin(object):
 
 
 def _url_to_local_path(url, path):
-    """Mirror a url path in a local destination (keeping folder structure)"""
+    """Mirror a url path in a local destination (keeping folder structure)."""
     destination = urllib.parse.urlparse(url).path
     # First char should be '/', and it needs to be discarded
     if len(destination) < 2 or destination[0] != '/':
@@ -1994,7 +2051,7 @@ def _url_to_local_path(url, path):
 
 
 def _get_stim_channel(stim_channel, info, raise_error=True):
-    """Helper to determine the appropriate stim_channel
+    """Determine the appropriate stim_channel.
 
     First, 'MNE_STIM_CHANNEL', 'MNE_STIM_CHANNEL_1', 'MNE_STIM_CHANNEL_2', etc.
     are read. If these are not found, it will fall back to 'STI 014' if
@@ -2047,7 +2104,7 @@ def _get_stim_channel(stim_channel, info, raise_error=True):
 
 
 def _check_fname(fname, overwrite=False, must_exist=False):
-    """Helper to check for file existence"""
+    """Check for file existence."""
     if not isinstance(fname, string_types):
         raise TypeError('file name is not a string')
     if must_exist and not op.isfile(fname):
@@ -2056,12 +2113,12 @@ def _check_fname(fname, overwrite=False, must_exist=False):
         if not overwrite:
             raise IOError('Destination file exists. Please use option '
                           '"overwrite=True" to force overwriting.')
-        else:
+        elif overwrite != 'read':
             logger.info('Overwriting existing file.')
 
 
 def _check_subject(class_subject, input_subject, raise_error=True):
-    """Helper to get subject name from class"""
+    """Get subject name from class."""
     if input_subject is not None:
         if not isinstance(input_subject, string_types):
             raise ValueError('subject input must be a string')
@@ -2080,8 +2137,21 @@ def _check_subject(class_subject, input_subject, raise_error=True):
         return None
 
 
+def _check_preload(inst, msg):
+    """Ensure data are preloaded."""
+    from .epochs import BaseEpochs
+
+    name = 'raw'
+    if isinstance(inst, BaseEpochs):
+        name = 'epochs'
+    if not inst.preload:
+        raise RuntimeError(msg + ' requires %s data to be loaded. Use '
+                           'preload=True (or string) in the constructor or '
+                           '%s.load_data().' % (name, name))
+
+
 def _check_pandas_installed():
-    """Aux function"""
+    """Aux function."""
     try:
         import pandas as pd
         return pd
@@ -2091,7 +2161,7 @@ def _check_pandas_installed():
 
 
 def _check_pandas_index_arguments(index, defaults):
-    """ Helper function to check pandas index arguments """
+    """Check pandas index arguments."""
     if not any(isinstance(index, k) for k in (list, tuple)):
         index = [index]
     invalid_choices = [e for e in index if e not in defaults]
@@ -2102,7 +2172,7 @@ def _check_pandas_index_arguments(index, defaults):
 
 
 def _clean_names(names, remove_whitespace=False, before_dash=True):
-    """ Remove white-space on topo matching
+    """Remove white-space on topo matching.
 
     This function handles different naming
     conventions for old VS new VectorView systems (`remove_whitespace`).
@@ -2124,15 +2194,15 @@ def _clean_names(names, remove_whitespace=False, before_dash=True):
             name = name.replace(' ', '')
         if '-' in name and before_dash:
             name = name.split('-')[0]
-        if name.endswith('_virtual'):
-            name = name[:-8]
+        if name.endswith('_v'):
+            name = name[:-2]
         cleaned.append(name)
 
     return cleaned
 
 
 def _check_type_picks(picks):
-    """helper to guarantee type integrity of picks"""
+    """Guarantee type integrity of picks."""
     err_msg = 'picks must be None, a list or an array of integers'
     if picks is None:
         pass
@@ -2150,7 +2220,7 @@ def _check_type_picks(picks):
 
 @nottest
 def run_tests_if_main(measure_mem=False):
-    """Run tests in a given file if it is run as a script"""
+    """Run tests in a given file if it is run as a script."""
     local_vars = inspect.currentframe().f_back.f_locals
     if not local_vars.get('__name__', '') == '__main__':
         return
@@ -2191,7 +2261,7 @@ def run_tests_if_main(measure_mem=False):
                 elapsed = int(round(time.time() - t1))
                 if elapsed >= max_elapsed:
                     max_elapsed, elapsed_name = elapsed, name
-                sys.stdout.write('time: %s sec%s\n' % (elapsed, mem))
+                sys.stdout.write('time: %0.3f sec%s\n' % (elapsed, mem))
                 sys.stdout.flush()
             except Exception as err:
                 if 'skiptest' in err.__class__.__name__.lower():
@@ -2200,19 +2270,22 @@ def run_tests_if_main(measure_mem=False):
                 else:
                     raise
     elapsed = int(round(time.time() - t0))
-    sys.stdout.write('Total: %s tests\n• %s sec (%s sec for %s)\n• Peak memory'
-                     ' %s MB (%s)\n' % (count, elapsed, max_elapsed,
-                                        elapsed_name, peak_mem, peak_name))
+    sys.stdout.write('Total: %s tests\n• %0.3f sec (%0.3f sec for %s)\n• '
+                     'Peak memory %s MB (%s)\n'
+                     % (count, elapsed, max_elapsed, elapsed_name, peak_mem,
+                        peak_name))
 
 
 class ArgvSetter(object):
-    """Temporarily set sys.argv"""
-    def __init__(self, args=(), disable_stdout=True, disable_stderr=True):
+    """Temporarily set sys.argv."""
+
+    def __init__(self, args=(), disable_stdout=True,
+                 disable_stderr=True):  # noqa: D102
         self.argv = list(('python',) + args)
         self.stdout = StringIO() if disable_stdout else sys.stdout
         self.stderr = StringIO() if disable_stderr else sys.stderr
 
-    def __enter__(self):
+    def __enter__(self):  # noqa: D105
         self.orig_argv = sys.argv
         sys.argv = self.argv
         self.orig_stdout = sys.stdout
@@ -2221,14 +2294,26 @@ class ArgvSetter(object):
         sys.stderr = self.stderr
         return self
 
-    def __exit__(self, *args):
+    def __exit__(self, *args):  # noqa: D105
         sys.argv = self.orig_argv
         sys.stdout = self.orig_stdout
         sys.stderr = self.orig_stderr
 
 
+class SilenceStdout(object):
+    """Silence stdout."""
+
+    def __enter__(self):  # noqa: D105
+        self.stdout = sys.stdout
+        sys.stdout = StringIO()
+        return self
+
+    def __exit__(self, *args):  # noqa: D105
+        sys.stdout = self.stdout
+
+
 def md5sum(fname, block_size=1048576):  # 2 ** 20
-    """Calculate the md5sum for a file
+    """Calculate the md5sum for a file.
 
     Parameters
     ----------
@@ -2253,7 +2338,7 @@ def md5sum(fname, block_size=1048576):  # 2 ** 20
 
 
 def create_slices(start, stop, step=None, length=1):
-    """ Generate slices of time indexes
+    """Generate slices of time indexes.
 
     Parameters
     ----------
@@ -2272,7 +2357,6 @@ def create_slices(start, stop, step=None, length=1):
     slices : list
         List of slice objects.
     """
-
     # default parameters
     if step is None:
         step = length
@@ -2284,7 +2368,7 @@ def create_slices(start, stop, step=None, length=1):
 
 
 def _time_mask(times, tmin=None, tmax=None, sfreq=None, raise_error=True):
-    """Helper to safely find sample boundaries"""
+    """Safely find sample boundaries."""
     orig_tmin = tmin
     orig_tmax = tmax
     tmin = -np.inf if tmin is None else tmin
@@ -2310,17 +2394,8 @@ def _time_mask(times, tmin=None, tmax=None, sfreq=None, raise_error=True):
     return mask
 
 
-def _get_fast_dot():
-    """"Helper to get fast dot"""
-    try:
-        from sklearn.utils.extmath import fast_dot
-    except ImportError:
-        fast_dot = np.dot
-    return fast_dot
-
-
 def random_permutation(n_samples, random_state=None):
-    """Helper to emulate the randperm matlab function.
+    """Emulate the randperm matlab function.
 
     It returns a vector containing a random permutation of the
     integers between 0 and n_samples-1. It returns the same random numbers
@@ -2351,17 +2426,14 @@ def random_permutation(n_samples, random_state=None):
     """
     rng = check_random_state(random_state)
     idx = rng.rand(n_samples)
-
     randperm = np.argsort(idx)
-
     return randperm
 
 
 def compute_corr(x, y):
-    """Compute pearson correlations between a vector and a matrix"""
+    """Compute pearson correlations between a vector and a matrix."""
     if len(x) == 0 or len(y) == 0:
         raise ValueError('x or y has zero length')
-    fast_dot = _get_fast_dot()
     X = np.array(x, float)
     Y = np.array(y, float)
     X -= X.mean(0)
@@ -2370,11 +2442,11 @@ def compute_corr(x, y):
     # if covariance matrix is fully expanded, Y needs a
     # transpose / broadcasting else Y is correct
     y_sd = Y.std(0, ddof=1)[:, None if X.shape == Y.shape else Ellipsis]
-    return (fast_dot(X.T, Y) / float(len(X) - 1)) / (x_sd * y_sd)
+    return (np.dot(X.T, Y) / float(len(X) - 1)) / (x_sd * y_sd)
 
 
 def grand_average(all_inst, interpolate_bads=True, drop_bads=True):
-    """Make grand average of a list evoked or AverageTFR data
+    """Make grand average of a list evoked or AverageTFR data.
 
     For evoked data, the function interpolates bad channels based on
     `interpolate_bads` parameter. If `interpolate_bads` is True, the grand
@@ -2446,7 +2518,7 @@ def grand_average(all_inst, interpolate_bads=True, drop_bads=True):
 
 
 def _get_root_dir():
-    """Helper to get as close to the repo root as possible"""
+    """Get as close to the repo root as possible."""
     root_dir = op.abspath(op.dirname(__file__))
     up_dir = op.join(root_dir, '..')
     if op.isfile(op.join(up_dir, 'setup.py')) and all(
@@ -2456,7 +2528,7 @@ def _get_root_dir():
 
 
 def sys_info(fid=None, show_paths=False):
-    """Print the system information for debugging
+    """Print the system information for debugging.
 
     This function is useful for printing system information
     to help triage bugs.
@@ -2487,18 +2559,27 @@ def sys_info(fid=None, show_paths=False):
 
         sklearn:       0.18.dev0
         nibabel:       2.1.0dev
-        nitime:        0.6
         mayavi:        4.3.1
-        nose:          1.3.7
-        pandas:        0.17.1+25.g547750a
         pycuda:        2015.1.3
         skcuda:        0.5.2
+        pandas:        0.17.1+25.g547750a
 
-    """  # noqa
+    """  # noqa: E501
     ljust = 15
     out = 'Platform:'.ljust(ljust) + platform.platform() + '\n'
     out += 'Python:'.ljust(ljust) + str(sys.version).replace('\n', ' ') + '\n'
-    out += 'Executable:'.ljust(ljust) + sys.executable + '\n\n'
+    out += 'Executable:'.ljust(ljust) + sys.executable + '\n'
+    out += 'CPU:'.ljust(ljust) + ('%s: %s cores\n' %
+                                  (platform.processor(),
+                                   multiprocessing.cpu_count()))
+    out += 'Memory:'.ljust(ljust)
+    try:
+        import psutil
+    except ImportError:
+        out += 'Unavailable (requires "psutil" package)'
+    else:
+        out += '%0.1f GB\n' % (psutil.virtual_memory().total / float(2 ** 30),)
+    out += '\n'
     old_stdout = sys.stdout
     capture = StringIO()
     try:
@@ -2516,8 +2597,8 @@ def sys_info(fid=None, show_paths=False):
     libs = ', '.join(libs)
     version_texts = dict(pycuda='VERSION_TEXT')
     for mod_name in ('mne', 'numpy', 'scipy', 'matplotlib', '',
-                     'sklearn', 'nibabel', 'nitime', 'mayavi', 'nose',
-                     'pandas', 'pycuda', 'skcuda'):
+                     'sklearn', 'nibabel', 'mayavi', 'pycuda', 'skcuda',
+                     'pandas'):
         if mod_name == '':
             out += '\n'
             continue
@@ -2536,13 +2617,61 @@ def sys_info(fid=None, show_paths=False):
 
 
 class ETSContext(object):
-    """Add more meaningful message to errors generated by ETS Toolkit"""
-    def __enter__(self):
+    """Add more meaningful message to errors generated by ETS Toolkit."""
+
+    def __enter__(self):  # noqa: D105
         pass
 
-    def __exit__(self, type, value, traceback):
+    def __exit__(self, type, value, traceback):  # noqa: D105
         if isinstance(value, SystemExit) and value.code.\
                 startswith("This program needs access to the screen"):
             value.code += ("\nThis can probably be solved by setting "
                            "ETS_TOOLKIT=qt4. On bash, type\n\n    $ export "
                            "ETS_TOOLKIT=qt4\n\nand run the command again.")
+
+
+def open_docs(kind=None, version=None):
+    """Launch a new web browser tab with the MNE documentation.
+
+    Parameters
+    ----------
+    kind : str | None
+        Can be "api" (default), "tutorials", or "examples".
+        The default can be changed by setting the configuration value
+        MNE_DOCS_KIND.
+    version : str | None
+        Can be "stable" (default) or "dev".
+        The default can be changed by setting the configuration value
+        MNE_DOCS_VERSION.
+    """
+    if kind is None:
+        kind = get_config('MNE_DOCS_KIND', 'api')
+    help_dict = dict(api='python_reference.html', tutorials='tutorials.html',
+                     examples='auto_examples/index.html')
+    if kind not in help_dict:
+        raise ValueError('kind must be one of %s, got %s'
+                         % (sorted(help_dict.keys()), kind))
+    kind = help_dict[kind]
+    if version is None:
+        version = get_config('MNE_DOCS_VERSION', 'stable')
+    versions = ('stable', 'dev')
+    if version not in versions:
+        raise ValueError('version must be one of %s, got %s'
+                         % (version, versions))
+    webbrowser.open_new_tab('https://martinos.org/mne/%s/%s' % (version, kind))
+
+
+def _scale_dep(want, got, name_want, name_got):
+    if got is not None:
+        warn('%s is deprecated and will be removed in 0.16, use %s instead'
+             % (name_got, name_want), DeprecationWarning)
+        want = got
+    return want
+
+
+def _freqs_dep(freqs, frequencies, prefix=''):
+    if frequencies is not None:
+        freqs = frequencies
+        warn('%sfrequencies is deprecated and will be removed in 0.16, use '
+             '%sfreqs instead.' % (prefix, prefix), DeprecationWarning)
+    return freqs
diff --git a/mne/viz/_3d.py b/mne/viz/_3d.py
index 42cfd43..934a199 100644
--- a/mne/viz/_3d.py
+++ b/mne/viz/_3d.py
@@ -1,5 +1,5 @@
-"""Functions to make 3D plots with M/EEG data
-"""
+# -*- coding: utf-8 -*-
+"""Functions to make 3D plots with M/EEG data."""
 from __future__ import print_function
 
 # Authors: Alexandre Gramfort <alexandre.gramfort at telecom-paristech.fr>
@@ -16,27 +16,166 @@ from distutils.version import LooseVersion
 from itertools import cycle
 import os.path as op
 import warnings
+from functools import partial
 
 import numpy as np
 from scipy import linalg
 
-from ..externals.six import string_types, advance_iterator
+from ..defaults import DEFAULTS
+from ..externals.six import BytesIO, string_types, advance_iterator
 from ..io import _loc_to_coil_trans, Info
 from ..io.pick import pick_types
 from ..io.constants import FIFF
-from ..surface import (get_head_surf, get_meg_helmet_surf, read_surface,
-                       transform_surface_to)
+from ..io.meas_info import read_fiducials
+from ..source_space import SourceSpaces, _create_surf_spacing, _check_spacing
+
+from ..surface import (_get_head_surface, get_meg_helmet_surf, read_surface,
+                       transform_surface_to, _project_onto_surface,
+                       complete_surface_info, mesh_edges,
+                       _complete_sphere_surf)
 from ..transforms import (read_trans, _find_trans, apply_trans,
                           combine_transforms, _get_trans, _ensure_trans,
                           invert_transform, Transform)
-from ..utils import get_subjects_dir, logger, _check_subject, verbose, warn
-from .utils import mne_analyze_colormap, _prepare_trellis, COLORS, plt_show
-from ..externals.six import BytesIO
+from ..utils import (get_subjects_dir, logger, _check_subject, verbose, warn,
+                     _import_mlab, SilenceStdout, has_nibabel, check_version,
+                     _ensure_int, deprecated)
+from .utils import (mne_analyze_colormap, _prepare_trellis, COLORS, plt_show,
+                    tight_layout, figure_nobar)
+from ..bem import ConductorModel, _bem_find_surface, _surf_dict, _surf_name
+
+
+FIDUCIAL_ORDER = (FIFF.FIFFV_POINT_LPA, FIFF.FIFFV_POINT_NASION,
+                  FIFF.FIFFV_POINT_RPA)
+
+
+def _fiducial_coords(points, coord_frame=None):
+    """Generate 3x3 array of fiducial coordinates."""
+    if coord_frame is not None:
+        points = [p for p in points if p['coord_frame'] == coord_frame]
+    points_ = dict((p['ident'], p) for p in points if
+                   p['kind'] == FIFF.FIFFV_POINT_CARDINAL)
+    if points_:
+        return np.array([points_[i]['r'] for i in FIDUCIAL_ORDER])
+    else:
+        # XXX eventually this should probably live in montage.py
+        if coord_frame is None or coord_frame == FIFF.FIFFV_COORD_HEAD:
+            # Try converting CTF HPI coils to fiducials
+            out = np.empty((3, 3))
+            out.fill(np.nan)
+            for p in points:
+                if p['kind'] == FIFF.FIFFV_POINT_HPI:
+                    if np.isclose(p['r'][1:], 0, atol=1e-6).all():
+                        out[0 if p['r'][0] < 0 else 2] = p['r']
+                    elif np.isclose(p['r'][::2], 0, atol=1e-6).all():
+                        out[1] = p['r']
+            if np.isfinite(out).all():
+                return out
+        return np.array([])
+
+
+def plot_head_positions(pos, mode='traces', cmap='viridis', direction='z',
+                        show=True):
+    """Plot head positions.
+
+    Parameters
+    ----------
+    pos : ndarray, shape (n_pos, 10)
+        The head position data.
+    mode : str
+        Can be 'traces' (default) to show position and quaternion traces,
+        or 'field' to show the position as a vector field over time.
+        The 'field' mode requires matplotlib 1.4+.
+    cmap : matplotlib Colormap
+        Colormap to use for the trace plot, default is "viridis".
+    direction : str
+        Can be any combination of "x", "y", or "z" (default: "z") to show
+        directional axes in "field" mode.
+    show : bool
+        Show figure if True. Defaults to True.
+
+    Returns
+    -------
+    fig : Instance of matplotlib.figure.Figure
+        The figure.
+    """
+    from ..chpi import head_pos_to_trans_rot_t
+    import matplotlib.pyplot as plt
+    from matplotlib.colors import Normalize
+    from mpl_toolkits.mplot3d.art3d import Line3DCollection
+    from mpl_toolkits.mplot3d import axes3d  # noqa: F401, analysis:ignore
+    if not isinstance(mode, string_types) or mode not in ('traces', 'field'):
+        raise ValueError('mode must be "traces" or "field", got %s' % (mode,))
+    trans, rot, t = head_pos_to_trans_rot_t(pos)  # also ensures pos is okay
+    # trans, rot, and t are for dev_head_t, but what we really want
+    # is head_dev_t (i.e., where the head origin is in device coords)
+    use_trans = np.einsum('ijk,ik->ij', rot[:, :3, :3].transpose([0, 2, 1]),
+                          -trans) * 1000
+    use_rot = rot.transpose([0, 2, 1])
+    use_quats = -pos[:, 1:4]  # inverse (like doing rot.T)
+    if cmap == 'viridis' and not check_version('matplotlib', '1.5'):
+        warn('viridis is unavailable on matplotlib < 1.4, using "YlGnBu_r"')
+        cmap = 'YlGnBu_r'
+    if mode == 'traces':
+        fig, axes = plt.subplots(3, 2, sharex=True)
+        labels = ['xyz', ('$q_1$', '$q_2$', '$q_3$')]
+        for ii, (quat, coord) in enumerate(zip(use_quats.T, use_trans.T)):
+            axes[ii, 0].plot(t, coord, 'k')
+            axes[ii, 0].set(ylabel=labels[0][ii], xlim=t[[0, -1]])
+            axes[ii, 1].plot(t, quat, 'k')
+            axes[ii, 1].set(ylabel=labels[1][ii], xlim=t[[0, -1]])
+        for ii, title in enumerate(('Position (mm)', 'Rotation (quat)')):
+            axes[0, ii].set(title=title)
+            axes[-1, ii].set(xlabel='Time (s)')
+    else:  # mode == 'field':
+        if not check_version('matplotlib', '1.4'):
+            raise RuntimeError('The "field" mode requires matplotlib version '
+                               '1.4+')
+        fig, ax = plt.subplots(1, subplot_kw=dict(projection='3d'))
+        # First plot the trajectory as a colormap:
+        # http://matplotlib.org/examples/pylab_examples/multicolored_line.html
+        pts = use_trans[:, np.newaxis]
+        segments = np.concatenate([pts[:-1], pts[1:]], axis=1)
+        norm = Normalize(t[0], t[-2])
+        lc = Line3DCollection(segments, cmap=cmap, norm=norm)
+        lc.set_array(t[:-1])
+        ax.add_collection(lc)
+        # now plot the head directions as a quiver
+        dir_idx = dict(x=0, y=1, z=2)
+        kwargs = _pivot_kwargs()
+        for d, length in zip(direction, [1., 0.5, 0.25]):
+            use_dir = use_rot[:, :, dir_idx[d]]
+            # draws stems, then heads
+            array = np.concatenate((t, np.repeat(t, 2)))
+            ax.quiver(use_trans[:, 0], use_trans[:, 1], use_trans[:, 2],
+                      use_dir[:, 0], use_dir[:, 1], use_dir[:, 2], norm=norm,
+                      cmap=cmap, array=array, length=length, **kwargs)
+        mins = use_trans.min(0)
+        maxs = use_trans.max(0)
+        scale = (maxs - mins).max() / 2.
+        xlim, ylim, zlim = (maxs + mins)[:, np.newaxis] / 2. + [-scale, scale]
+        ax.set(xlabel='x', ylabel='y', zlabel='z',
+               xlim=xlim, ylim=ylim, zlim=zlim, aspect='equal')
+        ax.view_init(30, 45)
+    tight_layout(fig=fig)
+    plt_show(show)
+    return fig
+
+
+def _pivot_kwargs():
+    """Get kwargs for quiver."""
+    kwargs = dict()
+    if check_version('matplotlib', '1.5'):
+        kwargs['pivot'] = 'tail'
+    else:
+        import matplotlib
+        warn('pivot cannot be set in matplotlib %s (need version 1.5+), '
+             'locations are approximate' % (matplotlib.__version__,))
+    return kwargs
 
 
 def plot_evoked_field(evoked, surf_maps, time=None, time_label='t = %0.0f ms',
                       n_jobs=1):
-    """Plot MEG/EEG fields on head surface and helmet in 3D
+    """Plot MEG/EEG fields on head surface and helmet in 3D.
 
     Parameters
     ----------
@@ -70,7 +209,7 @@ def plot_evoked_field(evoked, surf_maps, time=None, time_label='t = %0.0f ms',
     types = [sm['kind'] for sm in surf_maps]
 
     # Plot them
-    from mayavi import mlab
+    mlab = _import_mlab()
     alphas = [1.0, 0.5]
     colors = [(0.6, 0.6, 0.6), (1.0, 1.0, 1.0)]
     colormap = mne_analyze_colormap(format='mayavi')
@@ -79,6 +218,7 @@ def plot_evoked_field(evoked, surf_maps, time=None, time_label='t = %0.0f ms',
                                      np.tile([255., 0., 0., 255.], (127, 1))])
 
     fig = mlab.figure(bgcolor=(0.0, 0.0, 0.0), size=(600, 600))
+    _toggle_mlab_render(fig, False)
 
     for ii, this_map in enumerate(surf_maps):
         surf = this_map['surf']
@@ -107,50 +247,59 @@ def plot_evoked_field(evoked, surf_maps, time=None, time_label='t = %0.0f ms',
 
         data = np.dot(map_data, evoked.data[pick, time_idx])
 
-        x, y, z = surf['rr'].T
-        nn = surf['nn']
-        # make absolutely sure these are normalized for Mayavi
-        nn = nn / np.sum(nn * nn, axis=1)[:, np.newaxis]
-
         # Make a solid surface
         vlim = np.max(np.abs(data))
         alpha = alphas[ii]
+        mesh = _create_mesh_surf(surf, fig)
         with warnings.catch_warnings(record=True):  # traits
-            mesh = mlab.pipeline.triangular_mesh_source(x, y, z, surf['tris'])
-        mesh.data.point_data.normals = nn
-        mesh.data.cell_data.normals = None
-        mlab.pipeline.surface(mesh, color=colors[ii], opacity=alpha)
+            surface = mlab.pipeline.surface(mesh, color=colors[ii],
+                                            opacity=alpha, figure=fig)
+        surface.actor.property.backface_culling = True
 
         # Now show our field pattern
+        mesh = _create_mesh_surf(surf, fig, scalars=data)
         with warnings.catch_warnings(record=True):  # traits
-            mesh = mlab.pipeline.triangular_mesh_source(x, y, z, surf['tris'],
-                                                        scalars=data)
-        mesh.data.point_data.normals = nn
-        mesh.data.cell_data.normals = None
-        with warnings.catch_warnings(record=True):  # traits
-            fsurf = mlab.pipeline.surface(mesh, vmin=-vlim, vmax=vlim)
+            fsurf = mlab.pipeline.surface(mesh, vmin=-vlim, vmax=vlim,
+                                          figure=fig)
         fsurf.module_manager.scalar_lut_manager.lut.table = colormap
+        fsurf.actor.property.backface_culling = True
 
         # And the field lines on top
+        mesh = _create_mesh_surf(surf, fig, scalars=data)
         with warnings.catch_warnings(record=True):  # traits
-            mesh = mlab.pipeline.triangular_mesh_source(x, y, z, surf['tris'],
-                                                        scalars=data)
-        mesh.data.point_data.normals = nn
-        mesh.data.cell_data.normals = None
-        with warnings.catch_warnings(record=True):  # traits
-            cont = mlab.pipeline.contour_surface(mesh, contours=21,
-                                                 line_width=1.0,
-                                                 vmin=-vlim, vmax=vlim,
-                                                 opacity=alpha)
+            cont = mlab.pipeline.contour_surface(
+                mesh, contours=21, line_width=1.0, vmin=-vlim, vmax=vlim,
+                opacity=alpha, figure=fig)
         cont.module_manager.scalar_lut_manager.lut.table = colormap_lines
 
     if '%' in time_label:
         time_label %= (1e3 * evoked.times[time_idx])
-    mlab.text(0.01, 0.01, time_label, width=0.4)
-    mlab.view(10, 60)
+    with warnings.catch_warnings(record=True):  # traits
+        mlab.text(0.01, 0.01, time_label, width=0.4, figure=fig)
+        with SilenceStdout():  # setting roll
+            mlab.view(10, 60, figure=fig)
+    _toggle_mlab_render(fig, True)
     return fig
 
 
+def _create_mesh_surf(surf, fig=None, scalars=None):
+    """Create Mayavi mesh from MNE surf."""
+    mlab = _import_mlab()
+    nn = surf['nn'].copy()
+    # make absolutely sure these are normalized for Mayavi
+    norm = np.sum(nn * nn, axis=1)
+    mask = norm > 0
+    nn[mask] /= norm[mask][:, np.newaxis]
+    x, y, z = surf['rr'].T
+    with warnings.catch_warnings(record=True):  # traits
+        mesh = mlab.pipeline.triangular_mesh_source(
+            x, y, z, surf['tris'], scalars=scalars, figure=fig)
+    mesh.data.point_data.normals = nn
+    mesh.data.cell_data.normals = None
+    mesh.update()
+    return mesh
+
+
 def _plot_mri_contours(mri_fname, surf_fnames, orientation='coronal',
                        slices=None, show=True, img_output=False):
     """Plot BEM contours on anatomical slices.
@@ -190,7 +339,10 @@ def _plot_mri_contours(mri_fname, surf_fnames, orientation='coronal',
     # Load the T1 data
     nim = nib.load(mri_fname)
     data = nim.get_data()
-    affine = nim.get_affine()
+    try:
+        affine = nim.affine
+    except AttributeError:  # old nibabel
+        affine = nim.get_affine()
 
     n_sag, n_axi, n_cor = data.shape
     orientation_name2axis = dict(sagittal=0, axial=1, coronal=2)
@@ -208,8 +360,7 @@ def _plot_mri_contours(mri_fname, surf_fnames, orientation='coronal',
     trans[:3, -1] = [n_sag // 2, n_axi // 2, n_cor // 2]
 
     for surf_fname in surf_fnames:
-        surf = dict()
-        surf['rr'], surf['tris'] = read_surface(surf_fname)
+        surf = read_surface(surf_fname, return_dict=True)[-1]
         # move back surface to MRI coordinate system
         surf['rr'] = nib.affines.apply_affine(trans, surf['rr'])
         surfs.append(surf)
@@ -265,12 +416,16 @@ def _plot_mri_contours(mri_fname, surf_fnames, orientation='coronal',
     return fig if img_output is None else outs
 
 
+ at deprecated('this function will be removed in version 0.16. '
+            'Use plot_alignment instead')
 @verbose
 def plot_trans(info, trans='auto', subject=None, subjects_dir=None,
-               ch_type=None, source=('bem', 'head'), coord_frame='head',
-               meg_sensors=False, eeg_sensors=True, dig=False, ref_meg=False,
-               verbose=None):
-    """Plot MEG/EEG head surface and helmet in 3D.
+               source=('bem', 'head', 'outer_skin'),
+               coord_frame='head', meg_sensors=('helmet', 'sensors'),
+               eeg_sensors='original', dig=False, ref_meg=False,
+               ecog_sensors=True, head=None, brain=None, skull=False,
+               src=None, mri_fiducials=False, verbose=None):
+    """Plot head, sensor, and source space alignment in 3D.
 
     Parameters
     ----------
@@ -278,35 +433,71 @@ def plot_trans(info, trans='auto', subject=None, subjects_dir=None,
         The measurement info.
     trans : str | 'auto' | dict | None
         The full path to the head<->MRI transform ``*-trans.fif`` file
-        produced during coregistration. If trans is None, no head
-        surface will be shown.
+        produced during coregistration. If trans is None, an identity matrix
+        is assumed.
     subject : str | None
         The subject name corresponding to FreeSurfer environment
-        variable SUBJECT.
+        variable SUBJECT. Can be omitted if ``src`` is provided.
     subjects_dir : str
         The path to the freesurfer subjects reconstructions.
         It corresponds to Freesurfer environment variable SUBJECTS_DIR.
-    ch_type : None | 'eeg' | 'meg'
-        If None, both the MEG helmet and EEG electrodes will be shown.
-        If 'meg', only the MEG helmet will be shown. If 'eeg', only the
-        EEG electrodes will be shown.
-    source : str
-        Type to load. Common choices would be `'bem'` or `'head'`. We first
-        try loading `'$SUBJECTS_DIR/$SUBJECT/bem/$SUBJECT-$SOURCE.fif'`, and
-        then look for `'$SUBJECT*$SOURCE.fif'` in the same directory. Defaults
-        to 'bem'. Note. For single layer bems it is recommended to use 'head'.
+    source : str | list
+        Type to load. Common choices would be `'bem'`, `'head'` or
+        `'outer_skin'`. If list, the sources are looked up in the given order
+        and first found surface is used. We first try loading
+        `'$SUBJECTS_DIR/$SUBJECT/bem/$SUBJECT-$SOURCE.fif'`, and then look for
+        `'$SUBJECT*$SOURCE.fif'` in the same directory. For `'outer_skin'`,
+        the subjects bem and bem/flash folders are searched. Defaults to 'bem'.
+        Note. For single layer bems it is recommended to use 'head'.
     coord_frame : str
         Coordinate frame to use, 'head', 'meg', or 'mri'.
-    meg_sensors : bool
-        If True, plot MEG sensors as points in addition to showing the helmet.
-    eeg_sensors : bool
-        If True, plot EEG sensors as points.
-    dig : bool
-        If True, plot the digitization points.
+    meg_sensors : bool | str | list
+        Can be "helmet" (equivalent to False) or "sensors" to show the MEG
+        helmet or sensors, respectively, or a combination of the two like
+        ``['helmet', 'sensors']`` (equivalent to True, default) or ``[]``.
+    eeg_sensors : bool | str | list
+        Can be "original" (default; equivalent to True) or "projected" to
+        show EEG sensors in their digitized locations or projected onto the
+        scalp, or a list of these options including ``[]`` (equivalent of
+        False).
+    dig : bool | 'fiducials'
+        If True, plot the digitization points; 'fiducials' to plot fiducial
+        points only.
     ref_meg : bool
         If True (default False), include reference MEG sensors.
+    ecog_sensors : bool
+        If True (default), show ECoG sensors.
+    head : bool | None
+        If True, show head surface. Can also be None, which will show the
+        head surface for MEG and EEG, but hide it if ECoG sensors are
+        present.
+    brain : bool | str | None
+        If True, show the brain surfaces. Can also be a str for
+        surface type (e.g., 'pial', same as True), or None (True for ECoG,
+        False otherwise).
+    skull : bool | str | list of str | list of dict
+        Whether to plot skull surface. If string, common choices would be
+        'inner_skull', or 'outer_skull'. Can also be a list to plot
+        multiple skull surfaces. If a list of dicts, each dict must
+        contain the complete surface info (such as you get from
+        :func:`mne.make_bem_model`). True is an alias of 'outer_skull'.
+        The subjects bem and bem/flash folders are searched for the 'surf'
+        files. Defaults to False.
+    src : instance of SourceSpaces | None
+        If not None, also plot the source space points.
+
+        .. versionadded:: 0.14
+
+    mri_fiducials : bool | str
+        Plot MRI fiducials (default False). If ``True``, look for a file with
+        the canonical name (``bem/{subject}-fiducials.fif``). If ``str`` it
+        should provide the full path to the fiducials file.
+
+        .. versionadded:: 0.14
+
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -314,156 +505,867 @@ def plot_trans(info, trans='auto', subject=None, subjects_dir=None,
         The mayavi figure.
     """
     from ..forward import _create_meg_coils
+    mlab = _import_mlab()
+    if meg_sensors is False:  # old behavior
+        meg_sensors = 'helmet'
+    elif meg_sensors is True:
+        meg_sensors = ['helmet', 'sensors']
+    if eeg_sensors is False:
+        eeg_sensors = []
+    elif eeg_sensors is True:
+        eeg_sensors = 'original'
+    if isinstance(eeg_sensors, string_types):
+        eeg_sensors = [eeg_sensors]
+    if isinstance(meg_sensors, string_types):
+        meg_sensors = [meg_sensors]
+    for kind, var in zip(('eeg', 'meg'), (eeg_sensors, meg_sensors)):
+        if not isinstance(var, (list, tuple)) or \
+                not all(isinstance(x, string_types) for x in var):
+            raise TypeError('%s_sensors must be list or tuple of str, got %s'
+                            % (type(var),))
+    if not all(x in ('helmet', 'sensors') for x in meg_sensors):
+        raise ValueError('meg_sensors must only contain "helmet" and "points",'
+                         ' got %s' % (meg_sensors,))
+    if not all(x in ('original', 'projected') for x in eeg_sensors):
+        raise ValueError('eeg_sensors must only contain "original" and '
+                         '"projected", got %s' % (eeg_sensors,))
+
     if not isinstance(info, Info):
         raise TypeError('info must be an instance of Info, got %s'
                         % type(info))
-    if coord_frame not in ['head', 'meg', 'mri']:
-        raise ValueError('coord_frame must be "head" or "meg"')
-    if ch_type not in [None, 'eeg', 'meg']:
-        raise ValueError('Argument ch_type must be None | eeg | meg. Got %s.'
-                         % ch_type)
+    valid_coords = ['head', 'meg', 'mri']
+    if coord_frame not in valid_coords:
+        raise ValueError('coord_frame must be one of %s' % (valid_coords,))
+    if src is not None:
+        if not isinstance(src, SourceSpaces):
+            raise TypeError('src must be None or SourceSpaces, got %s'
+                            % (type(src),))
+        src_subject = src[0].get('subject_his_id', None)
+        subject = src_subject if subject is None else subject
+        if src_subject is not None and subject != src_subject:
+            raise ValueError('subject ("%s") did not match the subject name '
+                             ' in src ("%s")' % (subject, src_subject))
+        src_rr = np.concatenate([s['rr'][s['inuse'].astype(bool)]
+                                 for s in src])
+        src_nn = np.concatenate([s['nn'][s['inuse'].astype(bool)]
+                                 for s in src])
+    else:
+        src_rr = src_nn = np.empty((0, 3))
+
+    meg_picks = pick_types(info, meg=True, ref_meg=ref_meg)
+    eeg_picks = pick_types(info, meg=False, eeg=True, ref_meg=False)
+    ecog_picks = pick_types(info, meg=False, ecog=True, ref_meg=False)
 
-    show_head = (subject is not None)
+    if head is None:
+        head = (len(ecog_picks) == 0 and subject is not None)
+    if head and subject is None:
+        raise ValueError('If head is True, subject must be provided')
     if isinstance(trans, string_types):
         if trans == 'auto':
             # let's try to do this in MRI coordinates so they're easy to plot
             subjects_dir = get_subjects_dir(subjects_dir, raise_error=True)
             trans = _find_trans(subject, subjects_dir)
-        trans = read_trans(trans)
+        trans = read_trans(trans, return_all=True)
+        exp = None
+        for trans in trans:  # we got at least 1
+            try:
+                trans = _ensure_trans(trans, 'head', 'mri')
+            except Exception as exp:
+                pass
+            else:
+                break
+        else:
+            raise exp
     elif trans is None:
-        trans = Transform('head', 'mri', np.eye(4))
-        show_head = False
+        trans = Transform('head', 'mri')
     elif not isinstance(trans, dict):
         raise TypeError('trans must be str, dict, or None')
     head_mri_t = _ensure_trans(trans, 'head', 'mri')
+    dev_head_t = info['dev_head_t']
     del trans
 
+    # Figure out our transformations
+    if coord_frame == 'meg':
+        head_trans = invert_transform(dev_head_t)
+        meg_trans = Transform('meg', 'meg')
+        mri_trans = invert_transform(combine_transforms(
+            dev_head_t, head_mri_t, 'meg', 'mri'))
+    elif coord_frame == 'mri':
+        head_trans = head_mri_t
+        meg_trans = combine_transforms(dev_head_t, head_mri_t, 'meg', 'mri')
+        mri_trans = Transform('mri', 'mri')
+    else:  # coord_frame == 'head'
+        head_trans = Transform('head', 'head')
+        meg_trans = info['dev_head_t']
+        mri_trans = invert_transform(head_mri_t)
+
     # both the head and helmet will be in MRI coordinates after this
-    meg_picks = pick_types(info, meg=True, ref_meg=ref_meg)
     surfs = dict()
-    if show_head:
+    if head:
         subjects_dir = get_subjects_dir(subjects_dir, raise_error=True)
-        surfs['head'] = get_head_surf(subject, source=source,
-                                      subjects_dir=subjects_dir)
-    if (ch_type is None and len(meg_picks) > 0) or ch_type == 'meg':
+        head_surf = _get_head_surface(subject, source=source,
+                                      subjects_dir=subjects_dir,
+                                      raise_error=False)
+        if head_surf is None:
+            if isinstance(source, string_types):
+                source = [source]
+            for this_surf in source:
+                if not this_surf.endswith('outer_skin'):
+                    continue
+                surf_fname = op.join(subjects_dir, subject, 'bem', 'flash',
+                                     '%s.surf' % this_surf)
+                if not op.exists(surf_fname):
+                    surf_fname = op.join(subjects_dir, subject, 'bem',
+                                         '%s.surf' % this_surf)
+                    if not op.exists(surf_fname):
+                        continue
+                logger.info('Using %s for head surface.' % this_surf)
+                rr, tris = read_surface(surf_fname)
+                head_surf = dict(rr=rr / 1000., tris=tris, ntri=len(tris),
+                                 np=len(rr), coord_frame=FIFF.FIFFV_COORD_MRI)
+                complete_surface_info(head_surf, copy=False, verbose=False)
+                break
+        if head_surf is None:
+            raise IOError('No head surface found for subject %s.' % subject)
+        surfs['head'] = head_surf
+
+    if mri_fiducials:
+        if mri_fiducials is True:
+            subjects_dir = get_subjects_dir(subjects_dir, raise_error=True)
+            if subject is None:
+                raise ValueError("Subject needs to be specified to "
+                                 "automatically find the fiducials file.")
+            mri_fiducials = op.join(subjects_dir, subject, 'bem',
+                                    subject + '-fiducials.fif')
+        if isinstance(mri_fiducials, string_types):
+            mri_fiducials, cf = read_fiducials(mri_fiducials)
+            if cf != FIFF.FIFFV_COORD_MRI:
+                raise ValueError("Fiducials are not in MRI space")
+        fid_loc = _fiducial_coords(mri_fiducials, FIFF.FIFFV_COORD_MRI)
+        fid_loc = apply_trans(mri_trans, fid_loc)
+    else:
+        fid_loc = []
+
+    if 'helmet' in meg_sensors and len(meg_picks) > 0:
         surfs['helmet'] = get_meg_helmet_surf(info, head_mri_t)
-    if coord_frame == 'meg':
-        surf_trans = combine_transforms(info['dev_head_t'], head_mri_t,
-                                        'meg', 'mri')
-    elif coord_frame == 'head':
-        surf_trans = head_mri_t
-    else:  # coord_frame == 'mri'
-        surf_trans = None
+    if brain is None:
+        if len(ecog_picks) > 0 and subject is not None:
+            brain = 'pial'
+        else:
+            brain = False
+    if brain:
+        subjects_dir = get_subjects_dir(subjects_dir, raise_error=True)
+        brain = 'pial' if brain is True else brain
+        for hemi in ['lh', 'rh']:
+            fname = op.join(subjects_dir, subject, 'surf',
+                            '%s.%s' % (hemi, brain))
+            rr, tris = read_surface(fname)
+            rr *= 1e-3
+            surfs[hemi] = dict(rr=rr, tris=tris, ntri=len(tris), np=len(rr),
+                               coord_frame=FIFF.FIFFV_COORD_MRI)
+            complete_surface_info(surfs[hemi], copy=False, verbose=False)
+
+    if skull is True:
+        skull = 'outer_skull'
+    if isinstance(skull, string_types):
+        skull = [skull]
+    elif not skull:
+        skull = []
+    if len(skull) > 0 and not isinstance(skull[0], dict):
+        skull = sorted(skull)
+    skull_alpha = dict()
+    skull_colors = dict()
+    hemi_val = 0.5
+    if src is None or (brain and any(s['type'] == 'surf' for s in src)):
+        hemi_val = 1.
+    alphas = (4 - np.arange(len(skull) + 1)) * (0.5 / 4.)
+    for idx, this_skull in enumerate(skull):
+        if isinstance(this_skull, dict):
+            from ..bem import _surf_name
+            skull_surf = this_skull
+            this_skull = _surf_name[skull_surf['id']]
+        else:
+            skull_fname = op.join(subjects_dir, subject, 'bem', 'flash',
+                                  '%s.surf' % this_skull)
+            if not op.exists(skull_fname):
+                skull_fname = op.join(subjects_dir, subject, 'bem',
+                                      '%s.surf' % this_skull)
+            if not op.exists(skull_fname):
+                raise IOError('No skull surface %s found for subject %s.'
+                              % (this_skull, subject))
+            logger.info('Using %s for head surface.' % skull_fname)
+            rr, tris = read_surface(skull_fname)
+            skull_surf = dict(rr=rr / 1000., tris=tris, ntri=len(tris),
+                              np=len(rr), coord_frame=FIFF.FIFFV_COORD_MRI)
+            complete_surface_info(skull_surf, copy=False, verbose=False)
+        skull_alpha[this_skull] = alphas[idx + 1]
+        skull_colors[this_skull] = (0.95 - idx * 0.2, 0.85, 0.95 - idx * 0.2)
+        surfs[this_skull] = skull_surf
+
+    if src is None and brain is False and len(skull) == 0:
+        head_alpha = 1.0
+    else:
+        head_alpha = alphas[0]
+
     for key in surfs.keys():
-        surfs[key] = transform_surface_to(surfs[key], coord_frame, surf_trans)
-    del surf_trans
+        surfs[key] = transform_surface_to(surfs[key], coord_frame, mri_trans)
+    src_rr = apply_trans(mri_trans, src_rr)
+    src_nn = apply_trans(mri_trans, src_nn, move=False)
 
     # determine points
     meg_rrs, meg_tris = list(), list()
+    ecog_loc = list()
     hpi_loc = list()
     ext_loc = list()
     car_loc = list()
     eeg_loc = list()
-    if eeg_sensors and (ch_type is None or ch_type == 'eeg'):
-        eeg_loc = np.array([info['chs'][k]['loc'][:3]
-                           for k in pick_types(info, meg=False, eeg=True)])
+    eegp_loc = list()
+    if len(eeg_sensors) > 0:
+        eeg_loc = np.array([info['chs'][k]['loc'][:3] for k in eeg_picks])
         if len(eeg_loc) > 0:
-            # Transform EEG electrodes from head coordinates if necessary
-            if coord_frame == 'meg':
-                eeg_loc = apply_trans(invert_transform(info['dev_head_t']),
-                                      eeg_loc)
-            elif coord_frame == 'mri':
-                eeg_loc = apply_trans(head_mri_t, eeg_loc)
-        else:
-            # only warn if EEG explicitly requested, or EEG channels exist but
-            # no locations are provided
-            if (ch_type is not None or
-                    len(pick_types(info, meg=False, eeg=True)) > 0):
-                warn('EEG electrode locations not found. Cannot plot EEG '
-                     'electrodes.')
-    if meg_sensors:
+            eeg_loc = apply_trans(head_trans, eeg_loc)
+            # XXX do projections here if necessary
+            if 'projected' in eeg_sensors:
+                eegp_loc, eegp_nn = _project_onto_surface(
+                    eeg_loc, surfs['head'], project_rrs=True,
+                    return_nn=True)[2:4]
+            if 'original' not in eeg_sensors:
+                eeg_loc = list()
+    del eeg_sensors
+    if 'sensors' in meg_sensors:
         coil_transs = [_loc_to_coil_trans(info['chs'][pick]['loc'])
                        for pick in meg_picks]
-        # Transform MEG coordinates from meg if necessary
-        trans = None
-        if coord_frame == 'head':
-            trans = info['dev_head_t']
-        elif coord_frame == 'mri':
-            trans = combine_transforms(info['dev_head_t'], head_mri_t,
-                                       'meg', 'mri')
         coils = _create_meg_coils([info['chs'][pick] for pick in meg_picks],
                                   acc='normal')
         offset = 0
         for coil, coil_trans in zip(coils, coil_transs):
             rrs, tris = _sensor_shape(coil)
             rrs = apply_trans(coil_trans, rrs)
-            if trans is not None:
-                rrs = apply_trans(trans, rrs)
             meg_rrs.append(rrs)
             meg_tris.append(tris + offset)
             offset += len(meg_rrs[-1])
         if len(meg_rrs) == 0:
             warn('MEG electrodes not found. Cannot plot MEG locations.')
         else:
-            meg_rrs = np.concatenate(meg_rrs, axis=0)
+            meg_rrs = apply_trans(meg_trans, np.concatenate(meg_rrs, axis=0))
             meg_tris = np.concatenate(meg_tris, axis=0)
+    del meg_sensors
     if dig:
-        hpi_loc = np.array([d['r'] for d in info['dig']
-                            if d['kind'] == FIFF.FIFFV_POINT_HPI])
-        ext_loc = np.array([d['r'] for d in info['dig']
-                           if d['kind'] == FIFF.FIFFV_POINT_EXTRA])
-        car_loc = np.array([d['r'] for d in info['dig']
-                            if d['kind'] == FIFF.FIFFV_POINT_CARDINAL])
+        if dig == 'fiducials':
+            hpi_loc = ext_loc = []
+        elif dig is not True:
+            raise ValueError("dig needs to be True, False or 'fiducials', "
+                             "not %s" % repr(dig))
+        else:
+            hpi_loc = np.array([d['r'] for d in info['dig']
+                                if d['kind'] == FIFF.FIFFV_POINT_HPI])
+            ext_loc = np.array([d['r'] for d in info['dig']
+                               if d['kind'] == FIFF.FIFFV_POINT_EXTRA])
+        car_loc = _fiducial_coords(info['dig'])
+        # Transform from head coords if necessary
         if coord_frame == 'meg':
-            t = invert_transform(info['dev_head_t'])
-            hpi_loc = apply_trans(t, hpi_loc)
-            ext_loc = apply_trans(t, ext_loc)
-            car_loc = apply_trans(t, car_loc)
+            for loc in (hpi_loc, ext_loc, car_loc):
+                loc[:] = apply_trans(invert_transform(info['dev_head_t']), loc)
         elif coord_frame == 'mri':
-            hpi_loc = apply_trans(head_mri_t, hpi_loc)
-            ext_loc = apply_trans(head_mri_t, ext_loc)
-            car_loc = apply_trans(head_mri_t, car_loc)
-        if len(car_loc) == len(ext_loc) == 0:
+            for loc in (hpi_loc, ext_loc, car_loc):
+                loc[:] = apply_trans(head_mri_t, loc)
+        if len(car_loc) == len(ext_loc) == len(hpi_loc) == 0:
             warn('Digitization points not found. Cannot plot digitization.')
+    del dig
+    if len(ecog_picks) > 0 and ecog_sensors:
+        ecog_loc = np.array([info['chs'][pick]['loc'][:3]
+                             for pick in ecog_picks])
 
-    # do the plotting, surfaces then points
-    from mayavi import mlab
+    # initialize figure
     fig = mlab.figure(bgcolor=(0.0, 0.0, 0.0), size=(600, 600))
-
-    alphas = dict(head=1.0, helmet=0.5)
-    colors = dict(head=(0.6, 0.6, 0.6), helmet=(0.0, 0.0, 0.6))
+    _toggle_mlab_render(fig, False)
+
+    # plot surfaces
+    alphas = dict(head=head_alpha, helmet=0.5, lh=hemi_val, rh=hemi_val)
+    alphas.update(skull_alpha)
+    colors = dict(head=(0.6,) * 3, helmet=(0.0, 0.0, 0.6), lh=(0.5,) * 3,
+                  rh=(0.5,) * 3)
+    colors.update(skull_colors)
     for key, surf in surfs.items():
-        x, y, z = surf['rr'].T
-        nn = surf['nn']
-        # make absolutely sure these are normalized for Mayavi
-        nn = nn / np.sum(nn * nn, axis=1)[:, np.newaxis]
+        # Make a solid surface
+        mesh = _create_mesh_surf(surf, fig)
+        with warnings.catch_warnings(record=True):  # traits
+            surface = mlab.pipeline.surface(mesh, color=colors[key],
+                                            opacity=alphas[key], figure=fig)
+        if key != 'helmet':
+            surface.actor.property.backface_culling = True
+
+    # plot points
+    defaults = DEFAULTS['coreg']
+    datas = [eeg_loc,
+             hpi_loc,
+             ext_loc, ecog_loc]
+    colors = [defaults['eeg_color'],
+              defaults['hpi_color'],
+              defaults['extra_color'], defaults['ecog_color']]
+    alphas = [0.8,
+              0.5,
+              0.25, 0.8]
+    scales = [defaults['eeg_scale'],
+              defaults['hpi_scale'],
+              defaults['extra_scale'], defaults['ecog_scale']]
+    for kind, loc in (('dig', car_loc), ('mri', fid_loc)):
+        if len(loc) > 0:
+            datas.extend(loc[:, np.newaxis])
+            colors.extend((defaults['lpa_color'],
+                           defaults['nasion_color'],
+                           defaults['rpa_color']))
+            alphas.extend(3 * (defaults[kind + '_fid_opacity'],))
+            scales.extend(3 * (defaults[kind + '_fid_scale'],))
+
+    for data, color, alpha, scale in zip(datas, colors, alphas, scales):
+        if len(data) > 0:
+            with warnings.catch_warnings(record=True):  # traits
+                points = mlab.points3d(data[:, 0], data[:, 1], data[:, 2],
+                                       color=color, scale_factor=scale,
+                                       opacity=alpha, figure=fig)
+                points.actor.property.backface_culling = True
+    if len(eegp_loc) > 0:
+        with warnings.catch_warnings(record=True):  # traits
+            quiv = mlab.quiver3d(
+                eegp_loc[:, 0], eegp_loc[:, 1], eegp_loc[:, 2],
+                eegp_nn[:, 0], eegp_nn[:, 1], eegp_nn[:, 2],
+                color=defaults['eegp_color'], mode='cylinder',
+                scale_factor=defaults['eegp_scale'], opacity=0.6, figure=fig)
+        quiv.glyph.glyph_source.glyph_source.height = defaults['eegp_height']
+        quiv.glyph.glyph_source.glyph_source.center = \
+            (0., -defaults['eegp_height'], 0)
+        quiv.glyph.glyph_source.glyph_source.resolution = 20
+        quiv.actor.property.backface_culling = True
+    if len(meg_rrs) > 0:
+        color, alpha = (0., 0.25, 0.5), 0.25
+        surf = dict(rr=meg_rrs, tris=meg_tris)
+        complete_surface_info(surf, copy=False, verbose=False)
+        mesh = _create_mesh_surf(surf, fig)
+        with warnings.catch_warnings(record=True):  # traits
+            surface = mlab.pipeline.surface(mesh, color=color,
+                                            opacity=alpha, figure=fig)
+        # Don't cull these backfaces
+    if len(src_rr) > 0:
+        with warnings.catch_warnings(record=True):  # traits
+            quiv = mlab.quiver3d(
+                src_rr[:, 0], src_rr[:, 1], src_rr[:, 2],
+                src_nn[:, 0], src_nn[:, 1], src_nn[:, 2], color=(1., 1., 0.),
+                mode='cylinder', scale_factor=3e-3, opacity=0.75, figure=fig)
+        quiv.glyph.glyph_source.glyph_source.height = 0.25
+        quiv.glyph.glyph_source.glyph_source.center = (0., 0., 0.)
+        quiv.glyph.glyph_source.glyph_source.resolution = 20
+        quiv.actor.property.backface_culling = True
+    with SilenceStdout():
+        mlab.view(90, 90, figure=fig)
+    _toggle_mlab_render(fig, True)
+    return fig
+
+
+ at verbose
+def plot_alignment(info, trans=None, subject=None, subjects_dir=None,
+                   surfaces=('head',), coord_frame='head',
+                   meg=('helmet', 'sensors'), eeg='original',
+                   dig=False, ecog=True, src=None, mri_fiducials=False,
+                   bem=None, verbose=None):
+    """Plot head, sensor, and source space alignment in 3D.
+
+    Parameters
+    ----------
+    info : dict
+        The measurement info.
+    trans : str | 'auto' | dict | None
+        The full path to the head<->MRI transform ``*-trans.fif`` file
+        produced during coregistration. If trans is None, an identity matrix
+        is assumed.
+    subject : str | None
+        The subject name corresponding to FreeSurfer environment
+        variable SUBJECT. Can be omitted if ``src`` is provided.
+    subjects_dir : str | None
+        The path to the freesurfer subjects reconstructions.
+        It corresponds to Freesurfer environment variable SUBJECTS_DIR.
+    surfaces : str | list
+        Surfaces to plot. Supported values: 'head', 'outer_skin',
+        'outer_skull', 'inner_skull', 'brain', 'pial', 'white', 'inflated'.
+        Defaults to ('head',).
+
+        .. note:: For single layer BEMs it is recommended to use 'brain'.
+    coord_frame : str
+        Coordinate frame to use, 'head', 'meg', or 'mri'.
+    meg : str | list | bool
+        Can be "helmet", "sensors" or "ref" to show the MEG helmet, sensors or
+        reference sensors respectively, or a combination like
+        ``['helmet', 'sensors']``. True translates to
+        ``('helmet', 'sensors', 'ref')``.
+    eeg : bool | str | list
+        Can be "original" (default; equivalent to True) or "projected" to
+        show EEG sensors in their digitized locations or projected onto the
+        scalp, or a list of these options including ``[]`` (equivalent of
+        False).
+    dig : bool | 'fiducials'
+        If True, plot the digitization points; 'fiducials' to plot fiducial
+        points only.
+    ecog : bool
+        If True (default), show ECoG sensors.
+    src : instance of SourceSpaces | None
+        If not None, also plot the source space points.
+    mri_fiducials : bool | str
+        Plot MRI fiducials (default False). If ``True``, look for a file with
+        the canonical name (``bem/{subject}-fiducials.fif``). If ``str`` it
+        should provide the full path to the fiducials file.
+    bem : list of dict | Instance of ConductorModel | None
+        Can be either the BEM surfaces (list of dict), a BEM solution or a
+        sphere model. If None, we first try loading
+        `'$SUBJECTS_DIR/$SUBJECT/bem/$SUBJECT-$SOURCE.fif'`, and then look for
+        `'$SUBJECT*$SOURCE.fif'` in the same directory. For `'outer_skin'`,
+        the subjects bem and bem/flash folders are searched. Defaults to None.
+    verbose : bool, str, int, or None
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
+
+    Returns
+    -------
+    fig : instance of mlab.Figure
+        The mayavi figure.
 
+    See Also
+    --------
+    mne.viz.plot_bem
+
+    Notes
+    -----
+    This function serves the purpose of checking the validity of the many
+    different steps of source reconstruction:
+
+    - Transform matrix (keywords ``trans``, ``meg`` and ``mri_fiducials``),
+    - BEM surfaces (keywords ``bem`` and ``surfaces``),
+    - sphere conductor model (keywords ``bem`` and ``surfaces``) and
+    - source space (keywords ``surfaces`` and ``src``).
+
+    .. versionadded:: 0.15
+    """
+    from ..forward import _create_meg_coils
+    mlab = _import_mlab()
+
+    if eeg is False:
+        eeg = list()
+    elif eeg is True:
+        eeg = 'original'
+    if meg is True:
+        meg = ('helmet', 'sensors', 'ref')
+    elif meg is False:
+        meg = list()
+    elif isinstance(meg, string_types):
+        meg = [meg]
+    if isinstance(eeg, string_types):
+        eeg = [eeg]
+
+    for kind, var in zip(('eeg', 'meg'), (eeg, meg)):
+        if not isinstance(var, (list, tuple)) or \
+                not all(isinstance(x, string_types) for x in var):
+            raise TypeError('%s must be list or tuple of str, got %s'
+                            % (kind, type(var)))
+    if not all(x in ('helmet', 'sensors', 'ref') for x in meg):
+        raise ValueError('meg must only contain "helmet", "sensors" or "ref", '
+                         'got %s' % (meg,))
+    if not all(x in ('original', 'projected') for x in eeg):
+        raise ValueError('eeg must only contain "original" and '
+                         '"projected", got %s' % (eeg,))
+
+    if not isinstance(info, Info):
+        raise TypeError('info must be an instance of Info, got %s'
+                        % type(info))
+
+    is_sphere = False
+    if isinstance(bem, ConductorModel) and bem['is_sphere']:
+        if len(bem['layers']) != 4 and len(surfaces) > 1:
+            raise ValueError('The sphere conductor model must have three '
+                             'layers for plotting skull and head.')
+        is_sphere = True
+
+    # Skull:
+    skull = list()
+    if 'outer_skull' in surfaces:
+        if isinstance(bem, ConductorModel) and not bem['is_sphere']:
+            skull.append(_bem_find_surface(bem, FIFF.FIFFV_BEM_SURF_ID_SKULL))
+        else:
+            skull.append('outer_skull')
+    if 'inner_skull' in surfaces:
+        if isinstance(bem, ConductorModel) and not bem['is_sphere']:
+            skull.append(_bem_find_surface(bem, FIFF.FIFFV_BEM_SURF_ID_BRAIN))
+        else:
+            skull.append('inner_skull')
+
+    surf_dict = _surf_dict.copy()
+    surf_dict['outer_skin'] = FIFF.FIFFV_BEM_SURF_ID_HEAD
+    if len(skull) > 0 and not isinstance(skull[0], dict):  # list of str
+        skull = sorted(skull)
+        # list of dict
+        if bem is not None and not isinstance(bem, ConductorModel):
+            for idx, surf_name in enumerate(skull):
+                for this_surf in bem:
+                    if this_surf['id'] == surf_dict[surf_name]:
+                        skull[idx] = this_surf
+                        break
+                else:
+                    raise ValueError('Could not find the surface for '
+                                     '%s.' % surf_name)
+
+    valid_coords = ['head', 'meg', 'mri']
+    if coord_frame not in valid_coords:
+        raise ValueError('coord_frame must be one of %s' % (valid_coords,))
+    if src is not None:
+        if not isinstance(src, SourceSpaces):
+            raise TypeError('src must be None or SourceSpaces, got %s'
+                            % (type(src),))
+        src_subject = src[0].get('subject_his_id', None)
+        subject = src_subject if subject is None else subject
+        if src_subject is not None and subject != src_subject:
+            raise ValueError('subject ("%s") did not match the subject name '
+                             ' in src ("%s")' % (subject, src_subject))
+        src_rr = np.concatenate([s['rr'][s['inuse'].astype(bool)]
+                                 for s in src])
+        src_nn = np.concatenate([s['nn'][s['inuse'].astype(bool)]
+                                 for s in src])
+    else:
+        src_rr = src_nn = np.empty((0, 3))
+
+    ref_meg = 'ref' in meg
+    meg_picks = pick_types(info, meg=True, ref_meg=ref_meg)
+    eeg_picks = pick_types(info, meg=False, eeg=True, ref_meg=False)
+    ecog_picks = pick_types(info, meg=False, ecog=True, ref_meg=False)
+
+    if isinstance(trans, string_types):
+        if trans == 'auto':
+            # let's try to do this in MRI coordinates so they're easy to plot
+            subjects_dir = get_subjects_dir(subjects_dir, raise_error=True)
+            trans = _find_trans(subject, subjects_dir)
+        trans = read_trans(trans, return_all=True)
+        exp = None
+        for trans in trans:  # we got at least 1
+            try:
+                trans = _ensure_trans(trans, 'head', 'mri')
+            except Exception as exp:
+                pass
+            else:
+                break
+        else:
+            raise exp
+    elif trans is None:
+        trans = Transform('head', 'mri')
+    elif not isinstance(trans, dict):
+        raise TypeError('trans must be str, dict, or None')
+    head_mri_t = _ensure_trans(trans, 'head', 'mri')
+    dev_head_t = info['dev_head_t']
+    del trans
+
+    # Figure out our transformations
+    if coord_frame == 'meg':
+        head_trans = invert_transform(dev_head_t)
+        meg_trans = Transform('meg', 'meg')
+        mri_trans = invert_transform(combine_transforms(
+            dev_head_t, head_mri_t, 'meg', 'mri'))
+    elif coord_frame == 'mri':
+        head_trans = head_mri_t
+        meg_trans = combine_transforms(dev_head_t, head_mri_t, 'meg', 'mri')
+        mri_trans = Transform('mri', 'mri')
+    else:  # coord_frame == 'head'
+        head_trans = Transform('head', 'head')
+        meg_trans = info['dev_head_t']
+        mri_trans = invert_transform(head_mri_t)
+
+    # both the head and helmet will be in MRI coordinates after this
+    surfs = dict()
+
+    # Head:
+    head = any(['outer_skin' in surfaces, 'head' in surfaces])
+    if head:
+        head_surf = None
+        if bem is not None:
+            if isinstance(bem, ConductorModel):
+                if is_sphere:
+                    head_surf = _complete_sphere_surf(bem, 3, 4)
+                else:  # BEM solution
+                    head_surf = _bem_find_surface(bem,
+                                                  FIFF.FIFFV_BEM_SURF_ID_HEAD)
+                    complete_surface_info(head_surf, copy=False, verbose=False)
+            elif bem is not None:  # list of dict
+                for this_surf in bem:
+                    if this_surf['id'] == FIFF.FIFFV_BEM_SURF_ID_HEAD:
+                        head_surf = this_surf
+                        break
+                else:
+                    raise ValueError('Could not find the surface for head.')
+        if head_surf is None:
+            subjects_dir = get_subjects_dir(subjects_dir, raise_error=True)
+            surf_fname = op.join(subjects_dir, subject, 'bem', 'flash',
+                                 'outer_skin.surf')
+            if not op.exists(surf_fname):
+                surf_fname = op.join(subjects_dir, subject, 'bem',
+                                     'outer_skin.surf')
+                if not op.exists(surf_fname):
+                    raise IOError('No head surface found for subject '
+                                  '%s.' % subject)
+            logger.info('Using outer_skin for head surface.')
+            rr, tris = read_surface(surf_fname)
+            head_surf = dict(rr=rr / 1000., tris=tris, ntri=len(tris),
+                             np=len(rr), coord_frame=FIFF.FIFFV_COORD_MRI)
+            complete_surface_info(head_surf, copy=False, verbose=False)
+
+        surfs['head'] = head_surf
+
+    if mri_fiducials:
+        if mri_fiducials is True:
+            subjects_dir = get_subjects_dir(subjects_dir, raise_error=True)
+            if subject is None:
+                raise ValueError("Subject needs to be specified to "
+                                 "automatically find the fiducials file.")
+            mri_fiducials = op.join(subjects_dir, subject, 'bem',
+                                    subject + '-fiducials.fif')
+        if isinstance(mri_fiducials, string_types):
+            mri_fiducials, cf = read_fiducials(mri_fiducials)
+            if cf != FIFF.FIFFV_COORD_MRI:
+                raise ValueError("Fiducials are not in MRI space")
+        fid_loc = _fiducial_coords(mri_fiducials, FIFF.FIFFV_COORD_MRI)
+        fid_loc = apply_trans(mri_trans, fid_loc)
+    else:
+        fid_loc = []
+
+    if 'helmet' in meg and len(meg_picks) > 0:
+        surfs['helmet'] = get_meg_helmet_surf(info, head_mri_t)
+
+    # Brain:
+    brain = False
+    brain_surfs = np.intersect1d(surfaces, ['brain', 'pial', 'white',
+                                            'inflated'])
+    if len(brain_surfs) > 1:
+        raise ValueError('Only one brain surface can be plotted. '
+                         'Got %s.' % brain_surfs)
+    elif len(brain_surfs) == 1:
+        if brain_surfs[0] == 'brain':
+            brain = 'pial'
+        else:
+            brain = brain_surfs[0]
+
+    if brain:
+        if is_sphere:
+            if len(bem['layers']) > 0:
+                surfs['lh'] = _complete_sphere_surf(bem, 0, 4)  # only plot 1
+        else:
+            subjects_dir = get_subjects_dir(subjects_dir, raise_error=True)
+            for hemi in ['lh', 'rh']:
+                fname = op.join(subjects_dir, subject, 'surf',
+                                '%s.%s' % (hemi, brain))
+                rr, tris = read_surface(fname)
+                rr *= 1e-3
+                surfs[hemi] = dict(rr=rr, ntri=len(tris), np=len(rr),
+                                   coord_frame=FIFF.FIFFV_COORD_MRI, tris=tris)
+                complete_surface_info(surfs[hemi], copy=False, verbose=False)
+
+    skull_alpha = dict()
+    skull_colors = dict()
+    hemi_val = 0.5
+    if src is None or (brain and any(s['type'] == 'surf' for s in src)):
+        hemi_val = 1.
+    alphas = (4 - np.arange(len(skull) + 1)) * (0.5 / 4.)
+    for idx, this_skull in enumerate(skull):
+        if isinstance(this_skull, dict):
+            skull_surf = this_skull
+            this_skull = _surf_name[skull_surf['id']]
+        elif is_sphere:  # this_skull == str
+            this_idx = 1 if this_skull == 'inner_skull' else 2
+            skull_surf = _complete_sphere_surf(bem, this_idx, 4)
+        else:  # str
+            skull_fname = op.join(subjects_dir, subject, 'bem', 'flash',
+                                  '%s.surf' % this_skull)
+            if not op.exists(skull_fname):
+                skull_fname = op.join(subjects_dir, subject, 'bem',
+                                      '%s.surf' % this_skull)
+            if not op.exists(skull_fname):
+                raise IOError('No skull surface %s found for subject %s.'
+                              % (this_skull, subject))
+            logger.info('Using %s for head surface.' % skull_fname)
+            rr, tris = read_surface(skull_fname)
+            skull_surf = dict(rr=rr / 1000., tris=tris, ntri=len(tris),
+                              np=len(rr), coord_frame=FIFF.FIFFV_COORD_MRI)
+            complete_surface_info(skull_surf, copy=False, verbose=False)
+        skull_alpha[this_skull] = alphas[idx + 1]
+        skull_colors[this_skull] = (0.95 - idx * 0.2, 0.85, 0.95 - idx * 0.2)
+        surfs[this_skull] = skull_surf
+
+    if src is None and brain is False and len(skull) == 0:
+        head_alpha = 1.0
+    else:
+        head_alpha = alphas[0]
+
+    for key in surfs.keys():
+        surfs[key] = transform_surface_to(surfs[key], coord_frame, mri_trans)
+    if src is not None:
+        if src[0]['coord_frame'] == FIFF.FIFFV_COORD_MRI:
+            src_rr = apply_trans(mri_trans, src_rr)
+            src_nn = apply_trans(mri_trans, src_nn, move=False)
+        elif src[0]['coord_frame'] == FIFF.FIFFV_COORD_HEAD:
+            src_rr = apply_trans(head_trans, src_rr)
+            src_nn = apply_trans(head_trans, src_nn, move=False)
+
+    # determine points
+    meg_rrs, meg_tris = list(), list()
+    ecog_loc = list()
+    hpi_loc = list()
+    ext_loc = list()
+    car_loc = list()
+    eeg_loc = list()
+    eegp_loc = list()
+    if len(eeg) > 0:
+        eeg_loc = np.array([info['chs'][k]['loc'][:3] for k in eeg_picks])
+        if len(eeg_loc) > 0:
+            eeg_loc = apply_trans(head_trans, eeg_loc)
+            # XXX do projections here if necessary
+            if 'projected' in eeg:
+                eegp_loc, eegp_nn = _project_onto_surface(
+                    eeg_loc, surfs['head'], project_rrs=True,
+                    return_nn=True)[2:4]
+            if 'original' not in eeg:
+                eeg_loc = list()
+    del eeg
+    if 'sensors' in meg:
+        coil_transs = [_loc_to_coil_trans(info['chs'][pick]['loc'])
+                       for pick in meg_picks]
+        coils = _create_meg_coils([info['chs'][pick] for pick in meg_picks],
+                                  acc='normal')
+        offset = 0
+        for coil, coil_trans in zip(coils, coil_transs):
+            rrs, tris = _sensor_shape(coil)
+            rrs = apply_trans(coil_trans, rrs)
+            meg_rrs.append(rrs)
+            meg_tris.append(tris + offset)
+            offset += len(meg_rrs[-1])
+        if len(meg_rrs) == 0:
+            warn('MEG electrodes not found. Cannot plot MEG locations.')
+        else:
+            meg_rrs = apply_trans(meg_trans, np.concatenate(meg_rrs, axis=0))
+            meg_tris = np.concatenate(meg_tris, axis=0)
+    del meg
+    if dig:
+        if dig == 'fiducials':
+            hpi_loc = ext_loc = []
+        elif dig is not True:
+            raise ValueError("dig needs to be True, False or 'fiducials', "
+                             "not %s" % repr(dig))
+        else:
+            hpi_loc = np.array([d['r'] for d in info['dig']
+                                if d['kind'] == FIFF.FIFFV_POINT_HPI])
+            ext_loc = np.array([d['r'] for d in info['dig']
+                               if d['kind'] == FIFF.FIFFV_POINT_EXTRA])
+        car_loc = _fiducial_coords(info['dig'])
+        # Transform from head coords if necessary
+        if coord_frame == 'meg':
+            for loc in (hpi_loc, ext_loc, car_loc):
+                loc[:] = apply_trans(invert_transform(info['dev_head_t']), loc)
+        elif coord_frame == 'mri':
+            for loc in (hpi_loc, ext_loc, car_loc):
+                loc[:] = apply_trans(head_mri_t, loc)
+        if len(car_loc) == len(ext_loc) == len(hpi_loc) == 0:
+            warn('Digitization points not found. Cannot plot digitization.')
+    del dig
+    if len(ecog_picks) > 0 and ecog:
+        ecog_loc = np.array([info['chs'][pick]['loc'][:3]
+                             for pick in ecog_picks])
+
+    # initialize figure
+    fig = mlab.figure(bgcolor=(0.0, 0.0, 0.0), size=(600, 600))
+    _toggle_mlab_render(fig, False)
+
+    # plot surfaces
+    alphas = dict(head=head_alpha, helmet=0.5, lh=hemi_val, rh=hemi_val)
+    alphas.update(skull_alpha)
+    colors = dict(head=(0.6,) * 3, helmet=(0.0, 0.0, 0.6), lh=(0.5,) * 3,
+                  rh=(0.5,) * 3)
+    colors.update(skull_colors)
+    for key, surf in surfs.items():
         # Make a solid surface
+        mesh = _create_mesh_surf(surf, fig)
         with warnings.catch_warnings(record=True):  # traits
-            mesh = mlab.pipeline.triangular_mesh_source(x, y, z, surf['tris'])
-        mesh.data.point_data.normals = nn
-        mesh.data.cell_data.normals = None
-        mlab.pipeline.surface(mesh, color=colors[key], opacity=alphas[key])
-
-    datas = (eeg_loc, hpi_loc, car_loc, ext_loc)
-    colors = ((1., 0., 0.), (0., 1., 0.), (1., 1., 0.), (1., 0.5, 0.))
-    alphas = (1.0, 0.5, 0.5, 0.25)
-    scales = (0.005, 0.015, 0.015, 0.0075)
+            surface = mlab.pipeline.surface(mesh, color=colors[key],
+                                            opacity=alphas[key], figure=fig)
+        if key != 'helmet':
+            surface.actor.property.backface_culling = True
+    if brain and 'lh' not in surfs:  # one layer sphere
+        assert bem['coord_frame'] == FIFF.FIFFV_COORD_HEAD
+        center = bem['r0'].copy()
+        center = apply_trans(head_trans, center)
+        mlab.points3d(*center, scale_factor=0.01, color=colors['lh'],
+                      opacity=alphas['lh'])
+
+    # plot points
+    defaults = DEFAULTS['coreg']
+    datas = [eeg_loc,
+             hpi_loc,
+             ext_loc, ecog_loc]
+    colors = [defaults['eeg_color'],
+              defaults['hpi_color'],
+              defaults['extra_color'], defaults['ecog_color']]
+    alphas = [0.8,
+              0.5,
+              0.25, 0.8]
+    scales = [defaults['eeg_scale'],
+              defaults['hpi_scale'],
+              defaults['extra_scale'], defaults['ecog_scale']]
+    for kind, loc in (('dig', car_loc), ('mri', fid_loc)):
+        if len(loc) > 0:
+            datas.extend(loc[:, np.newaxis])
+            colors.extend((defaults['lpa_color'],
+                           defaults['nasion_color'],
+                           defaults['rpa_color']))
+            alphas.extend(3 * (defaults[kind + '_fid_opacity'],))
+            scales.extend(3 * (defaults[kind + '_fid_scale'],))
+
     for data, color, alpha, scale in zip(datas, colors, alphas, scales):
         if len(data) > 0:
             with warnings.catch_warnings(record=True):  # traits
-                mlab.points3d(data[:, 0], data[:, 1], data[:, 2],
-                              color=color, scale_factor=scale, opacity=alpha)
+                points = mlab.points3d(data[:, 0], data[:, 1], data[:, 2],
+                                       color=color, scale_factor=scale,
+                                       opacity=alpha, figure=fig)
+                points.actor.property.backface_culling = True
+    if len(eegp_loc) > 0:
+        with warnings.catch_warnings(record=True):  # traits
+            quiv = mlab.quiver3d(
+                eegp_loc[:, 0], eegp_loc[:, 1], eegp_loc[:, 2],
+                eegp_nn[:, 0], eegp_nn[:, 1], eegp_nn[:, 2],
+                color=defaults['eegp_color'], mode='cylinder',
+                scale_factor=defaults['eegp_scale'], opacity=0.6, figure=fig)
+        quiv.glyph.glyph_source.glyph_source.height = defaults['eegp_height']
+        quiv.glyph.glyph_source.glyph_source.center = \
+            (0., -defaults['eegp_height'], 0)
+        quiv.glyph.glyph_source.glyph_source.resolution = 20
+        quiv.actor.property.backface_culling = True
     if len(meg_rrs) > 0:
         color, alpha = (0., 0.25, 0.5), 0.25
-        mlab.triangular_mesh(meg_rrs[:, 0], meg_rrs[:, 1], meg_rrs[:, 2],
-                             meg_tris, color=color, opacity=alpha)
-    mlab.view(90, 90)
+        surf = dict(rr=meg_rrs, tris=meg_tris)
+        complete_surface_info(surf, copy=False, verbose=False)
+        mesh = _create_mesh_surf(surf, fig)
+        with warnings.catch_warnings(record=True):  # traits
+            surface = mlab.pipeline.surface(mesh, color=color,
+                                            opacity=alpha, figure=fig)
+        # Don't cull these backfaces
+    if len(src_rr) > 0:
+        with warnings.catch_warnings(record=True):  # traits
+            quiv = mlab.quiver3d(
+                src_rr[:, 0], src_rr[:, 1], src_rr[:, 2],
+                src_nn[:, 0], src_nn[:, 1], src_nn[:, 2], color=(1., 1., 0.),
+                mode='cylinder', scale_factor=3e-3, opacity=0.75, figure=fig)
+        quiv.glyph.glyph_source.glyph_source.height = 0.25
+        quiv.glyph.glyph_source.glyph_source.center = (0., 0., 0.)
+        quiv.glyph.glyph_source.glyph_source.resolution = 20
+        quiv.actor.property.backface_culling = True
+    with SilenceStdout():
+        mlab.view(90, 90, figure=fig)
+    _toggle_mlab_render(fig, True)
     return fig
 
 
 def _make_tris_fan(n_vert):
-    """Helper to make tris given a number of vertices of a circle-like obj"""
+    """Make tris given a number of vertices of a circle-like obj."""
     tris = np.zeros((n_vert - 2, 3), int)
     tris[:, 2] = np.arange(2, n_vert)
     tris[:, 1] = tris[:, 2] - 1
@@ -471,7 +1373,7 @@ def _make_tris_fan(n_vert):
 
 
 def _sensor_shape(coil):
-    """Get the sensor shape vertices"""
+    """Get the sensor shape vertices."""
     rrs = np.empty([0, 2])
     tris = np.empty([0, 3], int)
     id_ = coil['type'] & 0xFFFF
@@ -496,7 +1398,8 @@ def _sensor_shape(coil):
         size = 0.001 if id_ == 2000 else (coil['size'] / 2.)
         rrs = np.array([[-1., 1.], [1., 1.], [1., -1.], [-1., -1.]]) * size
         tris = _make_tris_fan(4)
-    elif id_ in (4001, 4003, 5002, 7002, 7003):
+    elif id_ in (4001, 4003, 5002, 7002, 7003,
+                 FIFF.FIFFV_COIL_ARTEMIS123_REF_MAG):
         # round magnetometer
         n_pts = 15  # number of points for circle
         circle = np.exp(2j * np.pi * np.arange(n_pts) / float(n_pts))
@@ -504,7 +1407,9 @@ def _sensor_shape(coil):
         circle *= coil['size'] / 2.  # radius of coil
         rrs = np.array([circle.real, circle.imag]).T
         tris = _make_tris_fan(n_pts + 1)
-    elif id_ in (4002, 5001, 5003, 5004, 4004, 4005, 6001, 7001):
+    elif id_ in (4002, 5001, 5003, 5004, 4004, 4005, 6001, 7001,
+                 FIFF.FIFFV_COIL_ARTEMIS123_GRAD,
+                 FIFF.FIFFV_COIL_ARTEMIS123_REF_GRAD):
         # round coil 1st order (off-diagonal) gradiometer
         baseline = coil['base'] if id_ in (5004, 4005) else 0.
         n_pts = 16  # number of points for circle
@@ -524,8 +1429,7 @@ def _sensor_shape(coil):
 
 
 def _limits_to_control_points(clim, stc_data, colormap):
-    """Private helper function to convert limits (values or percentiles)
-    to control points.
+    """Convert limits (values or percentiles) to control points.
 
     Note: If using 'mne', generate cmap control points for a directly
     mirrored cmap for simplicity (i.e., no normalization is computed to account
@@ -543,7 +1447,6 @@ def _limits_to_control_points(clim, stc_data, colormap):
     colormap : str
         The colormap.
     """
-
     # Based on type of limits specified, get cmap control points
     if colormap == 'auto':
         if clim == 'auto':
@@ -596,23 +1499,210 @@ def _limits_to_control_points(clim, stc_data, colormap):
     return ctrl_pts, colormap
 
 
+def _handle_time(time_label, time_unit, times):
+    """Handle time label string and units."""
+    if time_label == 'auto':
+        if time_unit == 's':
+            time_label = 'time=%0.3fs'
+        elif time_unit == 'ms':
+            time_label = 'time=%0.1fms'
+    if time_unit == 's':
+        times = times
+    elif time_unit == 'ms':
+        times = 1e3 * times
+    else:
+        raise ValueError("time_unit needs to be 's' or 'ms', got %r" %
+                         (time_unit,))
+
+    return time_label, times
+
+
+def _key_pressed_slider(event, params):
+    """Handle key presses for time_viewer slider."""
+    step = 1
+    if event.key.startswith('ctrl'):
+        step = 5
+        event.key = event.key.split('+')[-1]
+    if event.key not in ['left', 'right']:
+        return
+    time_viewer = event.canvas.figure
+    value = time_viewer.slider.val
+    times = params['stc'].times
+    if params['time_unit'] == 'ms':
+        times = times * 1000.
+    time_idx = np.argmin(np.abs(times - value))
+    if event.key == 'left':
+        time_idx = np.max((0, time_idx - step))
+    elif event.key == 'right':
+        time_idx = np.min((len(times) - 1, time_idx + step))
+    this_time = times[time_idx]
+    time_viewer.slider.set_val(this_time)
+
+
+def _smooth_plot(this_time, params):
+    """Smooth source estimate data and plot with mpl."""
+    from ..source_estimate import _morph_buffer
+    from mpl_toolkits.mplot3d import art3d
+    ax = params['ax']
+    stc = params['stc']
+    ax.clear()
+    times = stc.times
+    scaler = 1000. if params['time_unit'] == 'ms' else 1.
+    if this_time is None:
+        time_idx = 0
+    else:
+        time_idx = np.argmin(np.abs(times - this_time / scaler))
+
+    if params['hemi_idx'] == 0:
+        data = stc.data[:len(stc.vertices[0]), time_idx:time_idx + 1]
+    else:
+        data = stc.data[len(stc.vertices[0]):, time_idx:time_idx + 1]
+
+    array_plot = _morph_buffer(data, params['vertices'], params['e'],
+                               params['smoothing_steps'], params['n_verts'],
+                               params['inuse'], params['maps'])
+
+    vmax = np.max(array_plot)
+    colors = array_plot / vmax
+
+    transp = 0.8
+    faces = params['faces']
+    greymap = params['greymap']
+    cmap = params['cmap']
+    polyc = ax.plot_trisurf(*params['coords'].T, triangles=faces,
+                            antialiased=False)
+    color_ave = np.mean(colors[faces], axis=1).flatten()
+    curv_ave = np.mean(params['curv'][faces], axis=1).flatten()
+    facecolors = art3d.PolyCollection.get_facecolors(polyc)
+
+    to_blend = color_ave > params['ctrl_pts'][0] / vmax
+    facecolors[to_blend, :3] = ((1 - transp) *
+                                greymap(curv_ave[to_blend])[:, :3] +
+                                transp * cmap(color_ave[to_blend])[:, :3])
+    facecolors[~to_blend, :3] = greymap(curv_ave[~to_blend])[:, :3]
+    ax.set_title(params['time_label'] % (times[time_idx] * scaler), color='w')
+    ax.set_aspect('equal')
+    ax.axis('off')
+    ax.set_xlim(-80, 80)
+    ax.set_ylim(-80, 80)
+    ax.set_zlim(-80, 80)
+    ax.figure.canvas.draw()
+
+
+def _plot_mpl_stc(stc, subject=None, surface='inflated', hemi='lh',
+                  colormap='auto', time_label='auto', smoothing_steps=10,
+                  subjects_dir=None, views='lat', clim='auto', figure=None,
+                  initial_time=None, time_unit='s', background='black',
+                  spacing='oct6', time_viewer=False):
+    """Plot source estimate using mpl."""
+    import matplotlib.pyplot as plt
+    from mpl_toolkits.mplot3d import Axes3D
+    from matplotlib import cm
+    from matplotlib.widgets import Slider
+    import nibabel as nib
+    from scipy import sparse, stats
+    from ..source_estimate import _get_subject_sphere_tris
+    if hemi not in ['lh', 'rh']:
+        raise ValueError("hemi must be 'lh' or 'rh' when using matplotlib. "
+                         "Got %s." % hemi)
+    kwargs = {'lat': {'elev': 5, 'azim': 0},
+              'med': {'elev': 5, 'azim': 180},
+              'fos': {'elev': 5, 'azim': 90},
+              'cau': {'elev': 5, 'azim': -90},
+              'dor': {'elev': 90, 'azim': 0},
+              'ven': {'elev': -90, 'azim': 0},
+              'fro': {'elev': 5, 'azim': 110},
+              'par': {'elev': 5, 'azim': -110}}
+    if views not in kwargs:
+        raise ValueError("views must be one of ['lat', 'med', 'fos', 'cau', "
+                         "'dor' 'ven', 'fro', 'par']. Got %s." % views)
+    ctrl_pts, colormap = _limits_to_control_points(clim, stc.data, colormap)
+    if colormap == 'auto':
+        colormap = mne_analyze_colormap(clim, format='matplotlib')
+
+    time_label, times = _handle_time(time_label, time_unit, stc.times)
+    fig = plt.figure(figsize=(6, 6)) if figure is None else figure
+    ax = Axes3D(fig)
+    hemi_idx = 0 if hemi == 'lh' else 1
+    surf = op.join(subjects_dir, subject, 'surf', '%s.%s' % (hemi, surface))
+    if spacing == 'all':
+        coords, faces = nib.freesurfer.read_geometry(surf)
+        inuse = slice(None)
+    else:
+        stype, sval, ico_surf, src_type_str = _check_spacing(spacing)
+        surf = _create_surf_spacing(surf, hemi, subject, stype, ico_surf,
+                                    subjects_dir)
+        inuse = surf['vertno']
+        faces = surf['use_tris']
+        coords = surf['rr'][inuse]
+        shape = faces.shape
+        faces = stats.rankdata(faces, 'dense').reshape(shape) - 1
+        faces = np.round(faces).astype(int)  # should really be int-like anyway
+    del surf
+    vertices = stc.vertices[hemi_idx]
+    n_verts = len(vertices)
+    tris = _get_subject_sphere_tris(subject, subjects_dir)[hemi_idx]
+    e = mesh_edges(tris)
+    e.data[e.data == 2] = 1
+    n_vertices = e.shape[0]
+    maps = sparse.identity(n_vertices).tocsr()
+    e = e + sparse.eye(n_vertices, n_vertices)
+    cmap = cm.get_cmap(colormap)
+    greymap = cm.get_cmap('Greys')
+
+    curv = nib.freesurfer.read_morph_data(
+        op.join(subjects_dir, subject, 'surf', '%s.curv' % hemi))[inuse]
+    curv = np.clip(np.array(curv > 0, np.int), 0.2, 0.8)
+    params = dict(ax=ax, stc=stc, coords=coords, faces=faces,
+                  hemi_idx=hemi_idx, vertices=vertices, e=e,
+                  smoothing_steps=smoothing_steps, n_verts=n_verts,
+                  inuse=inuse, maps=maps, cmap=cmap, curv=curv,
+                  ctrl_pts=ctrl_pts, greymap=greymap, time_label=time_label,
+                  time_unit=time_unit)
+    _smooth_plot(initial_time, params)
+
+    ax.view_init(**kwargs[views])
+
+    try:
+        ax.set_facecolor(background)
+    except AttributeError:
+        ax.set_axis_bgcolor(background)
+
+    if time_viewer:
+        time_viewer = figure_nobar(figsize=(4.5, .25))
+        fig.time_viewer = time_viewer
+        ax_time = plt.axes()
+        if initial_time is None:
+            initial_time = 0
+        slider = Slider(ax=ax_time, label='Time', valmin=times[0],
+                        valmax=times[-1], valinit=initial_time,
+                        valfmt=time_label)
+        time_viewer.slider = slider
+        callback_slider = partial(_smooth_plot, params=params)
+        slider.on_changed(callback_slider)
+        callback_key = partial(_key_pressed_slider, params=params)
+        time_viewer.canvas.mpl_connect('key_press_event', callback_key)
+
+        time_viewer.subplots_adjust(left=0.12, bottom=0.05, right=0.75,
+                                    top=0.95)
+    fig.subplots_adjust(left=0., bottom=0., right=1., top=1.)
+    plt.show()
+    return fig
+
+
 def plot_source_estimates(stc, subject=None, surface='inflated', hemi='lh',
                           colormap='auto', time_label='auto',
                           smoothing_steps=10, transparent=None, alpha=1.0,
-                          time_viewer=False, config_opts=None,
-                          subjects_dir=None, figure=None, views='lat',
-                          colorbar=True, clim='auto', cortex="classic",
-                          size=800, background="black", foreground="white",
-                          initial_time=None, time_unit=None):
-    """Plot SourceEstimates with PySurfer
-
-    Note: PySurfer currently needs the SUBJECTS_DIR environment variable,
-    which will automatically be set by this function. Plotting multiple
-    SourceEstimates with different values for subjects_dir will cause
-    PySurfer to use the wrong FreeSurfer surfaces when using methods of
-    the returned Brain object. It is therefore recommended to set the
-    SUBJECTS_DIR environment variable or always use the same value for
-    subjects_dir (within the same Python session).
+                          time_viewer=False, subjects_dir=None, figure=None,
+                          views='lat', colorbar=True, clim='auto',
+                          cortex="classic", size=800, background="black",
+                          foreground="white", initial_time=None,
+                          time_unit='s', backend='auto', spacing='oct6'):
+    """Plot SourceEstimates with PySurfer.
+
+    By default this function uses :mod:`mayavi.mlab` to plot the source
+    estimates. If Mayavi is not installed, the plotting is done with
+    :mod:`matplotlib.pyplot` (much slower, decimated source space by default).
 
     Parameters
     ----------
@@ -639,31 +1729,34 @@ def plot_source_estimates(stc, subject=None, surface='inflated', hemi='lh',
         The amount of smoothing
     transparent : bool | None
         If True, use a linear transparency between fmin and fmid.
-        None will choose automatically based on colormap type.
+        None will choose automatically based on colormap type. Has no effect
+        with mpl backend.
     alpha : float
-        Alpha value to apply globally to the overlay.
+        Alpha value to apply globally to the overlay. Has no effect with mpl
+        backend.
     time_viewer : bool
         Display time viewer GUI.
-    config_opts : dict
-        Deprecated parameter.
     subjects_dir : str
         The path to the freesurfer subjects reconstructions.
         It corresponds to Freesurfer environment variable SUBJECTS_DIR.
-    figure : instance of mayavi.core.scene.Scene | list | int | None
+    figure : instance of mayavi.core.scene.Scene | instance of matplotlib.figure.Figure | list | int | None
         If None, a new figure will be created. If multiple views or a
         split view is requested, this must be a list of the appropriate
         length. If int is provided it will be used to identify the Mayavi
-        figure by it's id or create a new figure with the given id.
+        figure by it's id or create a new figure with the given id. If an
+        instance of matplotlib figure, mpl backend is used for plotting.
     views : str | list
-        View to use. See surfer.Brain().
+        View to use. See surfer.Brain(). Supported views: ['lat', 'med', 'fos',
+        'cau', 'dor' 'ven', 'fro', 'par']. Using multiple views is not
+        supported for mpl backend.
     colorbar : bool
-        If True, display colorbar on scene.
+        If True, display colorbar on scene. Not available on mpl backend.
     clim : str | dict
         Colorbar properties specification. If 'auto', set clim automatically
         based on data percentiles. If dict, should contain:
 
-            ``kind`` : str
-                Flag to specify type of limits. 'value' or 'percent'.
+            ``kind`` : 'value' | 'percent'
+                Flag to specify type of limits.
             ``lims`` : list | np.ndarray | tuple of float, 3 elements
                 Note: Only use this if 'colormap' is not 'mne'.
                 Left, middle, and right bound for colormap.
@@ -674,74 +1767,78 @@ def plot_source_estimates(stc, subject=None, surface='inflated', hemi='lh',
                 construction to obtain negative control points.
 
     cortex : str or tuple
-        specifies how binarized curvature values are rendered.
-        either the name of a preset PySurfer cortex colorscheme (one of
-        'classic', 'bone', 'low_contrast', or 'high_contrast'), or the
-        name of mayavi colormap, or a tuple with values (colormap, min,
-        max, reverse) to fully specify the curvature colors.
+        Specifies how binarized curvature values are rendered.
+        Either the name of a preset PySurfer cortex colorscheme (one of
+        'classic', 'bone', 'low_contrast', or 'high_contrast'), or the name of
+        mayavi colormap, or a tuple with values (colormap, min, max, reverse)
+        to fully specify the curvature colors. Has no effect with mpl backend.
     size : float or pair of floats
         The size of the window, in pixels. can be one number to specify
         a square window, or the (width, height) of a rectangular window.
+        Has no effect with mpl backend.
     background : matplotlib color
         Color of the background of the display window.
     foreground : matplotlib color
-        Color of the foreground of the display window.
+        Color of the foreground of the display window. Has no effect with mpl
+        backend.
     initial_time : float | None
         The time to display on the plot initially. ``None`` to display the
         first time sample (default).
     time_unit : 's' | 'ms'
-        Whether time is represented in seconds (expected by PySurfer) or
-        milliseconds. The current default is 'ms', but will change to 's'
-        in MNE 0.14. To avoid a deprecation warning specify ``time_unit``
-        explicitly.
+        Whether time is represented in seconds ("s", default) or
+        milliseconds ("ms").
+    backend : 'auto' | 'mayavi' | 'matplotlib'
+        Which backend to use. If ``'auto'`` (default), tries to plot with
+        mayavi, but resorts to matplotlib if mayavi is not available.
+
+        .. versionadded:: 0.15.0
+
+    spacing : str
+        The spacing to use for the source space. Can be ``'ico#'`` for a
+        recursively subdivided icosahedron, ``'oct#'`` for a recursively
+        subdivided octahedron, or ``'all'`` for all points. In general, you can
+        speed up the plotting by selecting a sparser source space. Has no
+        effect with mayavi backend. Defaults  to 'oct6'.
 
+        .. versionadded:: 0.15.0
 
     Returns
     -------
-    brain : Brain
-        A instance of surfer.viz.Brain from PySurfer.
-    """
-    import surfer
-    from surfer import Brain, TimeViewer
-    import mayavi
-
+    figure : surfer.viz.Brain | matplotlib.figure.Figure
+        An instance of :class:`surfer.Brain` from PySurfer or
+        matplotlib figure.
+    """  # noqa: E501
     # import here to avoid circular import problem
     from ..source_estimate import SourceEstimate
-
-    surfer_version = LooseVersion(surfer.__version__)
-    v06 = LooseVersion('0.6')
-    if surfer_version < v06:
-        raise ImportError("This function requires PySurfer 0.6 (you are "
-                          "running version %s). You can update PySurfer "
-                          "using:\n\n    $ pip install -U pysurfer" %
-                          surfer.__version__)
-
-    if time_unit is None:
-        if initial_time is not None:
-            warn("The time_unit parameter default will change from 'ms' to "
-                 "'s' in MNE 0.14 and be removed in 0.15. To avoid this "
-                 "warning specify the parameter explicitly.",
-                 DeprecationWarning)
-        time_unit = 'ms'
-    elif time_unit not in ('s', 'ms'):
-        raise ValueError("time_unit needs to be 's' or 'ms', got %r" %
-                         (time_unit,))
-
-    if initial_time is not None and surfer_version > v06:
-        kwargs = {'initial_time': initial_time}
-        initial_time = None  # don't set it twice
-    else:
-        kwargs = {}
-
-    if time_label == 'auto':
-        if time_unit == 'ms':
-            time_label = 'time=%0.2f ms'
-        else:
-            def time_label(t):
-                return 'time=%0.2f ms' % (t * 1e3)
-
     if not isinstance(stc, SourceEstimate):
         raise ValueError('stc has to be a surface source estimate')
+    subjects_dir = get_subjects_dir(subjects_dir=subjects_dir,
+                                    raise_error=True)
+    subject = _check_subject(stc.subject, subject, True)
+    if backend not in ['auto', 'matplotlib', 'mayavi']:
+        raise ValueError("backend must be 'auto', 'mayavi' or 'matplotlib'. "
+                         "Got %s." % backend)
+    plot_mpl = backend == 'matplotlib'
+    if not plot_mpl:
+        try:
+            import mayavi
+        except ImportError:
+            if backend == 'auto':
+                warn('Mayavi not found. Resorting to matplotlib 3d.')
+                plot_mpl = True
+            else:  # 'mayavi'
+                raise
+
+    if plot_mpl:
+        return _plot_mpl_stc(stc, subject=subject, surface=surface, hemi=hemi,
+                             colormap=colormap, time_label=time_label,
+                             smoothing_steps=smoothing_steps,
+                             subjects_dir=subjects_dir, views=views, clim=clim,
+                             figure=figure, initial_time=initial_time,
+                             time_unit=time_unit, background=background,
+                             spacing=spacing, time_viewer=time_viewer)
+    from surfer import Brain, TimeViewer
+    initial_time, ad_kwargs, sd_kwargs = _get_ps_kwargs(initial_time)
 
     if hemi not in ['lh', 'rh', 'split', 'both']:
         raise ValueError('hemi has to be either "lh", "rh", "split", '
@@ -758,6 +1855,7 @@ def plot_source_estimates(stc, subject=None, surface='inflated', hemi='lh',
         if not all(isinstance(f, mayavi.core.scene.Scene) for f in figure):
             raise TypeError('figure must be a mayavi scene or list of scenes')
 
+    time_label, times = _handle_time(time_label, time_unit, stc.times)
     # convert control points to locations in colormap
     ctrl_pts, colormap = _limits_to_control_points(clim, stc.data, colormap)
 
@@ -771,9 +1869,6 @@ def plot_source_estimates(stc, subject=None, surface='inflated', hemi='lh',
         scale_pts = ctrl_pts
         transparent = True if transparent is None else transparent
 
-    subjects_dir = get_subjects_dir(subjects_dir=subjects_dir,
-                                    raise_error=True)
-    subject = _check_subject(stc.subject, subject, True)
     if hemi in ['both', 'split']:
         hemis = ['lh', 'rh']
     else:
@@ -785,12 +1880,7 @@ def plot_source_estimates(stc, subject=None, surface='inflated', hemi='lh',
                       title=title, cortex=cortex, size=size,
                       background=background, foreground=foreground,
                       figure=figure, subjects_dir=subjects_dir,
-                      views=views, config_opts=config_opts)
-
-    if time_unit == 's':
-        times = stc.times
-    else:  # time_unit == 'ms'
-        times = 1e3 * stc.times
+                      views=views)
 
     for hemi in hemis:
         hemi_idx = 0 if hemi == 'lh' else 1
@@ -799,15 +1889,17 @@ def plot_source_estimates(stc, subject=None, surface='inflated', hemi='lh',
         else:
             data = stc.data[len(stc.vertices[0]):]
         vertices = stc.vertices[hemi_idx]
-        with warnings.catch_warnings(record=True):  # traits warnings
-            brain.add_data(data, colormap=colormap, vertices=vertices,
-                           smoothing_steps=smoothing_steps, time=times,
-                           time_label=time_label, alpha=alpha, hemi=hemi,
-                           colorbar=colorbar, **kwargs)
+        if len(data) > 0:
+            with warnings.catch_warnings(record=True):  # traits warnings
+                brain.add_data(data, colormap=colormap, vertices=vertices,
+                               smoothing_steps=smoothing_steps, time=times,
+                               time_label=time_label, alpha=alpha, hemi=hemi,
+                               colorbar=colorbar, min=0, max=1, **ad_kwargs)
 
         # scale colormap and set time (index) to display
         brain.scale_data_colormap(fmin=scale_pts[0], fmid=scale_pts[1],
-                                  fmax=scale_pts[2], transparent=transparent)
+                                  fmax=scale_pts[2], transparent=transparent,
+                                  **sd_kwargs)
 
     if initial_time is not None:
         brain.set_time(initial_time)
@@ -816,6 +1908,205 @@ def plot_source_estimates(stc, subject=None, surface='inflated', hemi='lh',
     return brain
 
 
+def _get_ps_kwargs(initial_time, require='0.6'):
+    """Triage arguments based on PySurfer version."""
+    import surfer
+    surfer_version = LooseVersion(surfer.__version__)
+    if surfer_version < LooseVersion(require):
+        raise ImportError("This function requires PySurfer %s (you are "
+                          "running version %s). You can update PySurfer "
+                          "using:\n\n    $ pip install -U pysurfer" %
+                          (require, surfer.__version__))
+
+    ad_kwargs = dict()
+    sd_kwargs = dict()
+    if initial_time is not None and surfer_version >= LooseVersion('0.7'):
+        ad_kwargs['initial_time'] = initial_time
+        initial_time = None  # don't set it twice
+    if surfer_version >= LooseVersion('0.8'):
+        ad_kwargs['verbose'] = False
+        sd_kwargs['verbose'] = False
+    return initial_time, ad_kwargs, sd_kwargs
+
+
+def plot_vector_source_estimates(stc, subject=None, hemi='lh', colormap='hot',
+                                 time_label='auto', smoothing_steps=10,
+                                 transparent=None, brain_alpha=0.4,
+                                 overlay_alpha=None, vector_alpha=1.0,
+                                 scale_factor=None, time_viewer=False,
+                                 subjects_dir=None, figure=None, views='lat',
+                                 colorbar=True, clim='auto', cortex='classic',
+                                 size=800, background='black',
+                                 foreground='white', initial_time=None,
+                                 time_unit='s'):
+    """Plot VectorSourceEstimates with PySurfer.
+
+    A "glass brain" is drawn and all dipoles defined in the source estimate
+    are shown using arrows, depicting the direction and magnitude of the
+    current moment at the dipole. Additionally, an overlay is plotted on top of
+    the cortex with the magnitude of the current.
+
+    Parameters
+    ----------
+    stc : VectorSourceEstimate
+        The vector source estimate to plot.
+    subject : str | None
+        The subject name corresponding to FreeSurfer environment
+        variable SUBJECT. If None stc.subject will be used. If that
+        is None, the environment will be used.
+    hemi : str, 'lh' | 'rh' | 'split' | 'both'
+        The hemisphere to display.
+    colormap : str | np.ndarray of float, shape(n_colors, 3 | 4)
+        Name of colormap to use or a custom look up table. If array, must
+        be (n x 3) or (n x 4) array for with RGB or RGBA values between
+        0 and 255. Defaults to 'hot'.
+    time_label : str | callable | None
+        Format of the time label (a format string, a function that maps
+        floating point time values to strings, or None for no label). The
+        default is ``time=%0.2f ms``.
+    smoothing_steps : int
+        The amount of smoothing
+    transparent : bool | None
+        If True, use a linear transparency between fmin and fmid.
+        None will choose automatically based on colormap type.
+    brain_alpha : float
+        Alpha value to apply globally to the surface meshes. Defaults to 0.4.
+    overlay_alpha : float
+        Alpha value to apply globally to the overlay. Defaults to
+        ``brain_alpha``.
+    vector_alpha : float
+        Alpha value to apply globally to the vector glyphs. Defaults to 1.
+    scale_factor : float | None
+        Scaling factor for the vector glyphs. By default, an attempt is made to
+        automatically determine a sane value.
+    time_viewer : bool
+        Display time viewer GUI.
+    subjects_dir : str
+        The path to the freesurfer subjects reconstructions.
+        It corresponds to Freesurfer environment variable SUBJECTS_DIR.
+    figure : instance of mayavi.core.scene.Scene | list | int | None
+        If None, a new figure will be created. If multiple views or a
+        split view is requested, this must be a list of the appropriate
+        length. If int is provided it will be used to identify the Mayavi
+        figure by it's id or create a new figure with the given id.
+    views : str | list
+        View to use. See surfer.Brain().
+    colorbar : bool
+        If True, display colorbar on scene.
+    clim : str | dict
+        Colorbar properties specification. If 'auto', set clim automatically
+        based on data percentiles. If dict, should contain:
+
+            ``kind`` : 'value' | 'percent'
+                Flag to specify type of limits.
+            ``lims`` : list | np.ndarray | tuple of float, 3 elements
+                Note: Only use this if 'colormap' is not 'mne'.
+                Left, middle, and right bound for colormap.
+            ``pos_lims`` : list | np.ndarray | tuple of float, 3 elements
+                Note: Only use this if 'colormap' is 'mne'.
+                Left, middle, and right bound for colormap. Positive values
+                will be mirrored directly across zero during colormap
+                construction to obtain negative control points.
+
+    cortex : str or tuple
+        specifies how binarized curvature values are rendered.
+        either the name of a preset PySurfer cortex colorscheme (one of
+        'classic', 'bone', 'low_contrast', or 'high_contrast'), or the
+        name of mayavi colormap, or a tuple with values (colormap, min,
+        max, reverse) to fully specify the curvature colors.
+    size : float or pair of floats
+        The size of the window, in pixels. can be one number to specify
+        a square window, or the (width, height) of a rectangular window.
+    background : matplotlib color
+        Color of the background of the display window.
+    foreground : matplotlib color
+        Color of the foreground of the display window.
+    initial_time : float | None
+        The time to display on the plot initially. ``None`` to display the
+        first time sample (default).
+    time_unit : 's' | 'ms'
+        Whether time is represented in seconds ("s", default) or
+        milliseconds ("ms").
+
+    Returns
+    -------
+    brain : Brain
+        A instance of :class:`surfer.Brain` from PySurfer.
+
+    Notes
+    -----
+    .. versionadded:: 0.15
+
+    If the current magnitude overlay is not desired, set ``overlay_alpha=0``
+    and ``smoothing_steps=1``.
+    """
+    # Import here to avoid circular imports
+    from surfer import Brain, TimeViewer
+    from ..source_estimate import VectorSourceEstimate
+
+    if not isinstance(stc, VectorSourceEstimate):
+        raise ValueError('stc has to be a vector source estimate')
+
+    subjects_dir = get_subjects_dir(subjects_dir=subjects_dir,
+                                    raise_error=True)
+    subject = _check_subject(stc.subject, subject, True)
+    initial_time, ad_kwargs, sd_kwargs = _get_ps_kwargs(initial_time, '0.8')
+
+    if hemi not in ['lh', 'rh', 'split', 'both']:
+        raise ValueError('hemi has to be either "lh", "rh", "split", '
+                         'or "both"')
+
+    time_label, times = _handle_time(time_label, time_unit, stc.times)
+
+    # convert control points to locations in colormap
+    scale_pts, colormap = _limits_to_control_points(clim, stc.data, colormap)
+    transparent = True if transparent is None else transparent
+
+    if hemi in ['both', 'split']:
+        hemis = ['lh', 'rh']
+    else:
+        hemis = [hemi]
+
+    if overlay_alpha is None:
+        overlay_alpha = brain_alpha
+    if overlay_alpha == 0:
+        smoothing_steps = 1  # Disable smoothing to save time.
+
+    title = subject if len(hemis) > 1 else '%s - %s' % (subject, hemis[0])
+    with warnings.catch_warnings(record=True):  # traits warnings
+        brain = Brain(subject, hemi=hemi, surf='white', curv=True,
+                      title=title, cortex=cortex, size=size,
+                      background=background, foreground=foreground,
+                      figure=figure, subjects_dir=subjects_dir,
+                      views=views, alpha=brain_alpha)
+
+    for hemi in hemis:
+        hemi_idx = 0 if hemi == 'lh' else 1
+        if hemi_idx == 0:
+            data = stc.data[:len(stc.vertices[0])]
+        else:
+            data = stc.data[len(stc.vertices[0]):]
+        vertices = stc.vertices[hemi_idx]
+        if len(data) > 0:
+            with warnings.catch_warnings(record=True):  # traits warnings
+                brain.add_data(data, colormap=colormap, vertices=vertices,
+                               smoothing_steps=smoothing_steps, time=times,
+                               time_label=time_label, alpha=overlay_alpha,
+                               hemi=hemi, colorbar=colorbar,
+                               vector_alpha=vector_alpha,
+                               scale_factor=scale_factor, **ad_kwargs)
+
+        # scale colormap and set time (index) to display
+        brain.scale_data_colormap(fmin=scale_pts[0], fmid=scale_pts[1],
+                                  fmax=scale_pts[2], transparent=transparent,
+                                  **sd_kwargs)
+
+    if time_viewer:
+        TimeViewer(brain)
+
+    return brain
+
+
 def plot_sparse_source_estimates(src, stcs, colors=None, linewidth=2,
                                  fontsize=18, bgcolor=(.05, 0, .1),
                                  opacity=0.2, brain_color=(0.7,) * 3,
@@ -824,7 +2115,7 @@ def plot_sparse_source_estimates(src, stcs, colors=None, linewidth=2,
                                  modes=('cone', 'sphere'),
                                  scale_factors=(1, 0.6),
                                  verbose=None, **kwargs):
-    """Plot source estimates obtained with sparse solver
+    """Plot source estimates obtained with sparse solver.
 
     Active dipoles are represented in a "Glass" brain.
     If the same source is active in multiple source estimates it is
@@ -867,10 +2158,20 @@ def plot_sparse_source_estimates(src, stcs, colors=None, linewidth=2,
     scale_factors : list
         List of floating point scale factors for the markers.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
     **kwargs : kwargs
         Keyword arguments to pass to mlab.triangular_mesh.
+
+    Returns
+    -------
+    surface : instance of mlab Surface
+        The triangular mesh surface.
     """
+    mlab = _import_mlab()
+    import matplotlib.pyplot as plt
+    from matplotlib.colors import ColorConverter
+
     known_modes = ['cone', 'sphere']
     if not isinstance(modes, (list, tuple)) or \
             not all(mode in known_modes for mode in modes):
@@ -910,24 +2211,22 @@ def plot_sparse_source_estimates(src, stcs, colors=None, linewidth=2,
                for stc in stcs]
     unique_vertnos = np.unique(np.concatenate(vertnos).ravel())
 
-    from mayavi import mlab
-    from matplotlib.colors import ColorConverter
     color_converter = ColorConverter()
 
     f = mlab.figure(figure=fig_name, bgcolor=bgcolor, size=(600, 600))
     mlab.clf()
-    if mlab.options.backend != 'test':
-        f.scene.disable_render = True
+    _toggle_mlab_render(f, False)
     with warnings.catch_warnings(record=True):  # traits warnings
         surface = mlab.triangular_mesh(points[:, 0], points[:, 1],
                                        points[:, 2], use_faces,
                                        color=brain_color,
                                        opacity=opacity, **kwargs)
+    surface.actor.property.backface_culling = True
 
-    import matplotlib.pyplot as plt
     # Show time courses
-    plt.figure(fig_number)
-    plt.clf()
+    fig = plt.figure(fig_number)
+    fig.clf()
+    ax = fig.add_subplot(111)
 
     colors = cycle(colors)
 
@@ -966,30 +2265,38 @@ def plot_sparse_source_estimates(src, stcs, colors=None, linewidth=2,
             mask = (vertno == v)
             assert np.sum(mask) == 1
             linestyle = linestyles[k]
-            plt.plot(1e3 * stcs[k].times, 1e9 * stcs[k].data[mask].ravel(),
-                     c=c, linewidth=linewidth, linestyle=linestyle)
+            ax.plot(1e3 * stcs[k].times, 1e9 * stcs[k].data[mask].ravel(),
+                    c=c, linewidth=linewidth, linestyle=linestyle)
 
-    plt.xlabel('Time (ms)', fontsize=18)
-    plt.ylabel('Source amplitude (nAm)', fontsize=18)
+    ax.set_xlabel('Time (ms)', fontsize=18)
+    ax.set_ylabel('Source amplitude (nAm)', fontsize=18)
 
     if fig_name is not None:
-        plt.title(fig_name)
+        ax.set_title(fig_name)
     plt_show(show)
 
     surface.actor.property.backface_culling = True
     surface.actor.property.shading = True
-
+    _toggle_mlab_render(f, True)
     return surface
 
 
+def _toggle_mlab_render(fig, render):
+    mlab = _import_mlab()
+    if mlab.options.backend != 'test':
+        fig.scene.disable_render = not render
+
+
 def plot_dipole_locations(dipoles, trans, subject, subjects_dir=None,
-                          bgcolor=(1, 1, 1), opacity=0.3,
-                          brain_color=(1, 1, 0), fig_name=None,
-                          fig_size=(600, 600), mode='cone',
-                          scale_factor=0.1e-1, colors=None, verbose=None):
-    """Plot dipole locations
+                          mode='orthoview', coord_frame='mri', idx='gof',
+                          show_all=True, ax=None, block=False,
+                          show=True, verbose=None):
+    """Plot dipole locations.
+
+    If mode is set to 'cone' or 'sphere', only the location of the first
+    time point of each dipole is shown else use the show_all parameter.
 
-    Only the location of the first time point of each dipole is shown.
+    The option mode='orthoview' was added in version 0.14.
 
     Parameters
     ----------
@@ -1004,72 +2311,368 @@ def plot_dipole_locations(dipoles, trans, subject, subjects_dir=None,
         The path to the freesurfer subjects reconstructions.
         It corresponds to Freesurfer environment variable SUBJECTS_DIR.
         The default is None.
-    bgcolor : tuple of length 3
-        Background color in 3D.
-    opacity : float in [0, 1]
-        Opacity of brain mesh.
-    brain_color : tuple of length 3
-        Brain color.
-    fig_name : str
-        Mayavi figure name.
-    fig_size : tuple of length 2
-        Mayavi figure size.
     mode : str
-        Should be ``'cone'`` or ``'sphere'`` to specify how the
-        dipoles should be shown.
-    scale_factor : float
-        The scaling applied to amplitudes for the plot.
-    colors: list of colors | None
-        Color to plot with each dipole. If None default colors are used.
+        Currently only ``'orthoview'`` is supported.
+
+        .. versionadded:: 0.14.0
+    coord_frame : str
+        Coordinate frame to use, 'head' or 'mri'. Defaults to 'mri'.
+
+        .. versionadded:: 0.14.0
+    idx : int | 'gof' | 'amplitude'
+        Index of the initially plotted dipole. Can also be 'gof' to plot the
+        dipole with highest goodness of fit value or 'amplitude' to plot the
+        dipole with the highest amplitude. The dipoles can also be browsed
+        through using up/down arrow keys or mouse scroll. Defaults to 'gof'.
+        Only used if mode equals 'orthoview'.
+
+        .. versionadded:: 0.14.0
+    show_all : bool
+        Whether to always plot all the dipoles. If True (default), the active
+        dipole is plotted as a red dot and it's location determines the shown
+        MRI slices. The the non-active dipoles are plotted as small blue dots.
+        If False, only the active dipole is plotted.
+        Only used if mode equals 'orthoview'.
+
+        .. versionadded:: 0.14.0
+    ax : instance of matplotlib Axes3D | None
+        Axes to plot into. If None (default), axes will be created.
+        Only used if mode equals 'orthoview'.
+
+        .. versionadded:: 0.14.0
+    block : bool
+        Whether to halt program execution until the figure is closed. Defaults
+        to False.
+        Only used if mode equals 'orthoview'.
+
+        .. versionadded:: 0.14.0
+    show : bool
+        Show figure if True. Defaults to True.
+        Only used if mode equals 'orthoview'.
+
+        .. versionadded:: 0.14.0
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
-    fig : instance of mlab.Figure
-        The mayavi figure.
+    fig : instance of mlab.Figure or matplotlib Figure
+        The mayavi figure or matplotlib Figure.
 
     Notes
     -----
     .. versionadded:: 0.9.0
     """
-    from mayavi import mlab
-    from matplotlib.colors import ColorConverter
-    color_converter = ColorConverter()
+    if mode == 'orthoview':
+        fig = _plot_dipole_mri_orthoview(
+            dipoles, trans=trans, subject=subject, subjects_dir=subjects_dir,
+            coord_frame=coord_frame, idx=idx, show_all=show_all,
+            ax=ax, block=block, show=show)
+    else:
+        raise ValueError('Mode must be "orthoview", got %s.' % (mode,))
+
+    return fig
+
+
+def snapshot_brain_montage(fig, montage, hide_sensors=True):
+    """Take a snapshot of a Mayavi Scene and project channels onto 2d coords.
+
+    Note that this will take the raw values for 3d coordinates of each channel,
+    without applying any transforms. If brain images are flipped up/dn upon
+    using `imshow`, check your matplotlib backend as this behavior changes.
+
+    Parameters
+    ----------
+    fig : instance of Mayavi Scene
+        The figure on which you've plotted electrodes using
+        :func:`mne.viz.plot_alignment`.
+    montage : instance of `DigMontage` or `Info` | dict of ch: xyz mappings.
+        The digital montage for the electrodes plotted in the scene. If `Info`,
+        channel positions will be pulled from the `loc` field of `chs`.
+    hide_sensors : bool
+        Whether to remove the spheres in the scene before taking a snapshot.
+
+    Returns
+    -------
+    xy : array, shape (n_channels, 2)
+        The 2d location of each channel on the image of the current scene view.
+    im : array, shape (m, n, 3)
+        The screenshot of the current scene view
+    """
+    mlab = _import_mlab()
+    from ..channels import Montage, DigMontage
+    from .. import Info
+    if isinstance(montage, (Montage, DigMontage)):
+        chs = montage.dig_ch_pos
+        ch_names, xyz = zip(*[(ich, ixyz) for ich, ixyz in chs.items()])
+    elif isinstance(montage, Info):
+        xyz = [ich['loc'][:3] for ich in montage['chs']]
+        ch_names = [ich['ch_name'] for ich in montage['chs']]
+    elif isinstance(montage, dict):
+        if not all(len(ii) == 3 for ii in montage.values()):
+            raise ValueError('All electrode positions must be length 3')
+        ch_names, xyz = zip(*[(ich, ixyz) for ich, ixyz in montage.items()])
+    else:
+        raise ValueError('montage must be an instance of `DigMontage`, `Info`,'
+                         ' or `dict`')
+
+    xyz = np.vstack(xyz)
+    xy = _3d_to_2d(fig, xyz)
+    xy = dict(zip(ch_names, xy))
+    pts = fig.children[-1]
+
+    if hide_sensors is True:
+        pts.visible = False
+    with warnings.catch_warnings(record=True):
+        im = mlab.screenshot(fig)
+    pts.visible = True
+    return xy, im
+
+
+def _3d_to_2d(fig, xyz):
+    """Convert 3d points to a 2d perspective using a Mayavi Scene."""
+    from mayavi.core.scene import Scene
+
+    if not isinstance(fig, Scene):
+        raise TypeError('fig must be an instance of Scene, '
+                        'found type %s' % type(fig))
+    xyz = np.column_stack([xyz, np.ones(xyz.shape[0])])
+
+    # Transform points into 'unnormalized' view coordinates
+    comb_trans_mat = _get_world_to_view_matrix(fig.scene)
+    view_coords = np.dot(comb_trans_mat, xyz.T).T
+
+    # Divide through by the fourth element for normalized view coords
+    norm_view_coords = view_coords / (view_coords[:, 3].reshape(-1, 1))
+
+    # Transform from normalized view coordinates to display coordinates.
+    view_to_disp_mat = _get_view_to_display_matrix(fig.scene)
+    xy = np.dot(view_to_disp_mat, norm_view_coords.T).T
+
+    # Pull the first two columns since they're meaningful for 2d plotting
+    xy = xy[:, :2]
+    return xy
+
+
+def _get_world_to_view_matrix(scene):
+    """Return the 4x4 matrix to transform xyz space to the current view.
+
+    This is a concatenation of the model view and perspective transforms.
+    """
+    from mayavi.core.ui.mayavi_scene import MayaviScene
+    from tvtk.pyface.tvtk_scene import TVTKScene
+
+    if not isinstance(scene, (MayaviScene, TVTKScene)):
+        raise TypeError('scene must be an instance of TVTKScene/MayaviScene, '
+                        'found type %s' % type(scene))
+    cam = scene.camera
+
+    # The VTK method needs the aspect ratio and near and far
+    # clipping planes in order to return the proper transform.
+    scene_size = tuple(scene.get_size())
+    clip_range = cam.clipping_range
+    aspect_ratio = float(scene_size[0]) / scene_size[1]
+
+    # Get the vtk matrix object using the aspect ratio we defined
+    vtk_comb_trans_mat = cam.get_composite_projection_transform_matrix(
+        aspect_ratio, clip_range[0], clip_range[1])
+    vtk_comb_trans_mat = vtk_comb_trans_mat.to_array()
+    return vtk_comb_trans_mat
+
+
+def _get_view_to_display_matrix(scene):
+    """Return the 4x4 matrix to convert view coordinates to display coordinates.
+
+    It's assumed that the view should take up the entire window and that the
+    origin of the window is in the upper left corner.
+    """
+    from mayavi.core.ui.mayavi_scene import MayaviScene
+    from tvtk.pyface.tvtk_scene import TVTKScene
+
+    if not isinstance(scene, (MayaviScene, TVTKScene)):
+        raise TypeError('scene must be an instance of TVTKScene/MayaviScene, '
+                        'found type %s' % type(scene))
+
+    # normalized view coordinates have the origin in the middle of the space
+    # so we need to scale by width and height of the display window and shift
+    # by half width and half height. The matrix accomplishes that.
+    x, y = tuple(scene.get_size())
+    view_to_disp_mat = np.array([[x / 2.0,       0.,   0.,   x / 2.0],
+                                 [0.,      -y / 2.0,   0.,   y / 2.0],
+                                 [0.,            0.,   1.,        0.],
+                                 [0.,            0.,   0.,        1.]])
+    return view_to_disp_mat
+
+
+def _plot_dipole_mri_orthoview(dipole, trans, subject, subjects_dir=None,
+                               coord_frame='head', idx='gof', show_all=True,
+                               ax=None, block=False, show=True):
+    """Plot dipoles on top of MRI slices in 3-D."""
+    import matplotlib.pyplot as plt
+    from mpl_toolkits.mplot3d import Axes3D
+    from .. import Dipole
+    if not has_nibabel():
+        raise ImportError('This function requires nibabel.')
+    import nibabel as nib
+    from nibabel.processing import resample_from_to
+
+    if coord_frame not in ['head', 'mri']:
+        raise ValueError("coord_frame must be 'head' or 'mri'. "
+                         "Got %s." % coord_frame)
+
+    if not isinstance(dipole, Dipole):
+        from ..dipole import _concatenate_dipoles
+        dipole = _concatenate_dipoles(dipole)
+    if idx == 'gof':
+        idx = np.argmax(dipole.gof)
+    elif idx == 'amplitude':
+        idx = np.argmax(np.abs(dipole.amplitude))
+    else:
+        idx = _ensure_int(idx, 'idx', 'an int or one of ["gof", "amplitude"]')
 
-    trans = _get_trans(trans)[0]
     subjects_dir = get_subjects_dir(subjects_dir=subjects_dir,
                                     raise_error=True)
-    fname = op.join(subjects_dir, subject, 'bem', 'inner_skull.surf')
-    points, faces = read_surface(fname)
-    points = apply_trans(trans['trans'], points * 1e-3)
+    t1_fname = op.join(subjects_dir, subject, 'mri', 'T1.mgz')
+    t1 = nib.load(t1_fname)
+    vox2ras = t1.header.get_vox2ras_tkr()
+    ras2vox = linalg.inv(vox2ras)
+    trans = _get_trans(trans, fro='head', to='mri')[0]
+    zooms = t1.header.get_zooms()
+    if coord_frame == 'head':
+        affine_to = trans['trans'].copy()
+        affine_to[:3, 3] *= 1000  # to mm
+        aff = t1.affine.copy()
+
+        aff[:3, :3] /= zooms
+        affine_to = np.dot(affine_to, aff)
+        t1 = resample_from_to(t1, ([int(t1.shape[i] * zooms[i]) for i
+                                    in range(3)], affine_to))
+        dipole_locs = apply_trans(ras2vox, dipole.pos * 1e3) * zooms
+
+        ori = dipole.ori
+        scatter_points = dipole.pos * 1e3
+    else:
+        scatter_points = apply_trans(trans['trans'], dipole.pos) * 1e3
+        ori = apply_trans(trans['trans'], dipole.ori, move=False)
+        dipole_locs = apply_trans(ras2vox, scatter_points)
+
+    data = t1.get_data()
+    dims = len(data)  # Symmetric size assumed.
+    dd = dims / 2.
+    dd *= t1.header.get_zooms()[0]
+    if ax is None:
+        fig = plt.figure()
+        ax = Axes3D(fig)
+    elif not isinstance(ax, Axes3D):
+        raise ValueError('ax must be an instance of Axes3D. '
+                         'Got %s.' % type(ax))
+    else:
+        fig = ax.get_figure()
 
-    from .. import Dipole
-    if isinstance(dipoles, Dipole):
-        dipoles = [dipoles]
+    gridx, gridy = np.meshgrid(np.linspace(-dd, dd, dims),
+                               np.linspace(-dd, dd, dims))
 
-    if mode not in ['cone', 'sphere']:
-        raise ValueError('mode must be in "cone" or "sphere"')
+    _plot_dipole(ax, data, dipole_locs, idx, dipole, gridx, gridy, ori,
+                 coord_frame, zooms, show_all, scatter_points)
+    params = {'ax': ax, 'data': data, 'idx': idx, 'dipole': dipole,
+              'dipole_locs': dipole_locs, 'gridx': gridx, 'gridy': gridy,
+              'ori': ori, 'coord_frame': coord_frame, 'zooms': zooms,
+              'show_all': show_all, 'scatter_points': scatter_points}
+    ax.view_init(elev=30, azim=-140)
 
-    if colors is None:
-        colors = cycle(COLORS)
-
-    fig = mlab.figure(size=fig_size, bgcolor=bgcolor, fgcolor=(0, 0, 0))
-    with warnings.catch_warnings(record=True):  # FutureWarning in traits
-        mlab.triangular_mesh(points[:, 0], points[:, 1], points[:, 2],
-                             faces, color=brain_color, opacity=opacity)
-
-    for dip, color in zip(dipoles, colors):
-        rgb_color = color_converter.to_rgb(color)
-        with warnings.catch_warnings(record=True):  # FutureWarning in traits
-            mlab.quiver3d(dip.pos[0, 0], dip.pos[0, 1], dip.pos[0, 2],
-                          dip.ori[0, 0], dip.ori[0, 1], dip.ori[0, 2],
-                          opacity=1., mode=mode, color=rgb_color,
-                          scalars=dip.amplitude.max(),
-                          scale_factor=scale_factor)
-    if fig_name is not None:
-        mlab.title(fig_name)
-    if fig.scene is not None:  # safe for Travis
-        fig.scene.x_plus_view()
+    callback_func = partial(_dipole_changed, params=params)
+    fig.canvas.mpl_connect('scroll_event', callback_func)
+    fig.canvas.mpl_connect('key_press_event', callback_func)
 
+    plt_show(show, block=block)
     return fig
+
+
+def _plot_dipole(ax, data, points, idx, dipole, gridx, gridy, ori, coord_frame,
+                 zooms, show_all, scatter_points):
+    """Plot dipoles."""
+    import matplotlib.pyplot as plt
+    point = points[idx]
+    xidx, yidx, zidx = np.round(point).astype(int)
+    xslice = data[xidx][::-1]
+    yslice = data[:, yidx][::-1].T
+    zslice = data[:, :, zidx][::-1].T[::-1]
+    if coord_frame == 'head':
+        zooms = (1., 1., 1.)
+    else:
+        point = points[idx] * zooms
+        xidx, yidx, zidx = np.round(point).astype(int)
+    xyz = scatter_points
+
+    ori = ori[idx]
+    if show_all:
+        colors = np.repeat('y', len(points))
+        colors[idx] = 'r'
+        size = np.repeat(5, len(points))
+        size[idx] = 20
+        visibles = range(len(points))
+    else:
+        colors = 'r'
+        size = 20
+        visibles = idx
+
+    offset = np.min(gridx)
+    ax.scatter(xs=xyz[visibles, 0], ys=xyz[visibles, 1],
+               zs=xyz[visibles, 2], zorder=2, s=size, facecolor=colors)
+    xx = np.linspace(offset, xyz[idx, 0], xidx)
+    yy = np.linspace(offset, xyz[idx, 1], yidx)
+    zz = np.linspace(offset, xyz[idx, 2], zidx)
+    ax.plot(xx, np.repeat(xyz[idx, 1], len(xx)), zs=xyz[idx, 2], zorder=1,
+            linestyle='-', color='r')
+    ax.plot(np.repeat(xyz[idx, 0], len(yy)), yy, zs=xyz[idx, 2], zorder=1,
+            linestyle='-', color='r')
+    ax.plot(np.repeat(xyz[idx, 0], len(zz)),
+            np.repeat(xyz[idx, 1], len(zz)), zs=zz, zorder=1,
+            linestyle='-', color='r')
+    kwargs = _pivot_kwargs()
+    ax.quiver(xyz[idx, 0], xyz[idx, 1], xyz[idx, 2], ori[0], ori[1],
+              ori[2], length=50, color='r', **kwargs)
+    dims = np.array([(len(data) / -2.), (len(data) / 2.)])
+    ax.set_xlim(-1 * dims * zooms[:2])  # Set axis lims to RAS coordinates.
+    ax.set_ylim(-1 * dims * zooms[:2])
+    ax.set_zlim(dims * zooms[:2])
+
+    # Plot slices.
+    ax.contourf(xslice, gridx, gridy, offset=offset, zdir='x',
+                cmap='gray', zorder=0, alpha=.5)
+    ax.contourf(gridx, gridy, yslice, offset=offset, zdir='z',
+                cmap='gray', zorder=0, alpha=.5)
+    ax.contourf(gridx, zslice, gridy, offset=offset,
+                zdir='y', cmap='gray', zorder=0, alpha=.5)
+
+    plt.suptitle('Dipole #%s / %s @ %.3fs, GOF: %.1f%%, %.1fnAm\n' % (
+        idx + 1, len(dipole.times), dipole.times[idx], dipole.gof[idx],
+        dipole.amplitude[idx] * 1e9) +
+        '(%0.1f, %0.1f, %0.1f) mm' % tuple(xyz[idx]))
+    ax.set_xlabel('x')
+    ax.set_ylabel('y')
+    ax.set_zlabel('z')
+
+    plt.draw()
+
+
+def _dipole_changed(event, params):
+    """Handle dipole plotter scroll/key event."""
+    if event.key is not None:
+        if event.key == 'up':
+            params['idx'] += 1
+        elif event.key == 'down':
+            params['idx'] -= 1
+        else:  # some other key
+            return
+    elif event.step > 0:  # scroll event
+        params['idx'] += 1
+    else:
+        params['idx'] -= 1
+    params['idx'] = min(max(0, params['idx']), len(params['dipole'].pos) - 1)
+    params['ax'].clear()
+    _plot_dipole(params['ax'], params['data'], params['dipole_locs'],
+                 params['idx'], params['dipole'], params['gridx'],
+                 params['gridy'], params['ori'], params['coord_frame'],
+                 params['zooms'], params['show_all'], params['scatter_points'])
diff --git a/mne/viz/__init__.py b/mne/viz/__init__.py
index e8cb319..53ed4e8 100644
--- a/mne/viz/__init__.py
+++ b/mne/viz/__init__.py
@@ -1,5 +1,4 @@
-"""Visualization routines
-"""
+"""Visualization routines."""
 
 from .topomap import (plot_evoked_topomap, plot_projs_topomap,
                       plot_ica_components, plot_tfr_topomap, plot_topomap,
@@ -8,9 +7,12 @@ from .topo import plot_topo_image_epochs, iter_topography
 from .utils import (tight_layout, mne_analyze_colormap, compare_fiff,
                     ClickableImage, add_background_image, plot_sensors)
 from ._3d import (plot_sparse_source_estimates, plot_source_estimates,
-                  plot_trans, plot_evoked_field, plot_dipole_locations)
+                  plot_vector_source_estimates, plot_trans, plot_evoked_field,
+                  plot_dipole_locations, snapshot_brain_montage,
+                  plot_head_positions, plot_alignment)
 from .misc import (plot_cov, plot_bem, plot_events, plot_source_spectrogram,
-                   _get_presser, plot_dipole_amplitudes)
+                   _get_presser, plot_dipole_amplitudes, plot_ideal_filter,
+                   plot_filter, adjust_axes)
 from .evoked import (plot_evoked, plot_evoked_image, plot_evoked_white,
                      plot_snr_estimate, plot_evoked_topo,
                      plot_evoked_joint, plot_compare_evokeds)
diff --git a/mne/viz/circle.py b/mne/viz/circle.py
index f7362b4..fc34b89 100644
--- a/mne/viz/circle.py
+++ b/mne/viz/circle.py
@@ -1,5 +1,5 @@
-"""Functions to plot on circle as for connectivity
-"""
+"""Functions to plot on circle as for connectivity."""
+
 from __future__ import print_function
 
 # Authors: Alexandre Gramfort <alexandre.gramfort at telecom-paristech.fr>
@@ -14,7 +14,7 @@ from functools import partial
 
 import numpy as np
 
-from .utils import plt_show
+from .utils import plt_show, _set_ax_facecolor
 from ..externals.six import string_types
 
 
@@ -94,9 +94,10 @@ def circular_layout(node_names, node_order, start_pos=90, start_between=True,
 def _plot_connectivity_circle_onpick(event, fig=None, axes=None, indices=None,
                                      n_nodes=0, node_angles=None,
                                      ylim=[9, 10]):
-    """Isolates connections around a single node when user left clicks a node.
+    """Isolate connections around a single node when user left clicks a node.
 
-    On right click, resets all connections."""
+    On right click, resets all connections.
+    """
     if event.inaxes != axes:
         return
 
@@ -268,7 +269,8 @@ def plot_connectivity_circle(con, node_names, indices=None, n_lines=None,
     # Use a polar axes
     if not isinstance(subplot, tuple):
         subplot = (subplot,)
-    axes = plt.subplot(*subplot, polar=True, axisbg=facecolor)
+    axes = plt.subplot(*subplot, polar=True)
+    _set_ax_facecolor(axes, facecolor)
 
     # No ticks, we'll put our own
     plt.xticks([])
diff --git a/mne/viz/decoding.py b/mne/viz/decoding.py
index 88e07b3..921a776 100644
--- a/mne/viz/decoding.py
+++ b/mne/viz/decoding.py
@@ -1,5 +1,4 @@
-"""Functions to plot decoding results
-"""
+"""Functions to plot decoding results."""
 from __future__ import print_function
 
 # Authors: Denis Engemann <denis.engemann at gmail.com>
@@ -11,13 +10,14 @@ from __future__ import print_function
 import numpy as np
 
 from .utils import plt_show
-from ..utils import warn
+from ..utils import warn, deprecated
 
 
+ at deprecated('plot_gat_matrix is deprecated and will be removed in 0.15.')
 def plot_gat_matrix(gat, title=None, vmin=None, vmax=None, tlim=None,
                     ax=None, cmap='RdBu_r', show=True, colorbar=True,
                     xlabel=True, ylabel=True):
-    """Plotting function of GeneralizationAcrossTime object
+    """Plot a GeneralizationAcrossTime object.
 
     Predict each classifier. If multiple classifiers are passed, average
     prediction across all classifier to result in a single prediction per
@@ -65,9 +65,9 @@ def plot_gat_matrix(gat, title=None, vmin=None, vmax=None, tlim=None,
 
     # Define time limits
     if tlim is None:
-        tt_times = gat.train_times_['times']
-        tn_times = gat.test_times_['times']
-        tlim = [tn_times[0][0], tn_times[-1][-1], tt_times[0], tt_times[-1]]
+        tn_times = gat.train_times_['times']
+        tt_times = gat.test_times_['times']
+        tlim = [tt_times[0][0], tt_times[-1][-1], tn_times[0], tn_times[-1]]
 
     # Plot scores
     im = ax.imshow(gat.scores_, interpolation='nearest', origin='lower',
@@ -88,11 +88,12 @@ def plot_gat_matrix(gat, title=None, vmin=None, vmax=None, tlim=None,
     return fig if ax is None else ax.get_figure()
 
 
+ at deprecated('plot_gat_matrix is deprecated and will be removed in 0.15.')
 def plot_gat_times(gat, train_time='diagonal', title=None, xmin=None,
                    xmax=None, ymin=None, ymax=None, ax=None, show=True,
                    color=None, xlabel=True, ylabel=True, legend=True,
                    chance=True, label='Classif. score'):
-    """Plotting function of GeneralizationAcrossTime object
+    """Plot the GeneralizationAcrossTime results.
 
     Plot the scores of the classifier trained at 'train_time'.
 
@@ -190,9 +191,7 @@ def plot_gat_times(gat, train_time='diagonal', title=None, xmin=None,
 
 
 def _plot_gat_time(gat, train_time, ax, color, label):
-    """Aux function of plot_gat_time
-
-    Plots a unique score 1d array"""
+    """Plot a unique score 1d array."""
     # Detect whether gat is a full matrix or just its diagonal
     if np.all(np.unique([len(t) for t in gat.test_times_['times']]) == 1):
         scores = gat.scores_
@@ -226,6 +225,7 @@ def _plot_gat_time(gat, train_time, ax, color, label):
 
 
 def _get_chance_level(scorer, y_train):
+    """Get the chance level."""
     # XXX JRK This should probably be solved within sklearn?
     if scorer.__name__ == 'accuracy_score':
         chance = np.max([np.mean(y_train == c) for c in np.unique(y_train)])
diff --git a/mne/viz/epochs.py b/mne/viz/epochs.py
index be4ffde..d0c1075 100644
--- a/mne/viz/epochs.py
+++ b/mne/viz/epochs.py
@@ -1,11 +1,11 @@
-"""Functions to plot epochs data
-"""
+"""Functions to plot epochs data."""
 
 # Authors: Alexandre Gramfort <alexandre.gramfort at telecom-paristech.fr>
 #          Denis Engemann <denis.engemann at gmail.com>
 #          Martin Luessi <mluessi at nmr.mgh.harvard.edu>
 #          Eric Larson <larson.eric.d at gmail.com>
 #          Jaakko Leppakangas <jaeilepp at student.jyu.fi>
+#          Jona Sassenhagen <jona.sassenhagen at gmail.com>
 #
 # License: Simplified BSD
 
@@ -22,32 +22,44 @@ from ..time_frequency import psd_multitaper
 from .utils import (tight_layout, figure_nobar, _toggle_proj, _toggle_options,
                     _layout_figure, _setup_vmin_vmax, _channels_changed,
                     _plot_raw_onscroll, _onclick_help, plt_show,
-                    _compute_scalings, DraggableColorbar)
+                    _compute_scalings, DraggableColorbar, _setup_cmap,
+                    _grad_pair_pick_and_name, _handle_decim)
+from .misc import _handle_event_colors
 from ..defaults import _handle_default
+from ..externals.six import string_types
 
 
 def plot_epochs_image(epochs, picks=None, sigma=0., vmin=None,
                       vmax=None, colorbar=True, order=None, show=True,
-                      units=None, scalings=None, cmap='RdBu_r',
-                      fig=None, axes=None, overlay_times=None):
-    """Plot Event Related Potential / Fields image
+                      units=None, scalings=None, cmap=None, fig=None,
+                      axes=None, overlay_times=None, combine=None,
+                      group_by=None, evoked=True, ts_args=dict(), title=None):
+    """Plot Event Related Potential / Fields image.
 
     Parameters
     ----------
     epochs : instance of Epochs
         The epochs.
     picks : int | array-like of int | None
-        The indices of the channels to consider. If None, the first
-        five good channels are plotted.
+        The indices of the channels to consider. If None and ``combine`` is
+        also None, the first five good channels are plotted.
     sigma : float
         The standard deviation of the Gaussian smoothing to apply along
         the epoch axis to apply in the image. If 0., no smoothing is applied.
-    vmin : float
-        The min value in the image. The unit is uV for EEG channels,
-        fT for magnetometers and fT/cm for gradiometers.
-    vmax : float
-        The max value in the image. The unit is uV for EEG channels,
-        fT for magnetometers and fT/cm for gradiometers.
+        Defaults to 0.
+    vmin : None | float | callable
+        The min value in the image (and the ER[P/F]). The unit is uV for
+        EEG channels, fT for magnetometers and fT/cm for gradiometers.
+        If vmin is None and multiple plots are returned, the limit is
+        equalized within channel types.
+        Hint: to specify the lower limit of the data, use
+        ``vmin=lambda data: data.min()``.
+
+    vmax : None | float | callable
+        The max value in the image (and the ER[P/F]). The unit is uV for
+        EEG channels, fT for magnetometers and fT/cm for gradiometers.
+        If vmin is None and multiple plots are returned, the limit is
+        equalized within channel types.
     colorbar : bool
         Display or not a colorbar.
     order : None | array of int | callable
@@ -65,74 +77,320 @@ def plot_epochs_image(epochs, picks=None, sigma=0., vmin=None,
         The scalings of the channel types to be applied for plotting.
         If None, defaults to `scalings=dict(eeg=1e6, grad=1e13, mag=1e15,
         eog=1e6)`.
-    cmap : matplotlib colormap | (colormap, bool) | 'interactive'
+    cmap : None | matplotlib colormap | (colormap, bool) | 'interactive'
         Colormap. If tuple, the first value indicates the colormap to use and
         the second value is a boolean defining interactivity. In interactive
         mode the colors are adjustable by clicking and dragging the colorbar
         with left and right mouse button. Left mouse button moves the scale up
         and down and right mouse button adjusts the range. Hitting space bar
         resets the scale. Up and down arrows can be used to change the
-        colormap. If 'interactive', translates to ('RdBu_r', True). Defaults to
-        'RdBu_r'.
+        colormap. If 'interactive', translates to ('RdBu_r', True).
+        If None, "RdBu_r" is used, unless the data is all positive, in which
+        case "Reds" is used.
     fig : matplotlib figure | None
         Figure instance to draw the image to. Figure must contain two axes for
         drawing the single trials and evoked responses. If None a new figure is
         created. Defaults to None.
-    axes : list of matplotlib axes | None
+    axes : list of matplotlib axes | dict of lists of matplotlib Axes | None
         List of axes instances to draw the image, erp and colorbar to.
         Must be of length three if colorbar is True (with the last list element
         being the colorbar axes) or two if colorbar is False. If both fig and
-        axes are passed an error is raised. Defaults to None.
+        axes are passed, an error is raised.
+        If ``group_by`` is a dict, this cannot be a list, but it can be a dict
+        of lists of axes, with the keys matching those of ``group_by``. In that
+        case, the provided axes will be used for the corresponding groups.
+        Defaults to `None`.
     overlay_times : array-like, shape (n_epochs,) | None
         If not None the parameter is interpreted as time instants in seconds
         and is added to the image. It is typically useful to display reaction
         times. Note that it is defined with respect to the order
         of epochs such that overlay_times[0] corresponds to epochs[0].
+    combine : None | str | callable
+        If None, return one figure per pick. If not None, aggregate over
+        channels via the indicated method. If str, must be one of "mean",
+        "median", "std" or "gfp", in which case the mean, the median, the
+        standard deviation or the GFP over channels are plotted.
+        array (n_epochs, n_times).
+        If callable, it must accept one positional input, the data
+        in the format `(n_epochs, n_channels, n_times)`. It must return an
+        array `(n_epochs, n_times)`. For example::
+
+            combine = lambda data: np.median(data, axis=1)
+
+        Defaults to `None` if picks are provided, otherwise 'gfp'.
+    group_by : None | str | dict
+        If not None, combining happens over channel groups defined by this
+        parameter.
+        If str, must be "type", in which case one figure per channel type is
+        returned (combining within channel types).
+        If a dict, the values must be picks and one figure will be returned
+        for each entry, aggregating over the corresponding pick groups; keys
+        will become plot titles. This is useful for e.g. ROIs. Each entry must
+        contain only one channel type. For example::
+
+            group_by=dict(Left_ROI=[1, 2, 3, 4], Right_ROI=[5, 6, 7, 8])
+
+        If not None, combine must not be None. Defaults to `None` if picks are
+        provided, otherwise 'type'.
+
+    evoked : Bool
+        Draw the ER[P/F] below the image or not.
+    ts_args : dict
+        Arguments passed to a call to `mne.viz.plot_compare_evoked` to style
+        the evoked plot below the image. Defaults to an empty dictionary,
+        meaning `plot_compare_evokeds` will be called with default parameters
+        (yaxis truncation will be turned off).
+    title : None | str
+        If str, will be plotted as figure title. Else, the channels will be
+        indicated.
 
     Returns
     -------
     figs : lists of matplotlib figures
         One figure per channel displayed.
     """
-    from scipy import ndimage
     units = _handle_default('units', units)
     scalings = _handle_default('scalings', scalings)
 
-    import matplotlib.pyplot as plt
+    # setting defaults
+    if group_by is not None and combine is None:
+        combine = 'gfp'
+
+    if all(param is None for param in (group_by, picks, combine)):
+        group_by = "type"
+        combine = "gfp"
+
     if picks is None:
         picks = pick_types(epochs.info, meg=True, eeg=True, ref_meg=False,
-                           exclude='bads')[:5]
+                           exclude='bads')
+        if group_by is None:
+            picks = picks[:5]  # take 5 picks to prevent spawning many figs
+    else:
+        picks = np.atleast_1d(picks)
+
+    manual_ylims = "ylim" in ts_args
+    vlines = ts_args.get(
+        "vlines", [0] if (epochs.times[0] < 0 < epochs.times[-1]) else [])
+
+    # input checks
+    if (combine is None and (fig is not None or axes is not None) and
+            len(picks) > 1):
+        raise ValueError('Only single pick can be drawn to a figure/axis; '
+                         'provide only one pick, or use `combine`.')
 
     if set(units.keys()) != set(scalings.keys()):
         raise ValueError('Scalings and units must have the same keys.')
 
-    picks = np.atleast_1d(picks)
-    if (fig is not None or axes is not None) and len(picks) > 1:
-        raise ValueError('Only single pick can be drawn to a figure.')
-    if axes is not None:
-        if fig is not None:
-            raise ValueError('Both figure and axes were passed, please'
-                             'decide between the two.')
-        from .utils import _validate_if_list_of_axes
-        oblig_len = 3 if colorbar else 2
-        _validate_if_list_of_axes(axes, obligatory_len=oblig_len)
-        ax1, ax2 = axes[:2]
-        # if axes were passed - we ignore fig param and get figure from axes
-        fig = ax1.get_figure()
-        if colorbar:
-            ax3 = axes[-1]
-    evoked = epochs.average(picks)
-    data = epochs.get_data()[:, picks, :]
-    n_epochs = len(data)
-    data = np.swapaxes(data, 0, 1)
-    if sigma > 0.:
-        for k in range(len(picks)):
-            data[k, :] = ndimage.gaussian_filter1d(
-                data[k, :], sigma=sigma, axis=0)
+    ch_types = [channel_type(epochs.info, idx) for idx in picks]
+    if len(set(ch_types)) > 1 and group_by is None and combine is not None:
+        warn("Combining over multiple channel types. "
+             "Please use ``group_by``.")
+    for ch_type in ch_types:
+        if ch_type not in scalings:
+            # We know it's not in either scalings or units since keys match
+            raise KeyError('%s type not in scalings and units' % ch_type)
 
-    scale_vmin = True if vmin is None else False
-    scale_vmax = True if vmax is None else False
-    vmin, vmax = _setup_vmin_vmax(data, vmin, vmax)
+    if isinstance(axes, dict):
+        show = False
+        if not isinstance(group_by, dict):
+            raise ValueError("If axes is a dict, group_by must be a dict, "
+                             "got " + str(type(group_by)))
+    else:
+        if axes is not None and isinstance(group_by, dict):
+            raise ValueError("If ``group_by`` is a dict, axes must be a dict "
+                             "or None, got " + str(type(group_by)))
+    if isinstance(group_by, dict) and combine is None:
+        raise ValueError("If ``group_by`` is a dict, ``combine`` must not be "
+                         "None.")
+
+    # call helpers to prepare the plot
+    # First, we collect groupings of picks and types in two lists
+    # (all_picks, all_ch_types, names) -> group_by.
+    # Then, we construct a list of the corresponding data, names and evokeds
+    # (groups) -> combine.
+    # Then, we loop over this list and plot using _plot_epochs_image.
+
+    # group_by
+    all_picks, all_ch_types, names = _get_picks_and_types(
+        picks, ch_types, group_by, combine)
+    # all_picks is a list of lists of ints (picks); those lists will
+    # be length 1 if combine is None, else of length > 1.
+
+    # combine/construct list for plotting
+    groups = _pick_and_combine(
+        epochs, combine, all_picks, all_ch_types, scalings, names)
+    # each entry of groups is: (data, ch_type, evoked, name)
+
+    # prepare the image - required for uniform vlims
+    vmins, vmaxs = dict(), dict()
+    for group in groups:
+        epochs, ch_type = group[:2]
+        group.extend(_prepare_epochs_image_im_data(
+            epochs, ch_type, overlay_times, order, sigma, vmin, vmax,
+            scalings[ch_type], ts_args))
+        if vmin is None or vmax is None:  # equalize across groups
+            this_vmin, this_vmax, this_ylim = group[-3:]
+            if vmin is None and (this_vmin < vmins.get(ch_type, 1)):
+                vmins[ch_type] = this_vmin
+            if vmax is None and (this_vmax > vmaxs.get(ch_type, -1)):
+                vmaxs[ch_type] = this_vmax
+
+    # plot
+    figs, axes_list = list(), list()
+    ylims = dict((ch_type, (1., -1.)) for ch_type in all_ch_types)
+    for (epochs_, ch_type, ax_name, name, data, overlay_times, vmin, vmax,
+         ts_args) in groups:
+        vmin, vmax = vmins.get(ch_type, vmin), vmaxs.get(ch_type, vmax)
+        these_axes = axes[ax_name] if isinstance(axes, dict) else axes
+        axes_dict = _prepare_epochs_image_axes(these_axes, fig, colorbar,
+                                               evoked)
+        axes_list.append(axes_dict)
+        title_ = ((ax_name if isinstance(axes, dict) else name)
+                  if title is None else title)
+        this_fig = _plot_epochs_image(
+            epochs_, data, vmin=vmin, vmax=vmax, colorbar=colorbar, show=False,
+            unit=units[ch_type], ch_type=ch_type, cmap=cmap,
+            axes_dict=axes_dict, title=title_, overlay_times=overlay_times,
+            evoked=evoked, ts_args=ts_args)
+        figs.append(this_fig)
+
+        # the rest of the code is for aligning ylims for multiple plots
+        if evoked is True and not manual_ylims:
+            evoked_ax = axes_dict["evoked"]
+            this_min, this_max = evoked_ax.get_ylim()
+            curr_min, curr_max = ylims[ch_type]
+            ylims[ch_type] = min(curr_min, this_min), max(curr_max, this_max),
+
+    if evoked is True:  # adjust ylims
+        for group, axes_dict in zip(groups, axes_list):
+            ch_type = group[1]
+            ax = axes_dict["evoked"]
+            if not manual_ylims:
+                ax.set_ylim(ylims[ch_type])
+            if len(vlines) > 0:
+                upper_v, lower_v = ylims[ch_type]
+                if overlay_times is not None:
+                    overlay = {overlay_times.mean(), np.median(overlay_times)}
+                else:
+                    overlay = {}
+                for line in vlines:
+                    ax.vlines(line, upper_v, lower_v, colors='k',
+                              linestyles='-' if line in overlay else "--",
+                              linewidth=2. if line in overlay else 1.)
+
+    plt_show(show)
+    return figs
+
+
+def _get_picks_and_types(picks, ch_types, group_by, combine):
+    """Pack picks and types into a list. Helper for plot_epochs_image."""
+    if group_by is None:
+        if combine is not None:
+            picks = [picks]
+        return picks, ch_types, ch_types
+    elif group_by == "type":
+        all_picks, all_ch_types = list(), list()
+        for this_type in set(ch_types):
+            these_picks = picks[np.array(ch_types) == this_type]
+            all_picks.append(these_picks)
+            all_ch_types.append(this_type)
+        names = all_ch_types  # only differs for dict group_by
+    elif isinstance(group_by, dict):
+        names = list(group_by.keys())
+        all_picks = [group_by[name] for name in names]
+        for name, picks_ in group_by.items():
+            n_picks = len(picks_)
+            if n_picks < 2:
+                raise ValueError(" ".join(
+                    (name, "has only ", str(n_picks), "sensors.")))
+        all_ch_types = list()
+        for picks_, name in zip(all_picks, names):
+            this_ch_type = list(set((ch_types[pick] for pick in picks_)))
+            n_types = len(this_ch_type)
+            if n_types > 1:  # we can only scale properly with 1 type
+                raise ValueError(
+                    "Roi {} contains {} sensor types!".format(
+                        name, n_types))
+            all_ch_types.append(this_ch_type[0])
+            names.append(name)
+    else:
+        raise ValueError("If ``group_by`` is not None, it must be a dict "
+                         "or 'type', got " + str(type(group_by)))
+    return all_picks, all_ch_types, names  # all_picks is a list of lists
+
+
+def _pick_and_combine(epochs, combine, all_picks, all_ch_types, scalings,
+                      names):
+    """Pick and combine epochs image. Helper for plot_epochs_image."""
+    to_plot_list = list()
+    tmin = epochs.times[0]
+
+    if combine is None:
+        if epochs.preload is False:
+            epochs = epochs.copy().load_data()  # FIXME: avoid copy
+        for pick, ch_type in zip(all_picks, all_ch_types):
+            name = epochs.ch_names[pick]
+            these_epochs = epochs.copy().pick_channels([name])
+            to_plot_list.append([these_epochs, ch_type, name, name])
+        return to_plot_list
+
+    # if combine is not None ...
+    from .. import EpochsArray, pick_info
+    data = epochs.get_data()
+    type2name = {"eeg": "EEG", "grad": "Gradiometers",
+                 "mag": "Magnetometers"}
+    combine_title = (" (" + combine + ")"
+                     if isinstance(combine, string_types) else "")
+    if combine == "gfp":
+        def combine(data):
+            return np.sqrt((data * data).mean(axis=1))
+    elif combine == "mean":
+        def combine(data):
+            return np.mean(data, axis=1)
+    elif combine == "std":
+        def combine(data):
+            return np.std(data, axis=1)
+    elif combine == "median":
+        def combine(data):
+            return np.median(data, axis=1)
+    elif not callable(combine):
+        raise ValueError(
+            "``combine`` must be None, a callable or one out of 'mean' "
+            "or 'gfp'. Got " + str(type(combine)))
+    for ch_type, picks_, name in zip(all_ch_types, all_picks, names):
+        if len(np.atleast_1d(picks_)) < 2:
+            raise ValueError("Cannot combine over only one sensor. "
+                             "Consider using different values for "
+                             "``picks`` and/or ``group_by``.")
+        if ch_type == "grad":
+            def pair_and_combine(data):
+                data = data ** 2
+                data = (data[:, ::2, :] + data[:, 1::2, :]) / 2
+                return combine(np.sqrt(data))
+            picks_ = _grad_pair_pick_and_name(epochs.info, picks_)[0]
+            this_data = pair_and_combine(
+                data[:, picks_, :])[:, np.newaxis, :]
+        else:
+            this_data = combine(
+                data[:, picks_, :])[:, np.newaxis, :]
+        info = pick_info(epochs.info, [picks_[0]], copy=True)
+        these_epochs = EpochsArray(this_data.copy(), info, tmin=tmin)
+        d = these_epochs.get_data()  # Why is this necessary?
+        d[:] = this_data  # Without this, the data is all-zeros!
+        to_plot_list.append([these_epochs, ch_type, name,
+                             type2name.get(name, name) + combine_title])
+
+    return to_plot_list  # epochs, ch_type, name, axtitle
+
+
+def _prepare_epochs_image_im_data(epochs, ch_type, overlay_times, order,
+                                  sigma, vmin, vmax, scaling, ts_args):
+    """Preprocess epochs image (sort, filter). Helper for plot_epochs_image."""
+    from scipy import ndimage
+
+    # data transforms - sorting, scaling, smoothing
+    data = epochs.get_data()[:, 0, :]
+    n_epochs = len(data)
 
     if overlay_times is not None and len(overlay_times) != n_epochs:
         raise ValueError('size of overlay_times parameter (%s) do not '
@@ -143,97 +401,137 @@ def plot_epochs_image(epochs, picks=None, sigma=0., vmin=None,
         overlay_times = np.array(overlay_times)
         times_min = np.min(overlay_times)
         times_max = np.max(overlay_times)
-        if ((times_min < epochs.tmin) or (times_max > epochs.tmax)):
+        if ((times_min < epochs.times[0]) or (times_max > epochs.times[-1])):
             warn('Some values in overlay_times fall outside of the epochs '
                  'time interval (between %s s and %s s)'
-                 % (epochs.tmin, epochs.tmax))
-
-    figs = list()
-    for i, (this_data, idx) in enumerate(zip(data, picks)):
-        if fig is None:
-            this_fig = plt.figure()
-        else:
-            this_fig = fig
-        figs.append(this_fig)
-
-        ch_type = channel_type(epochs.info, idx)
-        if ch_type not in scalings:
-            # We know it's not in either scalings or units since keys match
-            raise KeyError('%s type not in scalings and units' % ch_type)
-        this_data *= scalings[ch_type]
+                 % (epochs.times[0], epochs.times[-1]))
 
-        this_order = order
-        if callable(order):
-            this_order = order(epochs.times, this_data)
+    if callable(order):
+        order = order(epochs.times, data)
+    if order is not None and (len(order) != n_epochs):
+        raise ValueError(("`order` must be None, callable or an array as long "
+                          "as the data. Got " + str(type(order))))
 
-        if this_order is not None and (len(this_order) != len(this_data)):
-            raise ValueError('size of order parameter (%s) does not '
-                             'match the number of epochs (%s).'
-                             % (len(this_order), len(this_data)))
-
-        this_overlay_times = None
+    if order is not None:
+        order = np.asarray(order)
+        data = data[order]
         if overlay_times is not None:
-            this_overlay_times = overlay_times
-
-        if this_order is not None:
-            this_order = np.asarray(this_order)
-            this_data = this_data[this_order]
-            if this_overlay_times is not None:
-                this_overlay_times = this_overlay_times[this_order]
-
-        plt.figure(this_fig.number)
-        if axes is None:
-            ax1 = plt.subplot2grid((3, 10), (0, 0), colspan=9, rowspan=2)
-            ax2 = plt.subplot2grid((3, 10), (2, 0), colspan=9, rowspan=1)
-            if colorbar:
-                ax3 = plt.subplot2grid((3, 10), (0, 9), colspan=1, rowspan=3)
-
-        this_vmin = vmin * scalings[ch_type] if scale_vmin else vmin
-        this_vmax = vmax * scalings[ch_type] if scale_vmax else vmax
-
-        if cmap == 'interactive':
-            cmap = ('RdBu_r', True)
-        elif not isinstance(cmap, tuple):
-            cmap = (cmap, True)
-        im = ax1.imshow(this_data,
-                        extent=[1e3 * epochs.times[0], 1e3 * epochs.times[-1],
-                                0, n_epochs],
-                        aspect='auto', origin='lower', interpolation='nearest',
-                        vmin=this_vmin, vmax=this_vmax, cmap=cmap[0])
-        if this_overlay_times is not None:
-            plt.plot(1e3 * this_overlay_times, 0.5 + np.arange(len(this_data)),
-                     'k', linewidth=2)
-        ax1.set_title(epochs.ch_names[idx])
-        ax1.set_ylabel('Epochs')
-        ax1.axis('auto')
-        ax1.axis('tight')
-        ax1.axvline(0, color='m', linewidth=3, linestyle='--')
-        evoked_data = scalings[ch_type] * evoked.data[i]
-        ax2.plot(1e3 * evoked.times, evoked_data)
-        ax2.set_xlabel('Time (ms)')
-        ax2.set_xlim([1e3 * evoked.times[0], 1e3 * evoked.times[-1]])
-        ax2.set_ylabel(units[ch_type])
-        evoked_vmin = min(evoked_data) * 1.1 if scale_vmin else vmin
-        evoked_vmax = max(evoked_data) * 1.1 if scale_vmax else vmax
-        if scale_vmin or scale_vmax:
-            evoked_vmax = max(np.abs([evoked_vmax, evoked_vmin]))
-            evoked_vmin = -evoked_vmax
-        ax2.set_ylim([evoked_vmin, evoked_vmax])
-        ax2.axvline(0, color='m', linewidth=3, linestyle='--')
+            overlay_times = overlay_times[order]
+
+    if sigma > 0.:
+        data = ndimage.gaussian_filter1d(data, sigma=sigma, axis=0)
+
+    # setup lims and cmap
+    scale_vmin = True if (vmin is None or callable(vmin)) else False
+    scale_vmax = True if (vmax is None or callable(vmax)) else False
+    vmin, vmax = _setup_vmin_vmax(
+        data, vmin, vmax, norm=(data.min() >= 0) and (vmin is None))
+    if not scale_vmin:
+        vmin /= scaling
+    if not scale_vmax:
+        vmax /= scaling
+
+    ylim = dict()
+    ts_args_ = dict(colors={"cond": "black"}, ylim=ylim, picks=[0], title='',
+                    truncate_yaxis=False, truncate_xaxis=False, show=False)
+    ts_args_.update(**ts_args)
+    ts_args_["vlines"] = []
+
+    return [data * scaling, overlay_times, vmin * scaling, vmax * scaling,
+            ts_args_]
+
+
+def _prepare_epochs_image_axes(axes, fig, colorbar, evoked):
+    """Prepare axes for image plotting. Helper for plot_epochs_image."""
+    import matplotlib.pyplot as plt
+    # prepare fig and axes
+    axes_dict = dict()
+    if axes is None:
+        if fig is None:
+            fig = plt.figure()
+        plt.figure(fig.number)
+        axes_dict["image"] = plt.subplot2grid(
+            (3, 10), (0, 0), colspan=9 if colorbar else 10,
+            rowspan=2 if evoked else 3)
+        if evoked:
+            axes_dict["evoked"] = plt.subplot2grid(
+                (3, 10), (2, 0), colspan=9 if colorbar else 10, rowspan=1)
         if colorbar:
-            cbar = plt.colorbar(im, cax=ax3)
-            if cmap[1]:
-                ax1.CB = DraggableColorbar(cbar, im)
-            tight_layout(fig=this_fig)
+            axes_dict["colorbar"] = plt.subplot2grid((3, 10), (0, 9),
+                                                     colspan=1, rowspan=3)
+    else:
+        if fig is not None:
+            raise ValueError('Both figure and axes were passed, please'
+                             'only pass one of these.')
+        from .utils import _validate_if_list_of_axes
+        oblig_len = 3 - ((not colorbar) + (not evoked))
+        _validate_if_list_of_axes(axes, obligatory_len=oblig_len)
+        axes_dict["image"] = axes[0]
+        if evoked:
+            axes_dict["evoked"] = axes[1]
+        # if axes were passed - we ignore fig param and get figure from axes
+        fig = axes_dict["image"].get_figure()
+        if colorbar:
+            axes_dict["colorbar"] = axes[-1]
+    return axes_dict
+
+
+def _plot_epochs_image(epochs, data, ch_type, vmin=None, vmax=None,
+                       colorbar=False, show=False, unit=None, cmap=None,
+                       axes_dict=None, overlay_times=None, title=None,
+                       evoked=False, ts_args=None):
+    """Plot epochs image. Helper function for plot_epochs_image."""
+    if cmap is None:
+        cmap = "Reds" if data.min() >= 0 else 'RdBu_r'
+
+    # Plot
+    # draw the image
+    ax = axes_dict["image"]
+    fig = ax.get_figure()
+    cmap = _setup_cmap(cmap)
+    n_epochs = len(data)
+    extent = [1e3 * epochs.times[0], 1e3 * epochs.times[-1], 0, n_epochs]
+    im = ax.imshow(data, vmin=vmin, vmax=vmax, cmap=cmap[0], aspect='auto',
+                   origin='lower', interpolation='nearest', extent=extent)
+    if overlay_times is not None:
+        ax.plot(1e3 * overlay_times, 0.5 + np.arange(n_epochs), 'k',
+                linewidth=2)
+    ax.set_title(title)
+    ax.set_ylabel('Epochs')
+    ax.axis('auto')
+    ax.axis('tight')
+    if overlay_times is not None:
+        ax.set_xlim(1e3 * epochs.times[0], 1e3 * epochs.times[-1])
+    ax.axvline(0, color='k', linewidth=1, linestyle='--')
+
+    # draw the evoked
+    if evoked:
+        from mne.viz import plot_compare_evokeds
+        plot_compare_evokeds(
+            {"cond": list(epochs.iter_evoked())}, axes=axes_dict["evoked"],
+            **ts_args)
+        axes_dict["evoked"].set_xlim(epochs.times[[0, -1]])
+        ax.set_xticks(())
+
+    # draw the colorbar
+    if colorbar:
+        import matplotlib.pyplot as plt
+        cbar = plt.colorbar(im, cax=axes_dict['colorbar'])
+        cbar.ax.set_ylabel(unit + "\n\n", rotation=270)
+        if cmap[1]:
+            ax.CB = DraggableColorbar(cbar, im)
+        tight_layout(fig=fig)
+    fig._axes_dict = axes_dict  # storing this here for easy access later
+
+    # finish
     plt_show(show)
-
-    return figs
+    return fig
 
 
 def plot_drop_log(drop_log, threshold=0, n_max_plot=20, subject='Unknown',
                   color=(0.9, 0.9, 0.9), width=0.8, ignore=('IGNORED',),
                   show=True):
-    """Show the channel stats based on a drop_log from Epochs
+    """Show the channel stats based on a drop_log from Epochs.
 
     Parameters
     ----------
@@ -292,7 +590,7 @@ def plot_drop_log(drop_log, threshold=0, n_max_plot=20, subject='Unknown',
 
 def _draw_epochs_axes(epoch_idx, good_ch_idx, bad_ch_idx, data, times, axes,
                       title_str, axes_handler):
-    """Aux functioin"""
+    """Handle drawing epochs axes."""
     this = axes_handler[0]
     for ii, data_, ax in zip(epoch_idx, data, axes):
         for l, d in zip(ax.lines, data_[good_ch_idx]):
@@ -327,7 +625,7 @@ def _draw_epochs_axes(epoch_idx, good_ch_idx, bad_ch_idx, data, times, axes,
 
 
 def _epochs_navigation_onclick(event, params):
-    """Aux function"""
+    """Handle epochs navigation click."""
     import matplotlib.pyplot as plt
     p = params
     here = None
@@ -354,7 +652,7 @@ def _epochs_navigation_onclick(event, params):
 
 
 def _epochs_axes_onclick(event, params):
-    """Aux function"""
+    """Handle epochs axes click."""
     reject_color = (0.8, 0.8, 0.8)
     ax = event.inaxes
     if event.inaxes is None:
@@ -383,9 +681,10 @@ def _epochs_axes_onclick(event, params):
     ax.get_figure().canvas.draw()
 
 
-def plot_epochs(epochs, picks=None, scalings=None, n_epochs=20,
-                n_channels=20, title=None, show=True, block=False):
-    """ Visualize epochs
+def plot_epochs(epochs, picks=None, scalings=None, n_epochs=20, n_channels=20,
+                title=None, events=None, event_colors=None, show=True,
+                block=False, decim='auto'):
+    """Visualize epochs.
 
     Bad epochs can be marked with a left click on top of the epoch. Bad
     channels can be selected by clicking the channel name on the left side of
@@ -394,7 +693,6 @@ def plot_epochs(epochs, picks=None, scalings=None, n_epochs=20,
 
     Parameters
     ----------
-
     epochs : instance of Epochs
         The epochs object
     picks : array-like of int | None
@@ -417,12 +715,39 @@ def plot_epochs(epochs, picks=None, scalings=None, n_epochs=20,
     title : str | None
         The title of the window. If None, epochs name will be displayed.
         Defaults to None.
+    events : None, array, shape (n_events, 3)
+        Events to show with vertical bars. If events are provided, the epoch
+        numbers are not shown to prevent overlap. You can toggle epoch
+        numbering through options (press 'o' key). You can use
+        :func:`mne.viz.plot_events` as a legend for the colors. By default, the
+        coloring scheme is the same.
+
+        .. warning::  If the epochs have been resampled, the events no longer
+            align with the data.
+
+        .. versionadded:: 0.14.0
+    event_colors : None, dict
+        Dictionary of event_id value and its associated color. If None,
+        colors are automatically drawn from a default list (cycled through if
+        number of events longer than list of default colors). Uses the same
+        coloring scheme as :func:`mne.viz.plot_events`.
+
+        .. versionadded:: 0.14.0
     show : bool
         Show figure if True. Defaults to True
     block : bool
         Whether to halt program execution until the figure is closed.
         Useful for rejecting bad trials on the fly by clicking on an epoch.
         Defaults to False.
+    decim : int | 'auto'
+        Amount to decimate the data during display for speed purposes.
+        You should only decimate if the data are sufficiently low-passed,
+        otherwise aliasing can occur. The 'auto' mode (default) uses
+        the decimation that results in a sampling rate at least three times
+        larger than ``info['lowpass']`` (e.g., a 40 Hz lowpass will result in
+        at least a 120 Hz displayed sample rate).
+
+        .. versionadded:: 0.15
 
     Returns
     -------
@@ -436,25 +761,32 @@ def plot_epochs(epochs, picks=None, scalings=None, n_epochs=20,
     keys, but this depends on the backend matplotlib is configured to use
     (e.g., mpl.use(``TkAgg``) should work). Full screen mode can be toggled
     with f11 key. The amount of epochs and channels per view can be adjusted
-    with home/end and page down/page up keys. Butterfly plot can be toggled
-    with ``b`` key. Right mouse click adds a vertical line to the plot.
+    with home/end and page down/page up keys. These can also be set through
+    options dialog by pressing ``o`` key. ``h`` key plots a histogram of
+    peak-to-peak values along with the used rejection thresholds. Butterfly
+    plot can be toggled with ``b`` key. Right mouse click adds a vertical line
+    to the plot. Click 'help' button at bottom left corner of the plotter to
+    view all the options.
 
     .. versionadded:: 0.10.0
     """
     epochs.drop_bad()
     scalings = _compute_scalings(scalings, epochs)
     scalings = _handle_default('scalings_plot_raw', scalings)
-
+    decim, data_picks = _handle_decim(epochs.info.copy(), decim, None)
     projs = epochs.info['projs']
 
     params = {'epochs': epochs,
               'info': copy.deepcopy(epochs.info),
               'bad_color': (0.8, 0.8, 0.8),
               't_start': 0,
-              'histogram': None}
+              'histogram': None,
+              'decim': decim,
+              'data_picks': data_picks}
     params['label_click_fun'] = partial(_pick_bad_channels, params=params)
     _prepare_mne_browse_epochs(params, projs, n_channels, n_epochs, scalings,
-                               title, picks)
+                               title, picks, events=events,
+                               event_colors=event_colors)
     _prepare_projectors(params)
     _layout_figure(params)
 
@@ -474,7 +806,7 @@ def plot_epochs_psd(epochs, fmin=0, fmax=np.inf, tmin=None, tmax=None,
                     normalization='length', picks=None, ax=None, color='black',
                     area_mode='std', area_alpha=0.33, dB=True, n_jobs=1,
                     show=True, verbose=None):
-    """Plot the power spectral density across epochs
+    """Plot the power spectral density across epochs.
 
     Parameters
     ----------
@@ -523,16 +855,18 @@ def plot_epochs_psd(epochs, fmin=0, fmax=np.inf, tmin=None, tmax=None,
     show : bool
         Show figure if True.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
     fig : instance of matplotlib figure
         Figure distributing one image per channel across sensor topography.
     """
-    from .raw import _set_psd_plot_params
-    fig, picks_list, titles_list, ax_list, make_label = _set_psd_plot_params(
-        epochs.info, proj, picks, ax, area_mode)
+    from .raw import _set_psd_plot_params, _convert_psds
+    fig, picks_list, titles_list, units_list, scalings_list, ax_list, \
+        make_label = _set_psd_plot_params(
+            epochs.info, proj, picks, ax, area_mode)
 
     for ii, (picks, title, ax) in enumerate(zip(picks_list, titles_list,
                                                 ax_list)):
@@ -543,12 +877,10 @@ def plot_epochs_psd(epochs, fmin=0, fmax=np.inf, tmin=None, tmax=None,
                                      normalization=normalization, proj=proj,
                                      n_jobs=n_jobs)
 
-        # Convert PSDs to dB
-        if dB:
-            psds = 10 * np.log10(psds)
-            unit = 'dB'
-        else:
-            unit = 'power'
+        ylabel = _convert_psds(psds, dB, 'auto', scalings_list[ii],
+                               units_list[ii],
+                               [epochs.ch_names[pi] for pi in picks])
+
         # mean across epochs and channels
         psd_mean = np.mean(psds, axis=0).mean(axis=0)
         if area_mode == 'std':
@@ -568,8 +900,7 @@ def plot_epochs_psd(epochs, fmin=0, fmax=np.inf, tmin=None, tmax=None,
         if make_label:
             if ii == len(picks_list) - 1:
                 ax.set_xlabel('Freq (Hz)')
-            if ii == len(picks_list) // 2:
-                ax.set_ylabel('Power Spectral Density (%s/Hz)' % unit)
+            ax.set_ylabel(ylabel)
             ax.set_title(title)
             ax.set_xlim(freqs[0], freqs[-1])
     if make_label:
@@ -579,8 +910,9 @@ def plot_epochs_psd(epochs, fmin=0, fmax=np.inf, tmin=None, tmax=None,
 
 
 def _prepare_mne_browse_epochs(params, projs, n_channels, n_epochs, scalings,
-                               title, picks, order=None):
-    """Helper for setting up the mne_browse_epochs window."""
+                               title, picks, events=None, event_colors=None,
+                               order=None):
+    """Set up the mne_browse_epochs window."""
     import matplotlib.pyplot as plt
     import matplotlib as mpl
     from matplotlib.collections import LineCollection
@@ -639,8 +971,8 @@ def _prepare_mne_browse_epochs(params, projs, n_channels, n_epochs, scalings,
         size = size.split(',')
         size = tuple(float(s) for s in size)
     if title is None:
-        title = epochs.name
-        if epochs.name is None or len(title) == 0:
+        title = epochs._name
+        if title is None or len(title) == 0:
             title = ''
     fig = figure_nobar(facecolor='w', figsize=size, dpi=80)
     fig.canvas.set_window_title('mne_browse_epochs')
@@ -696,7 +1028,7 @@ def _prepare_mne_browse_epochs(params, projs, n_channels, n_epochs, scalings,
     for ch_idx in range(n_channels):
         if len(colors) - 1 < ch_idx:
             break
-        lc = LineCollection(list(), antialiased=False, linewidths=0.5,
+        lc = LineCollection(list(), antialiased=True, linewidths=0.5,
                             zorder=3, picker=3.)
         ax.add_collection(lc)
         lines.append(lc)
@@ -719,7 +1051,6 @@ def _prepare_mne_browse_epochs(params, projs, n_channels, n_epochs, scalings,
     ax2.set_xticks(ticks[:n_epochs])
     labels = list(range(1, len(ticks) + 1))  # epoch numbers
     ax.set_xticklabels(labels)
-    ax2.set_xticklabels(labels)
     xlim = epoch_times[-1] + len(epochs.times)
     ax_hscroll.set_xlim(0, xlim)
     vertline_t = ax_hscroll.text(0, 1, '', color='y', va='bottom', ha='right')
@@ -747,6 +1078,14 @@ def _prepare_mne_browse_epochs(params, projs, n_channels, n_epochs, scalings,
                    ha='left', fontweight='bold')
     text.set_visible(False)
 
+    epoch_nr = True
+    if events is not None:
+        event_set = set(events[:, 2])
+        event_colors = _handle_event_colors(event_set, event_colors, event_set)
+        epoch_nr = False  # epoch number off by default to avoid overlap
+        for label in ax.xaxis.get_ticklabels():
+            label.set_visible(False)
+
     params.update({'fig': fig,
                    'ax': ax,
                    'ax2': ax2,
@@ -783,8 +1122,12 @@ def _prepare_mne_browse_epochs(params, projs, n_channels, n_epochs, scalings,
                    'ax_help_button': ax_help_button,  # needed for positioning
                    'help_button': help_button,  # reference needed for clicks
                    'fig_options': None,
-                   'settings': [True, True, True, True],
-                   'image_plot': None})
+                   'settings': [True, True, epoch_nr, True],
+                   'image_plot': None,
+                   'events': events,
+                   'event_colors': event_colors,
+                   'ev_lines': list(),
+                   'ev_texts': list()})
 
     params['plot_fun'] = partial(_plot_traces, params=params)
 
@@ -803,9 +1146,12 @@ def _prepare_mne_browse_epochs(params, projs, n_channels, n_epochs, scalings,
     # Draw event lines for the first time.
     _plot_vert_lines(params)
 
+    # default key to close window
+    params['close_key'] = 'escape'
+
 
 def _prepare_projectors(params):
-    """ Helper for setting up the projectors for epochs browser """
+    """Set up the projectors for epochs browser."""
     import matplotlib.pyplot as plt
     import matplotlib as mpl
     epochs = params['epochs']
@@ -829,7 +1175,7 @@ def _prepare_projectors(params):
 
 
 def _plot_traces(params):
-    """ Helper for plotting concatenated epochs """
+    """Plot concatenated epochs."""
     params['text'].set_visible(False)
     ax = params['ax']
     butterfly = params['butterfly']
@@ -878,14 +1224,20 @@ def _plot_traces(params):
             else:
                 tick_list += [params['ch_names'][ch_idx]]
                 offset = offsets[line_idx]
+
+            if params['inds'][ch_idx] in params['data_picks']:
+                this_decim = params['decim']
+            else:
+                this_decim = 1
             this_data = data[ch_idx]
 
             # subtraction here gets correct orientation for flipped ylim
             ydata = offset - this_data
             xdata = params['times'][:params['duration']]
-            num_epochs = np.min([params['n_epochs'],
-                                len(epochs.events)])
+            num_epochs = np.min([params['n_epochs'], len(epochs.events)])
+
             segments = np.split(np.array((xdata, ydata)).T, num_epochs)
+            segments = [segment[::this_decim] for segment in segments]
 
             ch_name = params['ch_names'][ch_idx]
             if ch_name in params['info']['bads']:
@@ -956,6 +1308,10 @@ def _plot_traces(params):
         ax.set_yticklabels(labels, fontsize=12, color='black')
     else:
         ax.set_yticklabels(tick_list, fontsize=12)
+
+    if params['events'] is not None:  # vertical lines for events.
+        _draw_event_lines(params)
+
     params['vsel_patch'].set_y(ch_start)
     params['fig'].canvas.draw()
     # XXX This is a hack to make sure this figure gets drawn last
@@ -966,19 +1322,24 @@ def _plot_traces(params):
 
 
 def _plot_update_epochs_proj(params, bools=None):
-    """Helper only needs to be called when proj is changed"""
+    """Deal with proj changed."""
     if bools is not None:
         inds = np.where(bools)[0]
         params['info']['projs'] = [copy.deepcopy(params['projs'][ii])
                                    for ii in inds]
         params['proj_bools'] = bools
+    epochs = params['epochs']
+    n_epochs = params['n_epochs']
     params['projector'], _ = setup_proj(params['info'], add_eeg_ref=False,
                                         verbose=False)
-
-    start = int(params['t_start'] / len(params['epochs'].times))
-    n_epochs = params['n_epochs']
+    start = int(params['t_start'] / len(epochs.times))
     end = start + n_epochs
-    data = np.concatenate(params['epochs'][start:end].get_data(), axis=1)
+    if epochs.preload:
+        data = np.concatenate(epochs.get_data()[start:end], axis=1)
+    else:
+        # this is faster than epochs.get_data()[start:end] when not preloaded
+        data = np.concatenate(epochs[start:end].get_data(), axis=1)
+
     if params['projector'] is not None:
         data = np.dot(params['projector'], data)
     types = params['types']
@@ -988,7 +1349,7 @@ def _plot_update_epochs_proj(params, bools=None):
 
 
 def _handle_picks(epochs):
-    """Aux function to handle picks."""
+    """Handle picks."""
     if any('ICA' in k for k in epochs.ch_names):
         picks = pick_types(epochs.info, misc=True, ref_meg=False,
                            exclude=[])
@@ -1013,17 +1374,18 @@ def _plot_window(value, params):
 
 
 def _plot_vert_lines(params):
-    """ Helper function for plotting vertical lines."""
+    """Plot vertical lines."""
     ax = params['ax']
     while len(ax.lines) > 0:
         ax.lines.pop()
     params['vert_lines'] = list()
+    params['ev_lines'] = list()
     params['vertline_t'].set_text('')
 
     epochs = params['epochs']
     if params['settings'][3]:  # if zeroline visible
         t_zero = np.where(epochs.times == 0.)[0]
-        if len(t_zero) == 1:
+        if len(t_zero) == 1:  # not True if tmin > 0
             for event_idx in range(len(epochs.events)):
                 pos = [event_idx * len(epochs.times) + t_zero[0],
                        event_idx * len(epochs.times) + t_zero[0]]
@@ -1031,10 +1393,12 @@ def _plot_vert_lines(params):
     for epoch_idx in range(len(epochs.events)):
         pos = [epoch_idx * len(epochs.times), epoch_idx * len(epochs.times)]
         ax.plot(pos, ax.get_ylim(), color='black', linestyle='--', zorder=2)
+    if params['events'] is not None:
+        _draw_event_lines(params)
 
 
 def _pick_bad_epochs(event, params):
-    """Helper for selecting / dropping bad epochs"""
+    """Select / drop bad epochs."""
     if 'ica' in params:
         pos = (event.xdata, event.ydata)
         _pick_bad_channels(pos, params)
@@ -1067,7 +1431,7 @@ def _pick_bad_epochs(event, params):
 
 
 def _pick_bad_channels(pos, params):
-    """Helper function for selecting bad channels."""
+    """Select bad channels."""
     text, ch_idx = _label2idx(params, pos)
     if text is None:
         return
@@ -1087,7 +1451,7 @@ def _pick_bad_channels(pos, params):
 
 
 def _plot_onscroll(event, params):
-    """Function to handle scroll events."""
+    """Handle scroll events."""
     if event.key == 'control':
         if event.step < 0:
             event.key = '-'
@@ -1101,7 +1465,7 @@ def _plot_onscroll(event, params):
 
 
 def _mouse_click(event, params):
-    """Function to handle mouse click events."""
+    """Handle mouse click events."""
     if event.inaxes is None:
         if params['butterfly'] or not params['settings'][0]:
             return
@@ -1178,7 +1542,7 @@ def _mouse_click(event, params):
 
 
 def _plot_onkey(event, params):
-    """Function to handle key presses."""
+    """Handle key presses."""
     import matplotlib.pyplot as plt
     if event.key == 'down':
         if params['butterfly']:
@@ -1237,7 +1601,7 @@ def _plot_onkey(event, params):
         offset = ylim[0] / n_channels
         params['offsets'] = np.arange(n_channels) * offset + (offset / 2.)
         params['n_channels'] = n_channels
-        lc = LineCollection(list(), antialiased=False, linewidths=0.5,
+        lc = LineCollection(list(), antialiased=True, linewidths=0.5,
                             zorder=3, picker=3.)
         params['ax'].add_collection(lc)
         params['ax'].set_yticks(params['offsets'])
@@ -1294,7 +1658,7 @@ def _plot_onkey(event, params):
 
 
 def _prepare_butterfly(params):
-    """Helper function for setting up butterfly plot."""
+    """Set up butterfly plot."""
     from matplotlib.collections import LineCollection
     butterfly = not params['butterfly']
     if butterfly:
@@ -1356,7 +1720,7 @@ def _prepare_butterfly(params):
             used_types += 1
 
         while len(params['lines']) < len(params['picks']):
-            lc = LineCollection(list(), antialiased=False, linewidths=0.5,
+            lc = LineCollection(list(), antialiased=True, linewidths=0.5,
                                 zorder=3, picker=3.)
             ax.add_collection(lc)
             params['lines'].append(lc)
@@ -1380,7 +1744,7 @@ def _prepare_butterfly(params):
 
 
 def _onpick(event, params):
-    """Helper to add a channel name on click"""
+    """Add a channel name on click."""
     if event.mouseevent.button != 2 or not params['butterfly']:
         return  # text label added with a middle mouse button
     lidx = np.where([l is event.artist for l in params['lines']])[0][0]
@@ -1395,21 +1759,21 @@ def _onpick(event, params):
 
 
 def _close_event(event, params):
-    """Function to drop selected bad epochs. Called on closing of the plot."""
+    """Drop selected bad epochs (called on closing of the plot)."""
     params['epochs'].drop(params['bads'])
     params['epochs'].info['bads'] = params['info']['bads']
     logger.info('Channels marked as bad: %s' % params['epochs'].info['bads'])
 
 
 def _resize_event(event, params):
-    """Function to handle resize event"""
+    """Handle resize event."""
     size = ','.join([str(s) for s in params['fig'].get_size_inches()])
     set_config('MNE_BROWSE_RAW_SIZE', size, set_env=False)
     _layout_figure(params)
 
 
 def _update_channels_epochs(event, params):
-    """Function for changing the amount of channels and epochs per view."""
+    """Change the amount of channels and epochs per view."""
     from matplotlib.collections import LineCollection
     # Channels
     n_channels = int(np.around(params['channel_slider'].val))
@@ -1419,7 +1783,7 @@ def _update_channels_epochs(event, params):
         params['ax'].collections.pop()
         params['lines'].pop()
     while len(params['lines']) < n_channels:
-        lc = LineCollection(list(), linewidths=0.5, antialiased=False,
+        lc = LineCollection(list(), linewidths=0.5, antialiased=True,
                             zorder=3, picker=3.)
         params['ax'].add_collection(lc)
         params['lines'].append(lc)
@@ -1443,7 +1807,7 @@ def _update_channels_epochs(event, params):
 
 
 def _toggle_labels(label, params):
-    """Function for toggling axis labels on/off."""
+    """Toggle axis labels."""
     if label == 'Channel names visible':
         params['settings'][0] = not params['settings'][0]
         labels = params['ax'].yaxis.get_ticklabels()
@@ -1468,7 +1832,7 @@ def _toggle_labels(label, params):
 
 
 def _open_options(params):
-    """Function for opening the option window."""
+    """Open the option window."""
     import matplotlib.pyplot as plt
     import matplotlib as mpl
     if params['fig_options'] is not None:
@@ -1517,12 +1881,12 @@ def _open_options(params):
 
 
 def _settings_closed(events, params):
-    """Function to handle close event from settings dialog."""
+    """Handle close event from settings dialog."""
     params['fig_options'] = None
 
 
 def _plot_histogram(params):
-    """Function for plotting histogram of peak-to-peak values."""
+    """Plot histogram of peak-to-peak values."""
     import matplotlib.pyplot as plt
     epochs = params['epochs']
     p2p = np.ptp(epochs.get_data(), axis=2)
@@ -1575,7 +1939,7 @@ def _plot_histogram(params):
 
 
 def _label2idx(params, pos):
-    """Aux function for click on labels. Returns channel name and idx."""
+    """Handle click on labels (returns channel name and idx)."""
     labels = params['ax'].yaxis.get_ticklabels()
     offsets = np.array(params['offsets']) + params['offsets'][0]
     line_idx = np.searchsorted(offsets, pos[1])
@@ -1584,3 +1948,37 @@ def _label2idx(params, pos):
         return None, None
     ch_idx = params['ch_start'] + line_idx
     return text, ch_idx
+
+
+def _draw_event_lines(params):
+    """Draw event lines."""
+    epochs = params['epochs']
+    n_times = len(epochs.times)
+    start_idx = int(params['t_start'] / n_times)
+    color = params['event_colors']
+    ax = params['ax']
+    for ev_line in params['ev_lines']:
+        ax.lines.remove(ev_line)  # clear the view first
+    for ev_text in params['ev_texts']:
+        ax.texts.remove(ev_text)
+    params['ev_texts'] = list()
+    params['ev_lines'] = list()
+    t_zero = np.where(epochs.times == 0.)[0]  # idx of 0s
+    if len(t_zero) == 0:
+        t_zero = epochs.times[0] * -1 * epochs.info['sfreq']  # if tmin > 0
+    end = params['n_epochs'] + start_idx
+    samp_times = params['events'][:, 0]
+    for idx, event in enumerate(epochs.events[start_idx:end]):
+        event_mask = ((event[0] - t_zero < samp_times) &
+                      (samp_times < event[0] + n_times - t_zero))
+        for ev in params['events'][event_mask]:
+            if ev[0] == event[0]:  # don't redraw the zeroline
+                continue
+            pos = [idx * n_times + ev[0] - event[0] + t_zero,
+                   idx * n_times + ev[0] - event[0] + t_zero]
+            kwargs = {} if ev[2] not in color else {'color': color[ev[2]]}
+            params['ev_lines'].append(ax.plot(pos, ax.get_ylim(),
+                                              zorder=3, **kwargs)[0])
+            params['ev_texts'].append(ax.text(pos[0], ax.get_ylim()[0],
+                                              ev[2], color=color[ev[2]],
+                                              ha='center', va='top'))
diff --git a/mne/viz/evoked.py b/mne/viz/evoked.py
index 42abbe8..c858bed 100644
--- a/mne/viz/evoked.py
+++ b/mne/viz/evoked.py
@@ -1,5 +1,4 @@
-"""Functions to make simple plot on evoked M/EEG data (besides topographies)
-"""
+"""Functions to plot evoked M/EEG data (besides topographies)."""
 from __future__ import print_function
 
 # Authors: Alexandre Gramfort <alexandre.gramfort at telecom-paristech.fr>
@@ -12,27 +11,32 @@ from __future__ import print_function
 # License: Simplified BSD
 
 from functools import partial
+from copy import deepcopy
+from numbers import Integral
 
 import numpy as np
 
 from ..io.pick import (channel_type, pick_types, _picks_by_type,
-                       _pick_data_channels, _DATA_CH_TYPES_SPLIT)
+                       _pick_data_channels, _VALID_CHANNEL_TYPES)
 from ..externals.six import string_types
 from ..defaults import _handle_default
 from .utils import (_draw_proj_checkbox, tight_layout, _check_delayed_ssp,
-                    plt_show, _process_times, DraggableColorbar)
-from ..utils import logger, _clean_names, warn
-from ..io.pick import pick_info
+                    plt_show, _process_times, DraggableColorbar, _setup_cmap,
+                    _setup_vmin_vmax, _grad_pair_pick_and_name)
+from ..utils import logger, _clean_names, warn, _pl, verbose
+from ..io.pick import pick_info, _DATA_CH_TYPES_SPLIT
+from ..io.proc_history import _get_rank_sss
+
 from .topo import _plot_evoked_topo
+from .utils import COLORS, _setup_ax_spines
 from .topomap import (_prepare_topo_plot, plot_topomap, _check_outlines,
-                      _draw_outlines, _prepare_topomap, _topomap_animation)
-from ..channels import find_layout
-from ..channels.layout import (_pair_grad_sensors, generate_2d_layout,
-                               _auto_topomap_coords)
+                      _draw_outlines, _prepare_topomap, _topomap_animation,
+                      _set_contour_locator)
+from ..channels.layout import _pair_grad_sensors, _auto_topomap_coords
 
 
 def _butterfly_onpick(event, params):
-    """Helper to add a channel name on click"""
+    """Add a channel name on click."""
     params['need_draw'] = True
     ax = event.artist.axes
     ax_idx = np.where([ax is a for a in params['axes']])[0]
@@ -58,7 +62,7 @@ def _butterfly_onpick(event, params):
 
 
 def _butterfly_on_button_press(event, params):
-    """Helper to only draw once for picking"""
+    """Only draw once for picking."""
     if params['need_draw']:
         event.canvas.draw()
     else:
@@ -71,12 +75,16 @@ def _butterfly_on_button_press(event, params):
     params['need_draw'] = False
 
 
-def _butterfly_onselect(xmin, xmax, ch_types, evoked, text=None):
-    """Function for drawing topomaps from the selected area."""
+def _line_plot_onselect(xmin, xmax, ch_types, info, data, times, text=None,
+                        psd=False):
+    """Draw topomaps from the selected area."""
     import matplotlib.pyplot as plt
     ch_types = [type_ for type_ in ch_types if type_ in ('eeg', 'grad', 'mag')]
+    if len(ch_types) == 0:
+        raise ValueError('Interactive topomaps only allowed for EEG '
+                         'and MEG channels.')
     if ('grad' in ch_types and
-            len(_pair_grad_sensors(evoked.info, topomap_coords=False,
+            len(_pair_grad_sensors(info, topomap_coords=False,
                                    raise_error=False)) < 2):
         ch_types.remove('grad')
         if len(ch_types) == 0:
@@ -86,36 +94,44 @@ def _butterfly_onselect(xmin, xmax, ch_types, evoked, text=None):
     if text is not None:
         text.set_visible(True)
         ax = text.axes
-        ylim = ax.get_ylim()
-        vert_lines.append(ax.plot([xmin, xmin], ylim, zorder=0, color='red'))
-        vert_lines.append(ax.plot([xmax, xmax], ylim, zorder=0, color='red'))
-        fill = ax.fill_betweenx(ylim, x1=xmin, x2=xmax, alpha=0.2,
-                                color='green')
+        vert_lines.append(ax.axvline(xmin, zorder=0, color='red'))
+        vert_lines.append(ax.axvline(xmax, zorder=0, color='red'))
+        fill = ax.axvspan(xmin, xmax, alpha=0.2, color='green')
         evoked_fig = plt.gcf()
         evoked_fig.canvas.draw()
         evoked_fig.canvas.flush_events()
-    times = evoked.times
-    xmin *= 0.001
+
     minidx = np.abs(times - xmin).argmin()
-    xmax *= 0.001
     maxidx = np.abs(times - xmax).argmin()
     fig, axarr = plt.subplots(1, len(ch_types), squeeze=False,
                               figsize=(3 * len(ch_types), 3))
+
     for idx, ch_type in enumerate(ch_types):
+        if ch_type not in ('eeg', 'grad', 'mag'):
+            continue
         picks, pos, merge_grads, _, ch_type = _prepare_topo_plot(
-            evoked, ch_type, layout=None)
-        data = evoked.data[picks, minidx:maxidx]
+            info, ch_type, layout=None)
+        if len(pos) < 2:
+            fig.delaxes(axarr[0][idx])
+            continue
+        this_data = data[picks, minidx:maxidx]
         if merge_grads:
             from ..channels.layout import _merge_grad_data
-            data = _merge_grad_data(data)
-            title = '%s RMS' % ch_type
+            method = 'mean' if psd else 'rms'
+            this_data = _merge_grad_data(this_data, method=method)
+            title = '%s %s' % (ch_type, method.upper())
         else:
             title = ch_type
-        data = np.average(data, axis=1)
+        this_data = np.average(this_data, axis=1)
         axarr[0][idx].set_title(title)
-        plot_topomap(data, pos, axes=axarr[0][idx], show=False)
-
-    fig.suptitle('Average over %.2fs - %.2fs' % (xmin, xmax), fontsize=15,
+        vmin = min(this_data) if psd else None
+        vmax = max(this_data) if psd else None  # All negative for dB psd.
+        cmap = 'Reds' if psd else None
+        plot_topomap(this_data, pos, cmap=cmap, vmin=vmin, vmax=vmax,
+                     axes=axarr[0][idx], show=False)
+
+    unit = 'Hz' if psd else 'ms'
+    fig.suptitle('Average over %.2f%s - %.2f%s' % (xmin, unit, xmax, unit),
                  y=0.1)
     tight_layout(pad=2.0, fig=fig)
     plt_show()
@@ -129,49 +145,42 @@ def _butterfly_onselect(xmin, xmax, ch_types, evoked, text=None):
 
 
 def _topo_closed(events, ax, lines, fill):
-    """Callback for removing lines from evoked plot as topomap is closed."""
+    """Remove lines from evoked plot as topomap is closed."""
     for line in lines:
-        ax.lines.remove(line[0])
-    ax.collections.remove(fill)
+        ax.lines.remove(line)
+    ax.patches.remove(fill)
     ax.get_figure().canvas.draw()
 
 
-def _rgb(info, x, y, z):
-    """Helper to transform x, y, z values into RGB colors"""
-    all_pos = np.array([ch['loc'][:3] for ch in info['chs']])
-    for idx, dim in enumerate([x, y, z]):
-        this_pos = all_pos[:, idx]
-        dim_min = this_pos.min()
-        dim_max = (this_pos - dim_min).max()
-        dim -= dim_min
-        dim /= dim_max
-    return np.asarray([x, y, z]).T
+def _rgb(x, y, z):
+    """Transform x, y, z values into RGB colors."""
+    rgb = np.array([x, y, z]).T
+    rgb -= rgb.min(0)
+    rgb /= np.maximum(rgb.max(0), 1e-16)  # avoid div by zero
+    return rgb
 
 
-def _plot_legend(pos, colors, axis, bads, outlines):
-    """Helper function to plot color/channel legends for butterfly plots
-    with spatial colors"""
-    from mpl_toolkits.axes_grid.inset_locator import inset_axes
+def _plot_legend(pos, colors, axis, bads, outlines, loc, size=30):
+    """Plot (possibly colorized) channel legends for evoked plots."""
+    from mpl_toolkits.axes_grid1.inset_locator import inset_axes
     bbox = axis.get_window_extent()  # Determine the correct size.
     ratio = bbox.width / bbox.height
-    ax = inset_axes(axis, width=str(30 / ratio) + '%', height='30%', loc=2)
+    ax = inset_axes(axis, width=str(size / ratio) + '%',
+                    height=str(size) + '%', loc=loc)
     pos_x, pos_y = _prepare_topomap(pos, ax)
-    ax.scatter(pos_x, pos_y, color=colors, s=25, marker='.', zorder=1)
-    for idx in bads:
-        ax.scatter(pos_x[idx], pos_y[idx], s=5, marker='.', color='w',
+    ax.scatter(pos_x, pos_y, color=colors, s=size * .8, marker='.', zorder=1)
+    if bads:
+        bads = np.array(bads)
+        ax.scatter(pos_x[bads], pos_y[bads], s=size / 6, marker='.', color='w',
                    zorder=1)
-
-    if isinstance(outlines, dict):
-        _draw_outlines(ax, outlines)
+    _draw_outlines(ax, outlines)
 
 
-def _plot_evoked(evoked, picks, exclude, unit, show,
-                 ylim, proj, xlim, hline, units,
-                 scalings, titles, axes, plot_type,
-                 cmap=None, gfp=False, window_title=None,
-                 spatial_colors=False, set_tight_layout=True,
-                 selectable=True, zorder='unsorted'):
-    """Aux function for plot_evoked and plot_evoked_image (cf. docstrings)
+def _plot_evoked(evoked, picks, exclude, unit, show, ylim, proj, xlim, hline,
+                 units, scalings, titles, axes, plot_type, cmap=None,
+                 gfp=False, window_title=None, spatial_colors=False,
+                 set_tight_layout=True, selectable=True, zorder='unsorted'):
+    """Aux function for plot_evoked and plot_evoked_image (cf. docstrings).
 
     Extra param is:
 
@@ -183,25 +192,16 @@ def _plot_evoked(evoked, picks, exclude, unit, show,
         interactive.
     """
     import matplotlib.pyplot as plt
-    from matplotlib import patheffects
-    from matplotlib.widgets import SpanSelector
     info = evoked.info
     if axes is not None and proj == 'interactive':
         raise RuntimeError('Currently only single axis figures are supported'
                            ' for interactive SSP selection.')
     if isinstance(gfp, string_types) and gfp != 'only':
         raise ValueError('gfp must be boolean or "only". Got %s' % gfp)
-    if cmap == 'interactive':
-        cmap = (None, True)
-    elif not isinstance(cmap, tuple):
-        cmap = (cmap, True)
+
     scalings = _handle_default('scalings', scalings)
     titles = _handle_default('titles', titles)
     units = _handle_default('units', units)
-    # Valid data types ordered for consistency
-    valid_channel_types = ['eeg', 'grad', 'mag', 'seeg', 'eog', 'ecg', 'emg',
-                           'dipole', 'gof', 'bio', 'ecog', 'hbo', 'hbr',
-                           'misc']
 
     if picks is None:
         picks = list(range(info['nchan']))
@@ -222,32 +222,33 @@ def _plot_evoked(evoked, picks, exclude, unit, show,
     picks = np.array(picks)
 
     types = np.array([channel_type(info, idx) for idx in picks])
-    n_channel_types = 0
-    ch_types_used = []
-    for t in valid_channel_types:
-        if t in types:
-            n_channel_types += 1
-            ch_types_used.append(t)
-
-    axes_init = axes  # remember if axes were given as input
+    ch_types_used = list()
+    for this_type in _VALID_CHANNEL_TYPES:
+        if this_type in types:
+            ch_types_used.append(this_type)
 
     fig = None
     if axes is None:
-        fig, axes = plt.subplots(n_channel_types, 1)
+        fig, axes = plt.subplots(len(ch_types_used), 1)
+        plt.subplots_adjust(0.175, 0.08, 0.94, 0.94, 0.2, 0.63)
+        if isinstance(axes, plt.Axes):
+            axes = [axes]
+        fig.set_size_inches(6.4, 2 + len(axes))
 
     if isinstance(axes, plt.Axes):
         axes = [axes]
     elif isinstance(axes, np.ndarray):
         axes = list(axes)
 
-    if axes_init is not None:
+    if fig is None:
         fig = axes[0].get_figure()
+
     if window_title is not None:
         fig.canvas.set_window_title(window_title)
 
-    if not len(axes) == n_channel_types:
+    if len(axes) != len(ch_types_used):
         raise ValueError('Number of axes (%g) must match number of channel '
-                         'types (%d: %s)' % (len(axes), n_channel_types,
+                         'types (%d: %s)' % (len(axes), len(ch_types_used),
                                              sorted(ch_types_used)))
 
     # instead of projecting during each iteration let's use the mixin here.
@@ -255,205 +256,255 @@ def _plot_evoked(evoked, picks, exclude, unit, show,
         evoked = evoked.copy()
         evoked.apply_proj()
 
-    times = 1e3 * evoked.times  # time in milliseconds
+    if plot_type == 'butterfly':
+        times = evoked.times * 1e3  # time in milliseconds
+        _plot_lines(evoked.data, info, picks, fig, axes, spatial_colors, unit,
+                    units, scalings, hline, gfp, types, zorder, xlim, ylim,
+                    times, bad_ch_idx, titles, ch_types_used, selectable,
+                    False, line_alpha=1.)
+        for ax in axes:
+            ax.set_xlabel('time (ms)')
+
+    elif plot_type == 'image':
+        for ax, this_type in zip(axes, ch_types_used):
+            this_picks = list(picks[types == this_type])
+            _plot_image(evoked.data, ax, this_type, this_picks, cmap, unit,
+                        units, scalings, evoked.times, xlim, ylim, titles)
+    if proj == 'interactive':
+        _check_delayed_ssp(evoked)
+        params = dict(evoked=evoked, fig=fig, projs=info['projs'], axes=axes,
+                      types=types, units=units, scalings=scalings, unit=unit,
+                      ch_types_used=ch_types_used, picks=picks,
+                      plot_update_proj_callback=_plot_update_evoked,
+                      plot_type=plot_type)
+        _draw_proj_checkbox(None, params)
+
+    for ax in fig.axes[:len(ch_types_used) - 1]:
+        ax.set_xlabel('')
+    fig.canvas.draw()  # for axes plots update axes.
+    if set_tight_layout:
+        tight_layout(fig=fig)
+    plt_show(show)
+    return fig
+
+
+def _plot_lines(data, info, picks, fig, axes, spatial_colors, unit, units,
+                scalings, hline, gfp, types, zorder, xlim, ylim, times,
+                bad_ch_idx, titles, ch_types_used, selectable, psd,
+                line_alpha):
+    """Plot data as butterfly plot."""
+    from matplotlib import patheffects
+    from matplotlib.widgets import SpanSelector
     texts = list()
     idxs = list()
     lines = list()
-    selectors = list()  # for keeping reference to span_selectors
     path_effects = [patheffects.withStroke(linewidth=2, foreground="w",
                                            alpha=0.75)]
     gfp_path_effects = [patheffects.withStroke(linewidth=5, foreground="w",
                                                alpha=0.75)]
-    for ax, t in zip(axes, ch_types_used):
+    if selectable:
+        selectables = np.ones(len(ch_types_used), dtype=bool)
+        for type_idx, this_type in enumerate(ch_types_used):
+            idx = picks[types == this_type]
+            if len(idx) < 2 or (this_type == 'grad' and len(idx) < 4):
+                # prevent unnecessary warnings for e.g. EOG
+                if this_type in _DATA_CH_TYPES_SPLIT:
+                    logger.info('Need more than one channel to make '
+                                'topography for %s. Disabling interactivity.'
+                                % (this_type,))
+                selectables[type_idx] = False
+
+    if selectable:
+        # Parameters for butterfly interactive plots
+        params = dict(axes=axes, texts=texts, lines=lines,
+                      ch_names=info['ch_names'], idxs=idxs, need_draw=False,
+                      path_effects=path_effects)
+        fig.canvas.mpl_connect('pick_event',
+                               partial(_butterfly_onpick, params=params))
+        fig.canvas.mpl_connect('button_press_event',
+                               partial(_butterfly_on_button_press,
+                                       params=params))
+    for ax, this_type in zip(axes, ch_types_used):
         line_list = list()  # 'line_list' contains the lines for this axes
-        ch_unit = units[t]
-        this_scaling = scalings[t]
+        ch_unit = units[this_type]
+        this_scaling = 1. if scalings is None else scalings[this_type]
         if unit is False:
             this_scaling = 1.0
             ch_unit = 'NA'  # no unit
-        idx = list(picks[types == t])
+        idx = list(picks[types == this_type])
         idxs.append(idx)
+
         if len(idx) > 0:
             # Set amplitude scaling
-            D = this_scaling * evoked.data[idx, :]
-            # Parameters for butterfly interactive plots
-            if plot_type == 'butterfly':
-                text = ax.annotate('Loading...', xy=(0.01, 0.1),
-                                   xycoords='axes fraction', fontsize=20,
-                                   color='green', zorder=3)
-                text.set_visible(False)
-                if selectable:
-                    callback_onselect = partial(
-                        _butterfly_onselect, ch_types=ch_types_used,
-                        evoked=evoked, text=text)
-                    blit = False if plt.get_backend() == 'MacOSX' else True
-                    selectors.append(SpanSelector(
-                        ax, callback_onselect, 'horizontal', minspan=10,
-                        useblit=blit, rectprops=dict(alpha=0.5,
-                                                     facecolor='red')))
-
-                gfp_only = (isinstance(gfp, string_types) and gfp == 'only')
-                if not gfp_only:
-                    if spatial_colors:
-                        chs = [info['chs'][i] for i in idx]
-                        locs3d = np.array([ch['loc'][:3] for ch in chs])
-                        x, y, z = locs3d.T
-                        colors = _rgb(info, x, y, z)
-                        if t in ('meg', 'mag', 'grad', 'eeg'):
-                            layout = find_layout(info, ch_type=t, exclude=[])
-                        else:
-                            layout = find_layout(info, None, exclude=[])
-                        # drop channels that are not in the data
-                        used_nm = np.array(_clean_names(info['ch_names']))[idx]
-                        names = np.asarray([name for name in used_nm
-                                            if name in layout.names])
-                        name_idx = [layout.names.index(name) for name in names]
-                        if len(name_idx) < len(chs):
-                            warn('Could not find layout for all the channels. '
-                                 'Generating custom layout from channel '
-                                 'positions.')
-                            xy = _auto_topomap_coords(info, idx, True)
-                            layout = generate_2d_layout(
-                                xy[idx], ch_names=list(used_nm), name='custom')
-                            names = used_nm
-                            name_idx = [layout.names.index(name) for name in
-                                        names]
-
-                        # find indices for bads
-                        bads = [np.where(names == bad)[0][0] for bad in
-                                info['bads'] if bad in names]
-                        pos, outlines = _check_outlines(layout.pos[:, :2],
-                                                        'skirt', None)
-                        pos = pos[name_idx]
-                        _plot_legend(pos, colors, ax, bads, outlines)
-                    else:
-                        colors = ['k'] * len(idx)
-                        for i in bad_ch_idx:
-                            if i in idx:
-                                colors[idx.index(i)] = 'r'
-
-                    if zorder == 'std':
-                        # find the channels with the least activity
-                        # to map them in front of the more active ones
-                        z_ord = D.std(axis=1).argsort()
-                    elif zorder == 'unsorted':
-                        z_ord = list(range(D.shape[0]))
-                    elif not callable(zorder):
-                        error = ('`zorder` must be a function, "std" '
-                                 'or "unsorted", not {0}.')
-                        raise TypeError(error.format(type(zorder)))
-                    else:
-                        z_ord = zorder(D)
-
-                    # plot channels
-                    for ch_idx, z in enumerate(z_ord):
-                        line_list.append(
-                            ax.plot(times, D[ch_idx], picker=3.,
-                                    zorder=z + 1 if spatial_colors else 1,
-                                    color=colors[ch_idx])[0])
-
-                if gfp:  # 'only' or boolean True
-                    gfp_color = 3 * (0.,) if spatial_colors else (0., 1., 0.)
-                    this_gfp = np.sqrt((D * D).mean(axis=0))
-                    this_ylim = ax.get_ylim() if (ylim is None or t not in
-                                                  ylim.keys()) else ylim[t]
-                    if not gfp_only:
-                        y_offset = this_ylim[0]
+            D = this_scaling * data[idx, :]
+            gfp_only = (isinstance(gfp, string_types) and gfp == 'only')
+            if not gfp_only:
+                chs = [info['chs'][i] for i in idx]
+                locs3d = np.array([ch['loc'][:3] for ch in chs])
+                if spatial_colors is True and (locs3d == 0).all():
+                    warn('Channel locations not available. Disabling spatial '
+                         'colors.')
+                    spatial_colors = selectable = False
+                if spatial_colors is True and len(idx) != 1:
+                    x, y, z = locs3d.T
+                    colors = _rgb(x, y, z)
+                    _handle_spatial_colors(colors, info, idx, this_type, psd,
+                                           ax)
+                else:
+                    if isinstance(spatial_colors, (tuple, string_types)):
+                        col = [spatial_colors]
                     else:
-                        y_offset = 0.
-                    this_gfp += y_offset
-                    ax.fill_between(times, y_offset, this_gfp, color='none',
-                                    facecolor=gfp_color, zorder=1, alpha=0.25)
-                    line_list.append(ax.plot(times, this_gfp, color=gfp_color,
-                                             zorder=3)[0])
-                    ax.text(times[0] + 0.01 * (times[-1] - times[0]),
-                            this_gfp[0] + 0.05 * np.diff(ax.get_ylim())[0],
-                            'GFP', zorder=4, color=gfp_color,
-                            path_effects=gfp_path_effects)
-                for ii, line in zip(idx, line_list):
-                    if ii in bad_ch_idx:
-                        line.set_zorder(2)
-                        if spatial_colors:
-                            line.set_linestyle("--")
-                ax.set_ylabel('data (%s)' % ch_unit)
-                # for old matplotlib, we actually need this to have a bounding
-                # box (!), so we have to put some valid text here, change
-                # alpha and path effects later
-                texts.append(ax.text(0, 0, 'blank', zorder=3,
-                                     verticalalignment='baseline',
-                                     horizontalalignment='left',
-                                     fontweight='bold', alpha=0))
-            elif plot_type == 'image':
-                im = ax.imshow(D, interpolation='nearest', origin='lower',
-                               extent=[times[0], times[-1], 0, D.shape[0]],
-                               aspect='auto', cmap=cmap[0])
-                cbar = plt.colorbar(im, ax=ax)
-                cbar.ax.set_title(ch_unit)
-                if cmap[1]:
-                    ax.CB = DraggableColorbar(cbar, im)
-                ax.set_ylabel('channels (%s)' % 'index')
-            else:
-                raise ValueError("plot_type has to be 'butterfly' or 'image'."
-                                 "Got %s." % plot_type)
+                        col = ['k']
+                    colors = col * len(idx)
+                    for i in bad_ch_idx:
+                        if i in idx:
+                            colors[idx.index(i)] = 'r'
+
+                if zorder == 'std':
+                    # find the channels with the least activity
+                    # to map them in front of the more active ones
+                    z_ord = D.std(axis=1).argsort()
+                elif zorder == 'unsorted':
+                    z_ord = list(range(D.shape[0]))
+                elif not callable(zorder):
+                    error = ('`zorder` must be a function, "std" '
+                             'or "unsorted", not {0}.')
+                    raise TypeError(error.format(type(zorder)))
+                else:
+                    z_ord = zorder(D)
+
+                # plot channels
+                for ch_idx, z in enumerate(z_ord):
+                    line_list.append(
+                        ax.plot(times, D[ch_idx], picker=3.,
+                                zorder=z + 1 if spatial_colors is True else 1,
+                                color=colors[ch_idx], alpha=line_alpha,
+                                linewidth=0.5)[0])
+
+            if gfp:  # 'only' or boolean True
+                gfp_color = 3 * (0.,) if spatial_colors is True else (0., 1.,
+                                                                      0.)
+                this_gfp = np.sqrt((D * D).mean(axis=0))
+                this_ylim = ax.get_ylim() if (ylim is None or this_type not in
+                                              ylim.keys()) else ylim[this_type]
+                if gfp_only:
+                    y_offset = 0.
+                else:
+                    y_offset = this_ylim[0]
+                this_gfp += y_offset
+                ax.fill_between(times, y_offset, this_gfp, color='none',
+                                facecolor=gfp_color, zorder=1, alpha=0.2)
+                line_list.append(ax.plot(times, this_gfp, color=gfp_color,
+                                         zorder=3, alpha=line_alpha)[0])
+                ax.text(times[0] + 0.01 * (times[-1] - times[0]),
+                        this_gfp[0] + 0.05 * np.diff(ax.get_ylim())[0],
+                        'GFP', zorder=4, color=gfp_color,
+                        path_effects=gfp_path_effects)
+            for ii, line in zip(idx, line_list):
+                if ii in bad_ch_idx:
+                    line.set_zorder(2)
+                    if spatial_colors is True:
+                        line.set_linestyle("--")
+            ax.set_ylabel(ch_unit)
+            # for old matplotlib, we actually need this to have a bounding
+            # box (!), so we have to put some valid text here, change
+            # alpha and path effects later
+            texts.append(ax.text(0, 0, 'blank', zorder=3,
+                                 verticalalignment='baseline',
+                                 horizontalalignment='left',
+                                 fontweight='bold', alpha=0))
+
             if xlim is not None:
                 if xlim == 'tight':
                     xlim = (times[0], times[-1])
                 ax.set_xlim(xlim)
-            if ylim is not None and t in ylim:
-                if plot_type == 'butterfly':
-                    ax.set_ylim(ylim[t])
-                elif plot_type == 'image':
-                    im.set_clim(ylim[t])
-            ax.set_title(titles[t] + ' (%d channel%s)' % (
-                         len(D), 's' if len(D) > 1 else ''))
-            ax.set_xlabel('time (ms)')
+            if ylim is not None and this_type in ylim:
+                ax.set_ylim(ylim[this_type])
+            ax.set_title(titles[this_type] + ' (%d channel%s)' % (len(D),
+                                                                  _pl(D)))
 
-            if (plot_type == 'butterfly') and (hline is not None):
+            if hline is not None:
                 for h in hline:
-                    c = ('r' if not spatial_colors else 'grey')
+                    c = ('grey' if spatial_colors is True else 'r')
                     ax.axhline(h, linestyle='--', linewidth=2, color=c)
         lines.append(line_list)
-    if plot_type == 'butterfly':
-        params = dict(axes=axes, texts=texts, lines=lines,
-                      ch_names=info['ch_names'], idxs=idxs, need_draw=False,
-                      path_effects=path_effects, selectors=selectors)
-        fig.canvas.mpl_connect('pick_event',
-                               partial(_butterfly_onpick, params=params))
-        fig.canvas.mpl_connect('button_press_event',
-                               partial(_butterfly_on_button_press,
-                                       params=params))
-
-    if axes_init is None:
-        plt.subplots_adjust(0.175, 0.08, 0.94, 0.94, 0.2, 0.63)
-
-    if proj == 'interactive':
-        _check_delayed_ssp(evoked)
-        params = dict(evoked=evoked, fig=fig, projs=info['projs'], axes=axes,
-                      types=types, units=units, scalings=scalings, unit=unit,
-                      ch_types_used=ch_types_used, picks=picks,
-                      plot_update_proj_callback=_plot_update_evoked,
-                      plot_type=plot_type)
-        _draw_proj_checkbox(None, params)
-
-    plt_show(show)
-    fig.canvas.draw()  # for axes plots update axes.
-    if set_tight_layout:
-        tight_layout(fig=fig)
-
-    return fig
-
-
+    if selectable:
+        import matplotlib.pyplot as plt
+        for ax in np.array(axes)[selectables]:
+            if len(ax.lines) == 1:
+                continue
+            text = ax.annotate('Loading...', xy=(0.01, 0.1),
+                               xycoords='axes fraction', fontsize=20,
+                               color='green', zorder=3)
+            text.set_visible(False)
+            callback_onselect = partial(_line_plot_onselect,
+                                        ch_types=ch_types_used, info=info,
+                                        data=data, times=times, text=text,
+                                        psd=psd)
+            blit = False if plt.get_backend() == 'MacOSX' else True
+            minspan = 0 if len(times) < 2 else times[1] - times[0]
+            ax._span_selector = SpanSelector(
+                ax, callback_onselect, 'horizontal', minspan=minspan,
+                useblit=blit, rectprops=dict(alpha=0.5, facecolor='red'))
+
+
+def _handle_spatial_colors(colors, info, idx, ch_type, psd, ax):
+    """Set up spatial colors."""
+    used_nm = np.array(_clean_names(info['ch_names']))[idx]
+    # find indices for bads
+    bads = [np.where(used_nm == bad)[0][0] for bad in info['bads'] if bad in
+            used_nm]
+    pos = _auto_topomap_coords(info, idx, ignore_overlap=True, to_sphere=True)
+    pos, outlines = _check_outlines(pos, np.array([1, 1]),
+                                    {'center': (0, 0), 'scale': (0.5, 0.5)})
+    loc = 1 if psd else 2  # Legend in top right for psd plot.
+    _plot_legend(pos, colors, ax, bads, outlines, loc)
+
+
+def _plot_image(data, ax, this_type, picks, cmap, unit, units, scalings, times,
+                xlim, ylim, titles):
+    """Plot images."""
+    import matplotlib.pyplot as plt
+    cmap = _setup_cmap(cmap)
+    ch_unit = units[this_type]
+    this_scaling = scalings[this_type]
+    if unit is False:
+        this_scaling = 1.0
+        ch_unit = 'NA'  # no unit
+
+    # Set amplitude scaling
+    data = this_scaling * data[picks, :]
+    im = ax.imshow(data, interpolation='nearest', origin='lower',
+                   extent=[times[0], times[-1], 0, data.shape[0]],
+                   aspect='auto', cmap=cmap[0])
+    if xlim is not None:
+        if xlim == 'tight':
+            xlim = (times[0], times[-1])
+        ax.set_xlim(xlim)
+        if ylim is not None and this_type in ylim:
+            im.set_clim(ylim[this_type])
+    cbar = plt.colorbar(im, ax=ax)
+    cbar.ax.set_title(ch_unit)
+    if cmap[1]:
+        ax.CB = DraggableColorbar(cbar, im)
+    ax.set_ylabel('channels (index)')
+    ax.set_title(titles[this_type] + ' (%d channel%s)' % (
+                 len(data), _pl(data)))
+    ax.set_xlabel('time (ms)')
+
+
+ at verbose
 def plot_evoked(evoked, picks=None, exclude='bads', unit=True, show=True,
                 ylim=None, xlim='tight', proj=False, hline=None, units=None,
                 scalings=None, titles=None, axes=None, gfp=False,
                 window_title=None, spatial_colors=False, zorder='unsorted',
-                selectable=True):
-    """Plot evoked data using butteryfly plots
+                selectable=True, verbose=None):
+    """Plot evoked data using butteryfly plots.
 
     Left click to a line shows the channel name. Selecting an area by clicking
     and holding left mouse button plots a topographic map of the painted area.
 
-    Note: If bad channels are not excluded they are shown in red.
+    .. note:: If bad channels are not excluded they are shown in red.
 
     Parameters
     ----------
@@ -485,8 +536,8 @@ def plot_evoked(evoked, picks=None, exclude='bads', unit=True, show=True,
         The units of the channel types used for axes lables. If None,
         defaults to `dict(eeg='uV', grad='fT/cm', mag='fT')`.
     scalings : dict | None
-        The scalings of the channel types to be applied for plotting. If None,`
-        defaults to `dict(eeg=1e6, grad=1e13, mag=1e15)`.
+        The scalings of the channel types to be applied for plotting. If None,
+        defaults to ``dict(eeg=1e6, grad=1e13, mag=1e15)``.
     titles : dict | None
         The titles associated with the channels. If None, defaults to
         `dict(eeg='EEG', grad='Gradiometers', mag='Magnetometers')`.
@@ -526,6 +577,10 @@ def plot_evoked(evoked, picks=None, exclude='bads', unit=True, show=True,
 
         .. versionadded:: 0.13.0
 
+    verbose : bool, str, int, or None
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
+
     Returns
     -------
     fig : instance of matplotlib.figure.Figure
@@ -542,9 +597,10 @@ def plot_evoked(evoked, picks=None, exclude='bads', unit=True, show=True,
 
 def plot_evoked_topo(evoked, layout=None, layout_scale=0.945, color=None,
                      border='none', ylim=None, scalings=None, title=None,
-                     proj=False, vline=[0.0], fig_facecolor='k',
-                     fig_background=None, axis_facecolor='k', font_color='w',
-                     merge_grads=False, show=True):
+                     proj=False, vline=[0.0], fig_facecolor=None,
+                     fig_background=None, axis_facecolor=None, font_color='w',
+                     merge_grads=False, legend=True, axes=None,
+                     background_color=None, show=True):
     """Plot 2D topography of evoked responses.
 
     Clicking on the plot of an individual sensor opens a new figure showing
@@ -586,16 +642,43 @@ def plot_evoked_topo(evoked, layout=None, layout_scale=0.945, color=None,
         The values at which to show a vertical line.
     fig_facecolor : str | obj
         The figure face color. Defaults to black.
+
+        .. note:: The parameter will be removed in version v0.16.
+                  Use background_color parameter instead.
+
     fig_background : None | numpy ndarray
         A background image for the figure. This must work with a call to
         plt.imshow. Defaults to None.
     axis_facecolor : str | obj
         The face color to be used for each sensor plot. Defaults to black.
+
+        .. note:: The parameter will be removed in version v0.16.
+                  Use background_color parameter instead.
+
     font_color : str | obj
         The color of text in the colorbar and title. Defaults to white.
+
+        .. note:: The parameter will be removed in version v0.16.
+                  Use background_color parameter instead.
+
     merge_grads : bool
         Whether to use RMS value of gradiometer pairs. Only works for Neuromag
         data. Defaults to False.
+    legend : bool | int | string | tuple
+        If True, create a legend based on evoked.comment. If False, disable the
+        legend. Otherwise, the legend is created and the parameter value is
+        passed as the location parameter to the matplotlib legend call. It can
+        be an integer (e.g. 0 corresponds to upper right corner of the plot),
+        a string (e.g. 'upper right'), or a tuple (x, y coordinates of the
+        lower left corner of the legend in the axes coordinate system).
+        See matplotlib documentation for more details.
+    axes : instance of matplotlib Axes | None
+        Axes to plot into. If None, axes will be created.
+    background_color : str | obj
+        Background color. Typically 'k' (black) or 'w' (white).
+        It will be set to 'w' by default in v0.16
+
+        .. versionadded:: 0.15.0
     show : bool
         Show figure if True.
 
@@ -604,6 +687,49 @@ def plot_evoked_topo(evoked, layout=None, layout_scale=0.945, color=None,
     fig : instance of matplotlib.figure.Figure
         Images of evoked responses at sensor locations
     """
+    from matplotlib.colors import colorConverter
+
+    if not type(evoked) in (tuple, list):
+        evoked = [evoked]
+
+    dark_background = None
+    if background_color is not None:
+        dark_background = \
+            np.mean(colorConverter.to_rgb(background_color)) < 0.5
+
+    if dark_background is not None:
+        if dark_background:
+            fig_facecolor = background_color
+            axis_facecolor = background_color
+            font_color = 'w'
+        else:
+            fig_facecolor = background_color
+            axis_facecolor = background_color
+            font_color = 'k'
+
+        if color is None:
+            if dark_background:
+                color = ['w'] + COLORS
+            else:
+                # default colors from M Waskom's Seaborn
+                color = ['#e41a1c', '#377eb8', '#4daf4a', '#984ea3', '#ff7f00',
+                         '#1b9e77', '#d95f02', '#7570b3', '#e7298a', '#66a61e']
+            color = color * ((len(evoked) % len(color)) + 1)
+            color = color[:len(evoked)]
+
+    if fig_facecolor is None:
+        fig_facecolor = 'k'
+        warn('axis_facecolor is deprecated and will be removed in v0.16. Use '
+             'background_color parameter or change matplotlib defaults.')
+    if axis_facecolor is None:
+        axis_facecolor = 'k'
+        warn('axis_facecolor is deprecated and will be removed in v0.16. Use '
+             'background_color parameter or change matplotlib defaults.')
+    if font_color is None:
+        font_color = 'w'
+        warn('font_color is deprecated and will be removed in v0.16. Use '
+             'background_color parameter or change matplotlib defaults.')
+
     return _plot_evoked_topo(evoked=evoked, layout=layout,
                              layout_scale=layout_scale, color=color,
                              border=border, ylim=ylim, scalings=scalings,
@@ -612,14 +738,16 @@ def plot_evoked_topo(evoked, layout=None, layout_scale=0.945, color=None,
                              fig_background=fig_background,
                              axis_facecolor=axis_facecolor,
                              font_color=font_color, merge_grads=merge_grads,
-                             show=show)
+                             legend=legend, axes=axes, show=show)
 
 
 def _animate_evoked_topomap(evoked, ch_type='mag', times=None, frame_rate=None,
                             butterfly=False, blit=True, show=True):
-    """Make animation of evoked data as topomap timeseries. Animation can be
-    paused/resumed with left mouse button. Left and right arrow keys can be
-    used to move backward or forward in time
+    """Make animation of evoked data as topomap timeseries.
+
+    The animation can be paused/resumed with left mouse button.
+    Left and right arrow keys can be used to move backward or forward in
+    time.
 
     Parameters
     ----------
@@ -664,7 +792,7 @@ def _animate_evoked_topomap(evoked, ch_type='mag', times=None, frame_rate=None,
 def plot_evoked_image(evoked, picks=None, exclude='bads', unit=True, show=True,
                       clim=None, xlim='tight', proj=False, units=None,
                       scalings=None, titles=None, axes=None, cmap='RdBu_r'):
-    """Plot evoked data as images
+    """Plot evoked data as images.
 
     Parameters
     ----------
@@ -692,13 +820,13 @@ def plot_evoked_image(evoked, picks=None, exclude='bads', unit=True, show=True,
         be shown.
     units : dict | None
         The units of the channel types used for axes lables. If None,
-        defaults to `dict(eeg='uV', grad='fT/cm', mag='fT')`.
+        defaults to ``dict(eeg='uV', grad='fT/cm', mag='fT')``.
     scalings : dict | None
         The scalings of the channel types to be applied for plotting. If None,`
-        defaults to `dict(eeg=1e6, grad=1e13, mag=1e15)`.
+        defaults to ``dict(eeg=1e6, grad=1e13, mag=1e15)``.
     titles : dict | None
         The titles associated with the channels. If None, defaults to
-        `dict(eeg='EEG', grad='Gradiometers', mag='Magnetometers')`.
+        ``dict(eeg='EEG', grad='Gradiometers', mag='Magnetometers')``.
     axes : instance of Axis | list | None
         The axes to plot to. If list, the list must be a list of Axes of
         the same length as the number of channel types. If instance of
@@ -710,8 +838,8 @@ def plot_evoked_image(evoked, picks=None, exclude='bads', unit=True, show=True,
         with left and right mouse button. Left mouse button moves the scale up
         and down and right mouse button adjusts the range. Hitting space bar
         resets the scale. Up and down arrows can be used to change the
-        colormap. If 'interactive', translates to ('RdBu_r', True). Defaults to
-        'RdBu_r'.
+        colormap. If 'interactive', translates to ``('RdBu_r', True)``.
+        Defaults to ``'RdBu_r'``.
 
     Returns
     -------
@@ -726,8 +854,7 @@ def plot_evoked_image(evoked, picks=None, exclude='bads', unit=True, show=True,
 
 
 def _plot_update_evoked(params, bools):
-    """ update the plot evoked lines
-    """
+    """Update the plot evoked lines."""
     picks, evoked = [params[k] for k in ('picks', 'evoked')]
     times = evoked.times * 1e3
     projs = [proj for ii, proj in enumerate(params['projs'])
@@ -749,8 +876,8 @@ def _plot_update_evoked(params, bools):
     params['fig'].canvas.draw()
 
 
-def plot_evoked_white(evoked, noise_cov, show=True):
-    """Plot whitened evoked response
+def plot_evoked_white(evoked, noise_cov, show=True, rank=None):
+    """Plot whitened evoked response.
 
     Plots the whitened evoked response and the whitened GFP as described in
     [1]_. If one single covariance object is passed, the GFP panel (bottom)
@@ -771,6 +898,14 @@ def plot_evoked_white(evoked, noise_cov, show=True):
         The noise covariance as computed by ``mne.cov.compute_covariance``.
     show : bool
         Show figure if True.
+    rank : dict of int | None
+        Dict of ints where keys are 'eeg', 'meg', mag' or 'grad'. If None,
+        the rank is detected automatically. Defaults to None. 'mag' or
+        'grad' cannot be specified jointly with 'meg'. For SSS'd data,
+        only 'meg' is valid. For non-SSS'd data, 'mag' and/or 'grad' must be
+        specified separately. If only one is specified, the other one gets
+        estimated. Note. The rank estimation will be printed by the logger for
+        each noise covariance estimator that is passed.
 
     Returns
     -------
@@ -784,11 +919,40 @@ def plot_evoked_white(evoked, noise_cov, show=True):
            signals, vol. 108, 328-342, NeuroImage.
     """
     return _plot_evoked_white(evoked=evoked, noise_cov=noise_cov,
-                              scalings=None, rank=None, show=show)
+                              scalings=None, rank=rank, show=show)
+
+
+def _match_proj_type(proj, ch_names):
+    """See if proj should be counted."""
+    proj_ch_names = proj['data']['col_names']
+    select = any(kk in ch_names for kk in proj_ch_names)
+    return select
+
+
+def _check_estimated_rank(this_estimated_rank, this_picks, this_info, evoked,
+                          cov, ch_type, has_meg, has_sss):
+    """Compare estimated against expected rank."""
+    expected_rank = len(this_picks)
+    expected_rank_reduction = 0
+    if has_meg and has_sss and ch_type == 'meg':
+        sss_rank = _get_rank_sss(evoked)
+        expected_rank_reduction += (expected_rank - sss_rank)
+    n_ssp = sum(_match_proj_type(pp, this_info['ch_names'])
+                for pp in cov['projs'])
+    expected_rank_reduction += n_ssp
+    expected_rank -= expected_rank_reduction
+    if this_estimated_rank != expected_rank:
+        logger.debug(
+            'For (%s) the expected and estimated rank diverge '
+            '(%i VS %i). \nThis may lead to surprising reults. '
+            '\nPlease consider using the `rank` parameter to '
+            'manually specify the spatial degrees of freedom.' % (
+                ch_type, expected_rank, this_estimated_rank
+            ))
 
 
 def _plot_evoked_white(evoked, noise_cov, scalings=None, rank=None, show=True):
-    """helper to plot_evoked_white
+    """Help plot_evoked_white.
 
     Additional Parameters
     ---------------------
@@ -802,20 +966,14 @@ def _plot_evoked_white(evoked, noise_cov, scalings=None, rank=None, show=True):
         Note. Theses values were tested on different datests across various
         conditions. You should not need to update them.
 
-    rank : dict of int | None
-        Dict of ints where keys are 'eeg', 'mag' or 'grad'. If None,
-        the rank is detected automatically. Defaults to None. Note.
-        The rank estimation will be printed by the logger for each noise
-        covariance estimator that is passed.
-
     """
-
     from ..cov import whiten_evoked, read_cov  # recursive import
     from ..cov import _estimate_rank_meeg_cov
     import matplotlib.pyplot as plt
     if scalings is None:
         scalings = dict(mag=1e12, grad=1e11, eeg=1e5)
-
+    if rank is None:
+        rank = {}
     ch_used = [ch for ch in ['eeg', 'grad', 'mag'] if ch in evoked]
     has_meg = 'mag' in ch_used and 'grad' in ch_used
 
@@ -824,19 +982,32 @@ def _plot_evoked_white(evoked, noise_cov, scalings=None, rank=None, show=True):
     if not isinstance(noise_cov, (list, tuple)):
         noise_cov = [noise_cov]
 
-    proc_history = evoked.info.get('proc_history', [])
+    if 'meg' in rank and ('grad' in rank or 'mag' in rank):
+        raise ValueError('Either pass rank for mag and/or grad or for meg')
+
     has_sss = False
-    if len(proc_history) > 0:
+    if len(evoked.info['proc_history']) > 0:
         # if SSSed, mags and grad are not longer independent
         # for correct display of the whitening we will drop the cross-terms
         # (the gradiometer * magnetometer covariance)
-        has_sss = 'max_info' in proc_history[0] and has_meg
+        has_sss = (evoked.info['proc_history'][0].get('max_info') is not
+                   None and has_meg)
     if has_sss:
         logger.info('SSS has been applied to data. Showing mag and grad '
                     'whitening jointly.')
-
+        if 'mag' in rank or 'grad' in rank:
+            raise ValueError('When using SSS separate rank values for mag or '
+                             'grad are meaningless.')
+    else:
+        if 'meg' in rank:
+            raise ValueError('When not using SSS separate rank values for mag '
+                             'or grad must be passed separately.')
     evoked = evoked.copy()  # handle ref meg
-    evoked.info['projs'] = []  # either applied already or not-- else issue
+    passive_idx = [idx for idx, proj in enumerate(evoked.info['projs'])
+                   if not proj['active']]
+    # either applied already or not-- else issue
+    for idx in passive_idx[::-1]:  # reverse order so idx does not change
+        evoked.del_proj(idx)
 
     picks = pick_types(evoked.info, meg=True, eeg=True, ref_meg=False,
                        exclude='bads')
@@ -855,31 +1026,47 @@ def _plot_evoked_white(evoked, noise_cov, scalings=None, rank=None, show=True):
     n_ch_used = len(ch_used)
 
     # make sure we use the same rank estimates for GFP and whitening
-    rank_list = []
+
+    picks_list2 = [k for k in picks_list]
+    # add meg picks if needed.
+    if 'grad' in evoked and 'mag' in evoked:
+        # append ("meg", picks_meg)
+        picks_list2 += _picks_by_type(evoked.info, meg_combined=True)
+
+    rank_list = []  # rank dict for each cov
     for cov in noise_cov:
-        rank_ = {}
+        this_rank = {}
         C = cov['data'].copy()
-        picks_list2 = [k for k in picks_list]
-        if rank is None:
-            if has_meg and not has_sss:
-                picks_list2 += _picks_by_type(evoked.info,
-                                              meg_combined=True)
-            for ch_type, this_picks in picks_list2:
+        # assemble rank dict for this cov, such that we have meg
+        for ch_type, this_picks in picks_list2:
+            # if we have already estimates / values for mag/grad but not
+            # a value for meg, combine grad and mag.
+            if ('mag' in this_rank and 'grad' in this_rank and
+                    'meg' not in rank):
+                this_rank['meg'] = this_rank['mag'] + this_rank['grad']
+                # and we're done here
+                break
+
+            if rank.get(ch_type) is None:
                 this_info = pick_info(evoked.info, this_picks)
                 idx = np.ix_(this_picks, this_picks)
-                this_rank = _estimate_rank_meeg_cov(C[idx], this_info,
-                                                    scalings)
-                rank_[ch_type] = this_rank
-        if rank is not None:
-            rank_.update(rank)
-        rank_list.append(rank_)
+                this_estimated_rank = _estimate_rank_meeg_cov(
+                    C[idx], this_info, scalings)
+                _check_estimated_rank(
+                    this_estimated_rank, this_picks, this_info, evoked,
+                    cov, ch_type, has_meg, has_sss)
+                this_rank[ch_type] = this_estimated_rank
+            elif rank.get(ch_type) is not None:
+                this_rank[ch_type] = rank[ch_type]
+
+        rank_list.append(this_rank)
+
+    # get one whitened evoked per cov
     evokeds_white = [whiten_evoked(evoked, n, picks, rank=r)
                      for n, r in zip(noise_cov, rank_list)]
 
-    axes_evoked = None
-
     def whitened_gfp(x, rank=None):
-        """Whitened Global Field Power
+        """Whitened Global Field Power.
 
         The MNE inverse solver assumes zero mean whitened data as input.
         Therefore, a chi^2 statistic will be best to detect model violations.
@@ -905,9 +1092,7 @@ def _plot_evoked_white(evoked, noise_cov, scalings=None, rank=None, show=True):
                     noise_cov[0].get('method', 'empirical'))
         fig.suptitle(suptitle)
 
-    ax_gfp = None
-    if any(((n_columns == 1 and n_ch_used == 1),
-            (n_columns == 1 and n_ch_used > 1),
+    if any(((n_columns == 1 and n_ch_used >= 1),
             (n_columns == 2 and n_ch_used == 1))):
         axes_evoked = axes[:n_ch_used]
         ax_gfp = axes[-1:]
@@ -927,6 +1112,7 @@ def _plot_evoked_white(evoked, noise_cov, scalings=None, rank=None, show=True):
                  'meg': 'steelblue'}
     iter_gfp = zip(evokeds_white, noise_cov, rank_list, colors)
 
+    # the first is by law the best noise cov, on the left we plot that one.
     if not has_sss:
         evokeds_white[0].plot(unit=False, axes=axes_evoked,
                               hline=[-1.96, 1.96], show=False)
@@ -936,9 +1122,10 @@ def _plot_evoked_white(evoked, noise_cov, scalings=None, rank=None, show=True):
             for hline in [-1.96, 1.96]:
                 ax.axhline(hline, color='red', linestyle='--')
 
-    # Now plot the GFP
+    # Now plot the GFP for all covs if indicated.
     for evoked_white, noise_cov, rank_, color in iter_gfp:
         i = 0
+
         for ch, sub_picks in picks_list:
             this_rank = rank_[ch]
             title = '{0} ({2}{1})'.format(
@@ -946,20 +1133,21 @@ def _plot_evoked_white(evoked, noise_cov, scalings=None, rank=None, show=True):
                     this_rank, 'rank ' if n_columns > 1 else '')
             label = noise_cov.get('method', 'empirical')
 
-            ax_gfp[i].set_title(title if n_columns > 1 else
-                                'whitened global field power (GFP),'
-                                ' method = "%s"' % label)
+            ax = ax_gfp[i]
+            ax.set_title(title if n_columns > 1 else
+                         'whitened global field power (GFP),'
+                         ' method = "%s"' % label)
 
             data = evoked_white.data[sub_picks]
             gfp = whitened_gfp(data, rank=this_rank)
-            ax_gfp[i].plot(times, gfp,
-                           label=(label if n_columns > 1 else title),
-                           color=color if n_columns > 1 else ch_colors[ch])
-            ax_gfp[i].set_xlabel('times [ms]')
-            ax_gfp[i].set_ylabel('GFP [chi^2]')
-            ax_gfp[i].set_xlim(times[0], times[-1])
-            ax_gfp[i].set_ylim(0, 10)
-            ax_gfp[i].axhline(1, color='red', linestyle='--')
+            ax.plot(times, gfp,
+                    label=label if n_columns > 1 else title,
+                    color=color if n_columns > 1 else ch_colors[ch])
+            ax.set_xlabel('times [ms]')
+            ax.set_ylabel('GFP [chi^2]')
+            ax.set_xlim(times[0], times[-1])
+            ax.set_ylim(0, 10)
+            ax.axhline(1, color='red', linestyle='--')
             if n_columns > 1:
                 i += 1
 
@@ -981,7 +1169,7 @@ def _plot_evoked_white(evoked, noise_cov, scalings=None, rank=None, show=True):
 
 
 def plot_snr_estimate(evoked, inv, show=True):
-    """Plot a data SNR estimate
+    """Plot a data SNR estimate.
 
     Parameters
     ----------
@@ -1024,7 +1212,7 @@ def plot_snr_estimate(evoked, inv, show=True):
 
 
 def _connection_line(x, fig, sourceax, targetax):
-    """Helper function to connect time series and topolots"""
+    """Connect time series and topolots."""
     from matplotlib.lines import Line2D
     transFigure = fig.transFigure.inverted()
     tf = fig.transFigure
@@ -1037,22 +1225,22 @@ def _connection_line(x, fig, sourceax, targetax):
 
 
 def plot_evoked_joint(evoked, times="peaks", title='', picks=None,
-                      exclude=None,
-                      show=True, ts_args=None, topomap_args=None):
-    """Plot evoked data as butterfly plot and add topomaps for selected
-    time points.
+                      exclude=None, show=True, ts_args=None,
+                      topomap_args=None):
+    """Plot evoked data as butterfly plot and add topomaps for time points.
 
     Parameters
     ----------
     evoked : instance of Evoked
         The evoked instance.
-    times : float | array of floats | "auto" | "peaks".
+    times : float | array of floats | "auto" | "peaks"
         The time point(s) to plot. If "auto", 5 evenly spaced topographies
         between the first and last time instant will be shown. If "peaks",
         finds time points automatically by checking for 3 local maxima in
         Global Field Power. Defaults to "peaks".
     title : str | None
-        The title. If `None`, suppress printing channel type. Defaults to ''.
+        The title. If `None`, suppress printing channel type. If an empty
+        string, a default title is created. Defaults to ''.
     picks : array-like of int | None
         The indices of channels to plot. If None show all. Defaults to None.
     exclude : None | list of str | 'bads'
@@ -1062,16 +1250,17 @@ def plot_evoked_joint(evoked, times="peaks", title='', picks=None,
         Show figure if True. Defaults to True.
     ts_args : None | dict
         A dict of `kwargs` that are forwarded to `evoked.plot` to
-        style the butterfly plot. `axes` and `show` are ignored.
-        If `spatial_colors` is not in this dict, `spatial_colors=True`,
-        and (if it is not in the dict) `zorder='std'` will be passed.
-        Defaults to ``None``.
+        style the butterfly plot. If they are not in this dict, the following
+        defaults are passed: ``spatial_colors=True``, ``zorder='std'``,
+        ``axes``, ``show``, ``exclude`` are illegal.
+        If None, no customizable arguments will be passed.
+        Defaults to `None`.
     topomap_args : None | dict
-        A dict of `kwargs` that are forwarded to `evoked.plot_topomap`
-        to style the topomaps. `axes` and `show` are ignored. If `times`
-        is not in this dict, automatic peak detection is used. Beyond that,
-        if ``None`, no customizable arguments will be passed.
-        Defaults to ``None``.
+        A dict of `kwargs` that are forwarded to `evoked.plot_topomap` to
+        style the topomaps. If it is not in this dict, ``outlines='skirt'``
+        will be passed. `axes`, `show`, `times`, `colorbar` are illegal`
+        If None, no customizable arguments will be passed.
+        Defaults to `None`.
 
     Returns
     -------
@@ -1091,8 +1280,14 @@ def plot_evoked_joint(evoked, times="peaks", title='', picks=None,
     if topomap_args is None:
         topomap_args = dict()
 
+    illegal_args = {"axes", "show", 'times', 'exclude'}
+    for args in (ts_args, topomap_args):
+        if any((x in args for x in illegal_args)):
+            raise ValueError("Don't pass any of {} as *_args.".format(
+                ", ".join(list(illegal_args))))
+
     # channel selection
-    # simply create a new evoked object(s) with the desired channel selection
+    # simply create a new evoked object with the desired channel selection
     evoked = evoked.copy()
 
     if picks is not None:
@@ -1109,16 +1304,16 @@ def plot_evoked_joint(evoked, times="peaks", title='', picks=None,
         evoked.drop_channels(exclude)
 
     info = evoked.info
-    data_types = ['eeg', 'grad', 'mag', 'seeg', 'ecog', 'hbo', 'hbr']
+    data_types = {'eeg', 'grad', 'mag', 'seeg', 'ecog', 'hbo', 'hbr'}
     ch_types = set(ch_type for ch_type in data_types if ch_type in evoked)
 
     # if multiple sensor types: one plot per channel type, recursive call
     if len(ch_types) > 1:
         figs = list()
-        for t in ch_types:  # pick only the corresponding channel type
+        for this_type in ch_types:  # pick only the corresponding channel type
             ev_ = evoked.copy().pick_channels(
                 [info['ch_names'][idx] for idx in range(info['nchan'])
-                 if channel_type(info, idx) == t])
+                 if channel_type(info, idx) == this_type])
             if len(set([channel_type(ev_.info, idx)
                         for idx in range(ev_.info['nchan'])
                         if channel_type(ev_.info, idx) in data_types])) > 1:
@@ -1139,17 +1334,13 @@ def plot_evoked_joint(evoked, times="peaks", title='', picks=None,
     # butterfly/time series plot
     # most of this code is about passing defaults on demand
     ts_ax = fig.add_subplot(212)
-    ts_args_pass = dict((k, v) for k, v in ts_args.items() if k not in
-                        ['axes', 'show', 'colorbar', 'set_tight_layout'])
     ts_args_def = dict(picks=None, unit=True, ylim=None, xlim='tight',
                        proj=False, hline=None, units=None, scalings=None,
                        titles=None, gfp=False, window_title=None,
                        spatial_colors=True, zorder='std')
-    for key in ts_args_def:
-        if key not in ts_args:
-            ts_args_pass[key] = ts_args_def[key]
+    ts_args_def.update(ts_args)
     _plot_evoked(evoked, axes=ts_ax, show=False, plot_type='butterfly',
-                 exclude=[], set_tight_layout=False, **ts_args_pass)
+                 exclude=[], set_tight_layout=False, **ts_args_def)
 
     # handle title
     # we use a new axis for the title to handle scaling of plots
@@ -1157,7 +1348,8 @@ def plot_evoked_joint(evoked, times="peaks", title='', picks=None,
     ts_ax.set_title('')
     if title is not None:
         title_ax = plt.subplot(4, 3, 2)
-        title = ', '.join([title, old_title]) if len(title) > 0 else old_title
+        if title == '':
+            title = old_title
         title_ax.text(.5, .5, title, transform=title_ax.transAxes,
                       horizontalalignment='center',
                       verticalalignment='center')
@@ -1170,17 +1362,33 @@ def plot_evoked_joint(evoked, times="peaks", title='', picks=None,
     cbar_ax = plt.subplot(4, 3 * (ts + 1), 6 * (ts + 1))
 
     # topomap
-    topomap_args_pass = dict((k, v) for k, v in topomap_args.items() if
-                             k not in ['times', 'axes', 'show', 'colorbar'])
-    topomap_args_pass['outlines'] = (topomap_args['outlines'] if 'outlines'
-                                     in topomap_args else 'skirt')
-    evoked.plot_topomap(times=times, axes=map_ax, show=False,
-                        colorbar=False, **topomap_args_pass)
+    contours = topomap_args.get('contours', 6)
+    ch_type = ch_types.pop()  # set should only contain one element
+    # Since the data has all the ch_types, we get the limits from the plot.
+    vmin, vmax = ts_ax.get_ylim()
+    norm = ch_type == 'grad'
+    vmin = 0 if norm else vmin
+    vmin, vmax = _setup_vmin_vmax(evoked.data, vmin, vmax, norm)
+    if not isinstance(contours, (list, np.ndarray)):
+        locator, contours = _set_contour_locator(vmin, vmax, contours)
+    else:
+        locator = None
+
+    topomap_args_pass = topomap_args.copy()
+    topomap_args_pass['outlines'] = topomap_args.get('outlines', 'skirt')
+    topomap_args_pass['contours'] = contours
+    evoked.plot_topomap(times=times, axes=map_ax, show=False, colorbar=False,
+                        **topomap_args_pass)
 
     if topomap_args.get('colorbar', True):
         from matplotlib import ticker
         cbar = plt.colorbar(map_ax[0].images[0], cax=cbar_ax)
-        cbar.locator = ticker.MaxNLocator(nbins=5)
+        if isinstance(contours, (list, np.ndarray)):
+            cbar.set_ticks(contours)
+        else:
+            if locator is None:
+                locator = ticker.MaxNLocator(nbins=5)
+            cbar.locator = locator
         cbar.update_ticks()
 
     plt.subplots_adjust(left=.1, right=.93, bottom=.14,
@@ -1204,23 +1412,8 @@ def plot_evoked_joint(evoked, times="peaks", title='', picks=None,
     return fig
 
 
-def _ci(arr, ci):
-    """Calculate the `ci`% parametric confidence interval for `arr`.
-    Aux function for plot_compare_evokeds."""
-    from scipy import stats
-    mean, sigma = arr.mean(0), stats.sem(arr, 0)
-    # This is highly convoluted to support 17th century Scipy
-    # XXX Fix when Scipy 0.12 support is dropped!
-    # then it becomes just:
-    # return stats.t.interval(ci, loc=mean, scale=sigma, df=arr.shape[0])
-    return np.asarray([stats.t.interval(ci, arr.shape[0],
-                       loc=mean_, scale=sigma_)
-                       for mean_, sigma_ in zip(mean, sigma)]).T
-
-
 def _setup_styles(conditions, style_dict, style, default):
-    """Aux function for plot_compare_evokeds to set linestyles and colors"""
-
+    """Set linestyles and colors for plot_compare_evokeds."""
     # check user-supplied style to condition matching
     tags = set([tag for cond in conditions for tag in cond.split("/")])
     msg = ("Can't map between conditions and the provided {0}. Make sure "
@@ -1250,38 +1443,48 @@ def _setup_styles(conditions, style_dict, style, default):
 
 
 def _truncate_yaxis(axes, ymin, ymax, orig_ymin, orig_ymax, fraction,
-                    any_positive, any_negative):
-    """Aux function for truncating the y axis in plot_compare_evokeds"""
-    abs_lims = (orig_ymax if orig_ymax > np.abs(orig_ymin)
-                else np.abs(orig_ymin))
-    ymin_, ymax_ = (-(abs_lims // fraction), abs_lims // fraction)
-    # user supplied ymin and ymax overwrite everything
-    if ymin is not None and ymin > ymin_:
-        ymin_ = ymin
-    if ymax is not None and ymax < ymax_:
-        ymax_ = ymax
-    yticks = (ymin_ if any_negative else 0, ymax_ if any_positive else 0)
-    axes.set_yticks(yticks)
-    ymin_bound, ymax_bound = (-(abs_lims // fraction), abs_lims // fraction)
-    # user supplied ymin and ymax still overwrite everything
-    if ymin is not None and ymin > ymin_bound:
-        ymin_bound = ymin
-    if ymax is not None and ymax < ymax_bound:
-        ymax_bound = ymax
-    precision = 0.25  # round to .25
-    if ymin is None:
-        ymin_bound = round(ymin_bound / precision) * precision
-    if ymin is None:
-        ymax_bound = round(ymax_bound / precision) * precision
+                    any_positive, any_negative, truncation_style):
+    """Truncate the y axis in plot_compare_evokeds."""
+    if truncation_style != "max_ticks":
+        abs_lims = (orig_ymax if orig_ymax > np.abs(orig_ymin)
+                    else np.abs(orig_ymin))
+        ymin_, ymax_ = (-(abs_lims // fraction), abs_lims // fraction)
+        # user supplied ymin and ymax overwrite everything
+        if ymin is not None and ymin > ymin_:
+            ymin_ = ymin
+        if ymax is not None and ymax < ymax_:
+            ymax_ = ymax
+        yticks = (ymin_ if any_negative else 0, ymax_ if any_positive else 0)
+        axes.set_yticks(yticks)
+        ymin_bound, ymax_bound = (-(abs_lims // fraction),
+                                  abs_lims // fraction)
+        # user supplied ymin and ymax still overwrite everything
+        if ymin is not None and ymin > ymin_bound:
+            ymin_bound = ymin
+        if ymax is not None and ymax < ymax_bound:
+            ymax_bound = ymax
+        precision = 0.25  # round to .25
+        if ymin is None:
+            ymin_bound = round(ymin_bound / precision) * precision
+        if ymin is None:
+            ymax_bound = round(ymax_bound / precision) * precision
+    else:
+        ticks = axes.get_yticks()
+        ymin_bound, ymax_bound = ticks[[1, -2]]
+        if ymin_bound > 0:
+            ymin_bound = 0
+        ymin_bound = ymin if ymin is not None else ymin_bound
+        ymax_bound = ymax if ymax is not None else ymax_bound
     axes.spines['left'].set_bounds(ymin_bound, ymax_bound)
     return ymin_bound, ymax_bound
 
 
 def plot_compare_evokeds(evokeds, picks=list(), gfp=False, colors=None,
-                         linestyles=['-'], styles=None, vlines=[0.], ci=0.95,
-                         truncate_yaxis=True, ylim=dict(), invert_y=False,
-                         axes=None, title=None, show=True):
-    """Plot evoked time courses for one or multiple channels and conditions
+                         linestyles=['-'], styles=None, vlines=list((0.,)),
+                         ci=0.95, truncate_yaxis=False, truncate_xaxis=True,
+                         ylim=dict(), invert_y=False, show_sensors=None,
+                         show_legend=True, axes=None, title=None, show=True):
+    """Plot evoked time courses for one or multiple channels and conditions.
 
     This function is useful for comparing ER[P/F]s at a specific location. It
     plots Evoked data or, if supplied with a list/dict of lists of evoked
@@ -1298,6 +1501,7 @@ def plot_compare_evokeds(evokeds, picks=list(), gfp=False, colors=None,
         series and the parametric confidence interval is plotted as a shaded
         area. All instances must have the same shape - channel numbers, time
         points etc.
+        If dict, keys must be of type str.
     picks : int | list of int
         If int or list of int, the indices of the sensors to average and plot.
         Must all be of the same channel type.
@@ -1339,15 +1543,23 @@ def plot_compare_evokeds(evokeds, picks=list(), gfp=False, colors=None,
     vlines : list of int
         A list of integers corresponding to the positions, in seconds,
         at which to plot dashed vertical lines.
-    ci : float | None
-        If not None and `evokeds` is a [list/dict] of lists, a confidence
-        interval is drawn around the individual time series. This value
-        determines the CI width. E.g., if this value is .95 (the default),
-        the 95% parametric confidence interval is drawn.
-        If None, no shaded confidence band is plotted.
-    truncate_yaxis : bool
-        If True, the left y axis is truncated to half the max value and
-        rounded to .25 to reduce visual clutter. Defaults to True.
+    ci : float | callable | None
+        If not None and ``evokeds`` is a [list/dict] of lists, a shaded
+        confidence interval is drawn around the individual time series. If
+        float, a percentile bootstrap method is used to estimate the confidence
+        interval and this value determines the CI width. E.g., if this value is
+        .95 (the default), the 95% confidence interval is drawn. If a callable,
+        it must take as its single argument an array (observations x times) and
+        return the upper and lower confidence bands.
+        If None, no confidence band is plotted.
+    truncate_yaxis : bool | str
+        If True, the left y axis spine is truncated to reduce visual clutter.
+        If 'max_ticks', the spine is truncated at the minimum and maximum
+        ticks. Else, it is truncated to half the max absolute value, rounded to
+        .25. Defaults to False.
+    truncate_xaxis : bool
+        If True, the x axis is truncated to span from the first to the last.
+        xtick. Defaults to True.
     ylim : dict | None
         ylim for plots (after scaling has been applied). e.g.
         ylim = dict(eeg=[-20, 20])
@@ -1356,7 +1568,15 @@ def plot_compare_evokeds(evokeds, picks=list(), gfp=False, colors=None,
     invert_y : bool
         If True, negative values are plotted up (as is sometimes done
         for ERPs out of tradition). Defaults to False.
-    axes : None | `matplotlib.pyplot.axes` instance | list of `axes`
+    show_sensors: bool | int | None
+        If not False, channel locations are plotted on a small head circle.
+        If an int, the position of the axes (forwarded to
+        ``mpl_toolkits.axes_grid1.inset_locator.inset_axes``).
+        If None, defaults to True if ``gfp`` is False, else to False.
+    show_legend : bool | int
+        If not False, show a legend. If int, the position of the axes
+        (forwarded to ``mpl_toolkits.axes_grid1.inset_locator.inset_axes``).
+    axes : None | `matplotlib.axes.Axes` instance | list of `axes`
         What axes to plot to. If None, a new axes is created.
         When plotting multiple channel types, can also be a list of axes, one
         per channel type.
@@ -1380,6 +1600,9 @@ def plot_compare_evokeds(evokeds, picks=list(), gfp=False, colors=None,
     elif not isinstance(evokeds, dict):
         evokeds = dict((str(ii + 1), evoked)
                        for ii, evoked in enumerate(evokeds))
+    for cond in evokeds.keys():
+        if not isinstance(cond, string_types):
+            raise TypeError('Conditions must be str, not %s' % (type(cond),))
     conditions = sorted(list(evokeds.keys()))
 
     # get and set a few limits and variables (times, channels, units)
@@ -1391,29 +1614,40 @@ def plot_compare_evokeds(evokeds, picks=list(), gfp=False, colors=None,
                          "or a collection of mne.Evoked's")
     times = example.times
     tmin, tmax = times[0], times[-1]
+    if (tmin >= 0 or tmax <= 0) and vlines == [0.]:
+        vlines = list()
 
-    if isinstance(picks, int):
+    if isinstance(picks, Integral):
         picks = [picks]
     elif len(picks) == 0:
         warn("No picks, plotting the GFP ...")
         gfp = True
         picks = _pick_data_channels(example.info)
 
+        if len(picks) == 0:
+            raise ValueError("No valid channels were found to plot the GFP. " +
+                             "Use 'picks' instead to select them manually.")
+
+    if ylim is None:
+        ylim = dict()
+
     # deal with picks: infer indices and names
     if gfp is True:
+        show_sensors = False if show_sensors is None else show_sensors
         ch_names = ['Global Field Power']
         if len(picks) < 2:
             raise ValueError("A GFP with less than 2 channels doesn't work, "
                              "please pick more channels.")
     else:
-        if not isinstance(picks[0], int):
+        if not isinstance(picks[0], (int, np.integer)):
             msg = "'picks' must be int or a list of int, not {0}."
             raise ValueError(msg.format(type(picks)))
+        show_sensors = True if show_sensors is None else show_sensors
         ch_names = [example.ch_names[pick] for pick in picks]
     ch_types = list(set(channel_type(example.info, pick_)
                     for pick_ in picks))
     # XXX: could possibly be refactored; plot_joint is doing a similar thing
-    if any([type_ not in _DATA_CH_TYPES_SPLIT for type_ in ch_types]):
+    if any([type_ not in _VALID_CHANNEL_TYPES for type_ in ch_types]):
         raise ValueError("Non-data channel picked.")
     if len(ch_types) > 1:
         warn("Multiple channel types selected, returning one figure per type.")
@@ -1437,62 +1671,57 @@ def plot_compare_evokeds(evokeds, picks=list(), gfp=False, colors=None,
         ch_type = ch_types[0]
         ymin, ymax = ylim.get(ch_type, [None, None])
 
+    # deal with dict/list of lists and the CI
+    if ci is not None and not (isinstance(ci, np.float) or callable(ci)):
+        raise TypeError('ci must be float or callable, got ' + str(type(ci)))
+
     scaling = _handle_default("scalings")[ch_type]
+    unit = _handle_default("units")[ch_type]
 
-    if ch_type == 'grad' and gfp is not True:  # deal with grad pairs
-        from ..channels.layout import _merge_grad_data, _pair_grad_sensors
-        picked_chans = list()
-        pairpicks = _pair_grad_sensors(example.info, topomap_coords=False)
-        for ii in np.arange(0, len(pairpicks), 2):
-            first, second = pairpicks[ii], pairpicks[ii + 1]
-            if first in picks or second in picks:
-                picked_chans.append(first)
-                picked_chans.append(second)
-        picks = list(sorted(set(picked_chans)))
-        ch_names = [example.ch_names[pick] for pick in picks]
+    all_positive = gfp  # True if not gfp, False if gfp
+    if ch_type == 'grad' and len(picks) > 1:  # deal with grad pairs
+        from ..channels.layout import _merge_grad_data
+        all_positive = True
+        if gfp is not True:
+            picks, ch_names = _grad_pair_pick_and_name(example.info, picks)
 
-    if ymin is None and (gfp is True or ch_type == 'grad'):
-        ymin = 0  # 'grad' and GFP are plotted as all-positive
-
-    # deal with dict/list of lists and the CI
-    if not isinstance(ci, np.float):
-        msg = '"ci" must be float, got {0} instead.'
-        raise TypeError(msg.format(type(ci)))
+    if (ymin is None) and all_positive:
+        ymin = 0.  # 'grad' and GFP are plotted as all-positive
 
     # if we have a dict/list of lists, we compute the grand average and the CI
+    if ci is None:
+        ci = False
     if not all([isinstance(evoked_, Evoked) for evoked_ in evokeds.values()]):
-        if ci is not None and gfp is not True:
+        if ci is not False:
+            if callable(ci):
+                _ci_fun = ci
+            else:
+                from ..stats import _ci
+                _ci_fun = partial(_ci, ci=ci, method="bootstrap")
             # calculate the CI
-            sem_array = dict()
+            ci_array = dict()
             for condition in conditions:
                 # this will fail if evokeds do not have the same structure
                 # (e.g. channel count)
-                if ch_type == 'grad' and gfp is not True:
-                    data = np.asarray([
-                        _merge_grad_data(
-                            evoked_.data[picks, :]).mean(0)
-                        for evoked_ in evokeds[condition]])
-                else:
-                    data = np.asarray([evoked_.data[picks, :].mean(0)
-                                       for evoked_ in evokeds[condition]])
-                sem_array[condition] = _ci(data, ci)
+                data = np.asarray([evoked_.data[picks, :].mean(0)
+                                   for evoked_ in evokeds[condition]])
+                ci_array[condition] = _ci_fun(data) * scaling
 
         # get the grand mean
         evokeds = dict((cond, combine_evoked(evokeds[cond], weights='equal'))
                        for cond in conditions)
-
-        if gfp is True and ci is not None:
-            warn("Confidence Interval not drawn when plotting GFP.")
     else:
         ci = False
-        combine_evoked(list(evokeds.values()))  # check if they are compatible
+
+    if ci is False:
+        # check if they are compatible (XXX there should be a cleaner way)
+        combine_evoked(list(evokeds.values()), weights='nave')
     # we now have dicts for data ('evokeds' - grand averaged Evoked's)
     # and the CI ('sem_array') with cond name labels
 
     # let's plot!
     if axes is None:
-        fig, axes = plt.subplots(1, 1)
-        fig.set_size_inches(8, 6)
+        fig, axes = plt.subplots(1, 1, figsize=(8, 6))
     else:
         fig = axes.figure
 
@@ -1502,7 +1731,12 @@ def plot_compare_evokeds(evokeds, picks=list(), gfp=False, colors=None,
     # are pulled from the 'colors' and 'linestyles' dicts via '/'-tag matching
     # unless they are overwritten by entries from a user-provided 'styles'.
 
-    # first, check if input is valid
+    # first, copy to avoid overwriting
+    styles = deepcopy(styles)
+    colors = deepcopy(colors)
+    linestyles = deepcopy(linestyles)
+
+    # second, check if input is valid
     if isinstance(styles, dict):
         for style_ in styles:
             if style_ not in conditions:
@@ -1530,14 +1764,14 @@ def plot_compare_evokeds(evokeds, picks=list(), gfp=False, colors=None,
     else:
         colors = _setup_styles(conditions, colors, "color", "grey")
 
-    # third, linestyles
+    # fourth, linestyles
     if not isinstance(linestyles, dict):
         linestyles = dict((condition, linestyle) for condition, linestyle in
                           zip(conditions, ['-'] * len(conditions)))
     else:
         linestyles = _setup_styles(conditions, linestyles, "linestyle", "-")
 
-    # fourth, put it all together
+    # fifth, put it all together
     if styles is None:
         styles = dict()
     for condition, color, linestyle in zip(conditions, colors, linestyles):
@@ -1552,27 +1786,29 @@ def plot_compare_evokeds(evokeds, picks=list(), gfp=False, colors=None,
     any_negative, any_positive = False, False
     for condition in conditions:
         # plot the actual data ('d') as a line
-        if ch_type == 'grad' and gfp is False:
-            d = ((_merge_grad_data(evokeds[condition]
-                 .data[picks, :])).T * scaling).mean(-1)
+        if ch_type == 'grad' and len(picks) > 1 and gfp is False:
+            d = (_merge_grad_data(evokeds[condition]
+                 .data[picks, :]).T * scaling).mean(-1)
         else:
-            func = np.std if gfp is True else np.mean
-            d = func((evokeds[condition].data[picks, :].T * scaling), -1)
+            d = evokeds[condition].data[picks, :].T * scaling
+            if gfp is True:
+                d = np.sqrt((d * d).mean(axis=-1))
+            else:
+                d = d.mean(-1)
         axes.plot(times, d, zorder=1000, label=condition, **styles[condition])
-        if any(d > 0):
+        if any(d > 0) or all_positive:
             any_positive = True
-        if any(d < 0):
+        if np.any(d < 0):
             any_negative = True
 
-        # plot the confidence interval (standard error of the mean/'sem_')
-        if ci and gfp is not True:
-            sem_ = sem_array[condition]
-            axes.fill_between(times, sem_[0].flatten() * scaling,
-                              sem_[1].flatten() * scaling, zorder=100,
-                              color=styles[condition]['c'], alpha=.333)
+        # plot the confidence interval
+        if ci and (gfp is not True):
+            ci_ = ci_array[condition]
+            axes.fill_between(times, ci_[0].flatten(), ci_[1].flatten(),
+                              zorder=9, color=styles[condition]['c'], alpha=.3)
 
     # truncate the y axis
-    orig_ymin, orig_ymax = axes.get_ylim()[0], axes.get_ylim()[-1]
+    orig_ymin, orig_ymax = axes.get_ylim()
     if not any_positive:
         orig_ymax = 0
     if not any_negative:
@@ -1583,15 +1819,14 @@ def plot_compare_evokeds(evokeds, picks=list(), gfp=False, colors=None,
 
     fraction = 2 if axes.get_ylim()[0] >= 0 else 3
 
-    if truncate_yaxis and ymin is not None and not (ymin > 0):
-        ymin_bound, ymax_bound = _truncate_yaxis(
+    if truncate_yaxis is not False:
+        _, ymax_bound = _truncate_yaxis(
             axes, ymin, ymax, orig_ymin, orig_ymax, fraction,
-            any_positive, any_negative)
+            any_positive, any_negative, truncate_yaxis)
     else:
-        if ymin is not None and ymin > 0:
-            warn("ymin is positive, not truncating yaxis")
+        if truncate_yaxis is True and ymin is not None and ymin > 0:
+            warn("ymin is all-positive, not truncating yaxis")
         ymax_bound = axes.get_ylim()[-1]
-    y_range = -np.subtract(*axes.get_ylim())
 
     title = ", ".join(ch_names[:6]) if title is None else title
     if len(ch_names) > 6 and gfp is False:
@@ -1599,13 +1834,6 @@ def plot_compare_evokeds(evokeds, picks=list(), gfp=False, colors=None,
         title += ", ..."
     axes.set_title(title)
 
-    # style the spines/axes
-    axes.spines["top"].set_position('zero')
-    axes.spines["top"].set_smart_bounds(True)
-
-    axes.tick_params(direction='out')
-    axes.tick_params(right="off")
-
     current_ymin = axes.get_ylim()[0]
 
     # plot v lines
@@ -1616,33 +1844,32 @@ def plot_compare_evokeds(evokeds, picks=list(), gfp=False, colors=None,
     axes.vlines(vlines, upper_v, lower_v, linestyles='--', colors='k',
                 linewidth=1., zorder=1)
 
-    # set x label
-    axes.set_xlabel('Time (s)')
-    axes.xaxis.get_label().set_verticalalignment('center')
-
-    # set y label and ylabel position
-    axes.set_ylabel(_handle_default("units")[ch_type], rotation=0)
-    ylabel_height = (-(current_ymin / y_range)
-                     if 0 > current_ymin  # ... if we have negative values
-                     else (axes.get_yticks()[-1] / 2 / y_range))
-    axes.yaxis.set_label_coords(-0.05, 1 - ylabel_height
-                                if invert_y else ylabel_height)
-    xticks = sorted(list(set([x for x in axes.get_xticks()] + vlines)))
-    axes.set_xticks(xticks)
-    axes.set_xticklabels(xticks)
-    x_extrema = [t for t in xticks if tmax >= t >= tmin]
-    axes.spines['bottom'].set_bounds(x_extrema[0], x_extrema[-1])
-    axes.spines["left"].set_zorder(0)
-
-    # finishing touches
-    if invert_y:
-        axes.invert_yaxis()
-    axes.patch.set_alpha(0)
-    axes.spines['right'].set_color('none')
-    axes.set_xlim(tmin, tmax)
-
-    if len(conditions) > 1:
-        plt.legend(loc='best', ncol=1 + (len(conditions) // 5), frameon=True)
+    _setup_ax_spines(axes, vlines, tmin, tmax, invert_y, ymax_bound, unit,
+                     truncate_xaxis)
+
+    if show_sensors:
+        try:
+            pos = _auto_topomap_coords(example.info, picks,
+                                       ignore_overlap=True, to_sphere=True)
+        except ValueError:
+            warn("Cannot find channel coordinates in the supplied Evokeds. "
+                 "Not showing channel locations.")
+        else:
+            head_pos = {'center': (0, 0), 'scale': (0.5, 0.5)}
+            pos, outlines = _check_outlines(pos, np.array([1, 1]), head_pos)
+            if not isinstance(show_sensors, (np.int, bool)):
+                raise TypeError("`show_sensors` must be numeric or bool, not" +
+                                str(type(show_sensors)))
+            if show_sensors is True:
+                show_sensors = 2
+            _plot_legend(pos, ["k" for pick in picks], axes, list(), outlines,
+                         show_sensors, size=20)
+
+    if show_legend and len(conditions) > 1:
+        if show_legend is True:
+            show_legend = 'best'
+        axes.legend(loc=show_legend, ncol=1 + (len(conditions) // 5),
+                    frameon=True)
 
     plt_show(show)
     return fig
diff --git a/mne/viz/ica.py b/mne/viz/ica.py
index 90cbb7a..298fa09 100644
--- a/mne/viz/ica.py
+++ b/mne/viz/ica.py
@@ -1,5 +1,4 @@
-"""Functions to plot ICA specific data (besides topographies)
-"""
+"""Functions to plot ICA specific data (besides topographies)."""
 from __future__ import print_function
 
 # Authors: Denis Engemann <denis.engemann at gmail.com>
@@ -9,6 +8,7 @@ from __future__ import print_function
 # License: Simplified BSD
 
 from functools import partial
+from numbers import Integral
 
 import numpy as np
 
@@ -29,7 +29,8 @@ from ..time_frequency.psd import psd_multitaper
 
 
 def plot_ica_sources(ica, inst, picks=None, exclude=None, start=None,
-                     stop=None, title=None, show=True, block=False):
+                     stop=None, title=None, show=True, block=False,
+                     show_first_samp=False):
     """Plot estimated latent sources given the unmixing matrix.
 
     Typical usecases:
@@ -64,6 +65,8 @@ def plot_ica_sources(ica, inst, picks=None, exclude=None, start=None,
         Whether to halt program execution until the figure is closed.
         Useful for interactive selection of components in raw and epoch
         plotter. For evoked, this parameter has no effect. Defaults to False.
+    show_first_samp : bool
+        If True, show time axis relative to the ``raw.first_samp``.
 
     Returns
     -------
@@ -78,20 +81,19 @@ def plot_ica_sources(ica, inst, picks=None, exclude=None, start=None,
 
     .. versionadded:: 0.10.0
     """
-
-    from ..io.base import _BaseRaw
+    from ..io.base import BaseRaw
     from ..evoked import Evoked
-    from ..epochs import _BaseEpochs
+    from ..epochs import BaseEpochs
 
     if exclude is None:
         exclude = ica.exclude
     elif len(ica.exclude) > 0:
         exclude = np.union1d(ica.exclude, exclude)
-    if isinstance(inst, _BaseRaw):
+    if isinstance(inst, BaseRaw):
         fig = _plot_sources_raw(ica, inst, picks, exclude, start=start,
                                 stop=stop, show=show, title=title,
-                                block=block)
-    elif isinstance(inst, _BaseEpochs):
+                                block=block, show_first_samp=show_first_samp)
+    elif isinstance(inst, BaseEpochs):
         fig = _plot_sources_epochs(ica, inst, picks, exclude, start=start,
                                    stop=stop, show=show, title=title,
                                    block=block)
@@ -101,7 +103,7 @@ def plot_ica_sources(ica, inst, picks=None, exclude=None, start=None,
         sources = ica.get_sources(inst)
         fig = _plot_ica_sources_evoked(
             evoked=sources, picks=picks, exclude=exclude, title=title,
-            labels=getattr(ica, 'labels_', None), show=show)
+            labels=getattr(ica, 'labels_', None), show=show, ica=ica)
     else:
         raise ValueError('Data input must be of Raw or Epochs type')
 
@@ -109,7 +111,7 @@ def plot_ica_sources(ica, inst, picks=None, exclude=None, start=None,
 
 
 def _create_properties_layout(figsize=None):
-    """creates main figure and axes layout used by plot_ica_properties"""
+    """Create main figure and axes layout used by plot_ica_properties."""
     import matplotlib.pyplot as plt
     if figsize is None:
         figsize = [7., 6.]
@@ -126,8 +128,10 @@ def _create_properties_layout(figsize=None):
 def plot_ica_properties(ica, inst, picks=None, axes=None, dB=True,
                         plot_std=True, topomap_args=None, image_args=None,
                         psd_args=None, figsize=None, show=True):
-    """Display component properties: topography, epochs image, ERP/ERF,
-    power spectrum and epoch variance.
+    """Display component properties.
+
+    Properties include the topography, epochs image, ERP/ERF, power
+    spectrum, and epoch variance.
 
     Parameters
     ----------
@@ -174,11 +178,11 @@ def plot_ica_properties(ica, inst, picks=None, axes=None, dB=True,
     -----
     .. versionadded:: 0.13
     """
-    from ..io.base import _BaseRaw
-    from ..epochs import _BaseEpochs
+    from ..io.base import BaseRaw
+    from ..epochs import BaseEpochs
     from ..preprocessing import ICA
 
-    if not isinstance(inst, (_BaseRaw, _BaseEpochs)):
+    if not isinstance(inst, (BaseRaw, BaseEpochs)):
         raise ValueError('inst should be an instance of Raw or Epochs,'
                          ' got %s instead.' % type(inst))
     if not isinstance(ica, ICA):
@@ -195,7 +199,7 @@ def plot_ica_properties(ica, inst, picks=None, axes=None, dB=True,
 
     # if no picks given - plot the first 5 components
     picks = list(range(min(5, ica.n_components_))) if picks is None else picks
-    picks = [picks] if isinstance(picks, int) else picks
+    picks = [picks] if isinstance(picks, Integral) else picks
     if axes is None:
         fig, axes = _create_properties_layout(figsize=figsize)
     else:
@@ -208,6 +212,7 @@ def plot_ica_properties(ica, inst, picks=None, axes=None, dB=True,
     psd_args = dict() if psd_args is None else psd_args
     topomap_args = dict() if topomap_args is None else topomap_args
     image_args = dict() if image_args is None else image_args
+    image_args["ts_args"] = dict(truncate_xaxis=False, show_sensors=False)
     for d in (psd_args, topomap_args, image_args):
         if not isinstance(d, dict):
             raise ValueError('topomap_args, image_args and psd_args have to be'
@@ -218,14 +223,11 @@ def plot_ica_properties(ica, inst, picks=None, axes=None, dB=True,
 
     # calculations
     # ------------
-    plot_line_at_zero = False
-    if isinstance(inst, _BaseRaw):
+    if isinstance(inst, BaseRaw):
         # break up continuous signal into segments
         from ..epochs import _segment_raw
         inst = _segment_raw(inst, segment_length=2., verbose=False,
                             preload=True)
-    if inst.times[0] < 0. and inst.times[-1] > 0.:
-        plot_line_at_zero = True
 
     epochs_src = ica.get_sources(inst)
     ica_data = np.swapaxes(epochs_src.get_data()[:, picks, :], 0, 1)
@@ -269,15 +271,6 @@ def plot_ica_properties(ica, inst, picks=None, axes=None, dB=True,
             [np.sqrt((d[d > 0] ** 2).mean(axis=0)) for d in diffs.T]]
         spectrum_std = np.array(spectrum_std) * num_std
 
-        # erp std
-        if plot_std:
-            erp = ica_data[idx].mean(axis=0)
-            diffs = ica_data[idx] - erp
-            erp_std = [
-                [np.sqrt((d[d < 0] ** 2).mean(axis=0)) for d in diffs.T],
-                [np.sqrt((d[d > 0] ** 2).mean(axis=0)) for d in diffs.T]]
-            erp_std = np.array(erp_std) * num_std
-
         # epoch variance
         epoch_var = np.var(ica_data[idx], axis=1)
 
@@ -287,7 +280,7 @@ def plot_ica_properties(ica, inst, picks=None, axes=None, dB=True,
         _plot_ica_topomap(ica, pick, show=False, axes=axes[0], **topomap_args)
 
         # image and erp
-        plot_epochs_image(epochs_src, picks=pick, axes=axes[1:3],
+        plot_epochs_image(epochs_src, picks=pick, axes=axes[1:3], combine=None,
                           colorbar=False, show=False, **image_args)
 
         # spectrum
@@ -295,10 +288,10 @@ def plot_ica_properties(ica, inst, picks=None, axes=None, dB=True,
         if plot_std:
             axes[3].fill_between(freqs, psds_mean - spectrum_std[0],
                                  psds_mean + spectrum_std[1],
-                                 color='k', alpha=.15)
+                                 color='k', alpha=.2)
         if plot_lowpass_edge:
             axes[3].axvline(inst.info['lowpass'], lw=2, linestyle='--',
-                            color='k', alpha=0.15)
+                            color='k', alpha=0.2)
 
         # epoch variance
         axes[4].scatter(range(len(epoch_var)), epoch_var, alpha=0.5,
@@ -306,33 +299,20 @@ def plot_ica_properties(ica, inst, picks=None, axes=None, dB=True,
 
         # aesthetics
         # ----------
-        axes[0].set_title('IC #{0:0>3}'.format(pick))
+        axes[0].set_title(ica._ica_names[pick])
 
         set_title_and_labels(axes[1], 'epochs image and ERP/ERF', [], 'Epochs')
 
         # erp
-        set_title_and_labels(axes[2], [], 'time', 'AU')
-        # line color and std
-        axes[2].lines[0].set_color('k')
-        if plot_std:
-            erp_xdata = axes[2].lines[0].get_data()[0]
-            axes[2].fill_between(erp_xdata, erp - erp_std[0],
-                                 erp + erp_std[1], color='k', alpha=.15)
-            axes[2].autoscale(enable=True, axis='y')
-            axes[2].axis('auto')
-            axes[2].set_xlim(erp_xdata[[0, -1]])
+        set_title_and_labels(axes[2], [], 'time', 'AU\n')
+        axes[2].spines["right"].set_color('k')
+        axes[2].set_xlim(epochs_src.times[[0, -1]])
         # remove half of yticks if more than 5
         yt = axes[2].get_yticks()
         if len(yt) > 5:
             yt = yt[::2]
             axes[2].yaxis.set_ticks(yt)
 
-        if not plot_line_at_zero:
-            xlims = [1e3 * inst.times[0], 1e3 * inst.times[-1]]
-            for k, ax in enumerate(axes[1:3]):
-                ax.lines[k].remove()
-                ax.set_xlim(xlims)
-
         # remove xticks - erp plot shows xticks for both image and erp plot
         axes[1].xaxis.set_ticks([])
         yt = axes[1].get_yticks()
@@ -347,6 +327,7 @@ def plot_ica_properties(ica, inst, picks=None, axes=None, dB=True,
         ylim = axes[3].get_ylim()
         air = np.diff(ylim)[0] * 0.1
         axes[3].set_ylim(ylim[0] - air, ylim[1] + air)
+        axes[1].axhline(0, color='k', linewidth=.5)
 
         # epoch variance
         set_title_and_labels(axes[4], 'epochs variance', 'epoch', 'AU')
@@ -357,8 +338,9 @@ def plot_ica_properties(ica, inst, picks=None, axes=None, dB=True,
     return all_fig
 
 
-def _plot_ica_sources_evoked(evoked, picks, exclude, title, show, labels=None):
-    """Plot average over epochs in ICA space
+def _plot_ica_sources_evoked(evoked, picks, exclude, title, show, ica,
+                             labels=None):
+    """Plot average over epochs in ICA space.
 
     Parameters
     ----------
@@ -400,7 +382,7 @@ def _plot_ica_sources_evoked(evoked, picks, exclude, title, show, labels=None):
     exclude_labels = list()
     for ii in picks:
         if ii in exclude:
-            line_label = 'IC #%03d' % ii
+            line_label = ica._ica_names[ii]
             if labels is not None:
                 annot = list()
                 for this_label in labels_used:
@@ -472,7 +454,7 @@ def _plot_ica_sources_evoked(evoked, picks, exclude, title, show, labels=None):
 
 
 def plot_ica_scores(ica, scores, exclude=None, labels=None, axhline=None,
-                    title='ICA component scores', figsize=(12, 6), show=True):
+                    title='ICA component scores', figsize=None, show=True):
     """Plot scores related to detected components.
 
     Use this function to asses how well your score describes outlier
@@ -497,8 +479,8 @@ def plot_ica_scores(ica, scores, exclude=None, labels=None, axhline=None,
         Draw horizontal line to e.g. visualize rejection threshold.
     title : str
         The figure title.
-    figsize : tuple of int
-        The figure size. Defaults to (12, 6).
+    figsize : tuple of int | None
+        The figure size. If None it gets set automatically.
     show : bool
         Show figure if True.
 
@@ -515,13 +497,14 @@ def plot_ica_scores(ica, scores, exclude=None, labels=None, axhline=None,
     if not isinstance(scores[0], (list, np.ndarray)):
         scores = [scores]
     n_rows = len(scores)
-    figsize = (12, 6) if figsize is None else figsize
+    if figsize is None:
+        figsize = (6.4, 2.7 * n_rows)
     fig, axes = plt.subplots(n_rows, figsize=figsize, sharex=True, sharey=True)
     if isinstance(axes, np.ndarray):
         axes = axes.flatten()
     else:
         axes = [axes]
-    plt.suptitle(title)
+    axes[0].set_title(title)
 
     if labels == 'ecg':
         labels = [l for l in ica.labels_ if l.startswith('ecg/')]
@@ -536,14 +519,15 @@ def plot_ica_scores(ica, scores, exclude=None, labels=None, axhline=None,
         if len(labels) != len(axes):
             raise ValueError('Need as many labels as axes (%i)' % len(axes))
     elif labels is None:
-        labels = (None, None)
+        labels = (None,) * n_rows
+
     for label, this_scores, ax in zip(labels, scores, axes):
         if len(my_range) != len(this_scores):
             raise ValueError('The length of `scores` must equal the '
                              'number of ICA components.')
-        ax.bar(my_range, this_scores, color='w')
+        ax.bar(my_range, this_scores, color='w', edgecolor='k')
         for excl in exclude:
-            ax.bar(my_range[excl], this_scores[excl], color='r')
+            ax.bar(my_range[excl], this_scores[excl], color='r', edgecolor='k')
         if axhline is not None:
             if np.isscalar(axhline):
                 axhline = [axhline]
@@ -562,8 +546,6 @@ def plot_ica_scores(ica, scores, exclude=None, labels=None, axhline=None,
         ax.set_xlim(0, len(this_scores))
 
     tight_layout(fig=fig)
-    if len(axes) > 1:
-        plt.subplots_adjust(top=0.9)
     plt_show(show)
     return fig
 
@@ -606,11 +588,11 @@ def plot_ica_overlay(ica, inst, exclude=None, picks=None, start=None,
         The figure.
     """
     # avoid circular imports
-    from ..io.base import _BaseRaw
+    from ..io.base import BaseRaw
     from ..evoked import Evoked
     from ..preprocessing.ica import _check_start_stop
 
-    if not isinstance(inst, (_BaseRaw, Evoked)):
+    if not isinstance(inst, (BaseRaw, Evoked)):
         raise ValueError('Data input must be of Raw or Evoked type')
     if title is None:
         title = 'Signals before (red) and after (black) cleaning'
@@ -618,7 +600,7 @@ def plot_ica_overlay(ica, inst, exclude=None, picks=None, start=None,
         picks = [inst.ch_names.index(k) for k in ica.ch_names]
     if exclude is None:
         exclude = ica.exclude
-    if isinstance(inst, _BaseRaw):
+    if isinstance(inst, BaseRaw):
         if start is None:
             start = 0.0
         if stop is None:
@@ -646,7 +628,7 @@ def plot_ica_overlay(ica, inst, exclude=None, picks=None, start=None,
 
 
 def _plot_ica_overlay_raw(data, data_cln, times, title, ch_types_used, show):
-    """Plot evoked after and before ICA cleaning
+    """Plot evoked after and before ICA cleaning.
 
     Parameters
     ----------
@@ -694,7 +676,7 @@ def _plot_ica_overlay_raw(data, data_cln, times, title, ch_types_used, show):
 
 
 def _plot_ica_overlay_evoked(evoked, evoked_cln, title, show):
-    """Plot evoked after and before ICA cleaning
+    """Plot evoked after and before ICA cleaning.
 
     Parameters
     ----------
@@ -738,8 +720,8 @@ def _plot_ica_overlay_evoked(evoked, evoked_cln, title, show):
 
 
 def _plot_sources_raw(ica, raw, picks, exclude, start, stop, show, title,
-                      block):
-    """Function for plotting the ICA components as raw array."""
+                      block, show_first_samp):
+    """Plot the ICA components as raw array."""
     color = _handle_default('color', (0., 0., 0.))
     orig_data = ica._transform_raw(raw, 0, len(raw.times)) * 0.2
     if picks is None:
@@ -749,7 +731,7 @@ def _plot_sources_raw(ica, raw, picks, exclude, start, stop, show, title,
     eog_chs = pick_types(raw.info, meg=False, eog=True, ref_meg=False)
     ecg_chs = pick_types(raw.info, meg=False, ecg=True, ref_meg=False)
     data = [orig_data[pick] for pick in picks]
-    c_names = ['IC #%03d' % x for x in range(len(orig_data))]
+    c_names = list(ica._ica_names)  # new list
     for eog_idx in eog_chs:
         c_names.append(raw.ch_names[eog_idx])
         types.append('eog')
@@ -787,10 +769,13 @@ def _plot_sources_raw(ica, raw, picks, exclude, start, stop, show, title,
     inds = list(range(len(picks)))
     data = np.array(data)
     n_channels = min([20, len(picks)])
+    first_time = raw._first_time if show_first_samp else 0
+    start += first_time
     params = dict(raw=raw, orig_data=data, data=data[:, 0:t_end], inds=inds,
                   ch_start=0, t_start=start, info=info, duration=duration,
                   ica=ica, n_channels=n_channels, times=times, types=types,
-                  n_times=raw.n_times, bad_color=bad_color, picks=picks)
+                  n_times=raw.n_times, bad_color=bad_color, picks=picks,
+                  first_time=first_time, data_picks=[], decim=1)
     _prepare_mne_browse_raw(params, title, 'w', color, bad_color, inds,
                             n_channels)
     params['scale_factor'] = 1.0
@@ -813,6 +798,7 @@ def _plot_sources_raw(ica, raw, picks, exclude, start, stop, show, title,
     params['fig'].canvas.mpl_connect('close_event', callback_close)
     params['fig_proj'] = None
     params['event_times'] = None
+    params['butterfly'] = False
     params['update_fun']()
     params['plot_fun']()
     try:
@@ -823,16 +809,16 @@ def _plot_sources_raw(ica, raw, picks, exclude, start, stop, show, title,
 
 
 def _update_data(params):
-    """Function for preparing the data on horizontal shift of the viewport."""
+    """Prepare the data on horizontal shift of the viewport."""
     sfreq = params['info']['sfreq']
-    start = int(params['t_start'] * sfreq)
+    start = int((params['t_start'] - params['first_time']) * sfreq)
     end = int((params['t_start'] + params['duration']) * sfreq)
     params['data'] = params['orig_data'][:, start:end]
     params['times'] = params['raw'].times[start:end]
 
 
 def _pick_bads(event, params):
-    """Function for selecting components on click."""
+    """Select components on click."""
     bads = params['info']['bads']
     params['info']['bads'] = _select_bads(event, params, bads)
     params['update_fun']()
@@ -840,20 +826,20 @@ def _pick_bads(event, params):
 
 
 def _close_event(events, params):
-    """Function for excluding the selected components on close."""
+    """Exclude the selected components on close."""
     info = params['info']
-    c_names = ['IC #%03d' % x for x in range(params['ica'].n_components_)]
-    exclude = [c_names.index(x) for x in info['bads'] if x.startswith('IC')]
+    exclude = [params['ica']._ica_names.index(x)
+               for x in info['bads'] if x.startswith('ICA')]
     params['ica'].exclude = exclude
 
 
 def _plot_sources_epochs(ica, epochs, picks, exclude, start, stop, show,
                          title, block):
-    """Function for plotting the components as epochs."""
+    """Plot the components as epochs."""
     data = ica._transform_epochs(epochs, concatenate=True)
     eog_chs = pick_types(epochs.info, meg=False, eog=True, ref_meg=False)
     ecg_chs = pick_types(epochs.info, meg=False, ecg=True, ref_meg=False)
-    c_names = ['IC #%03d' % x for x in range(ica.n_components_)]
+    c_names = list(ica._ica_names)
     ch_types = np.repeat('misc', ica.n_components_)
     for eog_idx in eog_chs:
         c_names.append(epochs.ch_names[eog_idx])
@@ -892,7 +878,9 @@ def _plot_sources_epochs(ica, epochs, picks, exclude, start, stop, show,
               'orig_data': data,
               'bads': list(),
               'bad_color': (1., 0., 0.),
-              't_start': start * len(epochs.times)}
+              't_start': start * len(epochs.times),
+              'data_picks': [],
+              'decim': 1}
     params['label_click_fun'] = partial(_label_clicked, params=params)
     _prepare_mne_browse_epochs(params, projs=list(), n_channels=20,
                                n_epochs=n_epochs, scalings=scalings,
@@ -911,7 +899,7 @@ def _plot_sources_epochs(ica, epochs, picks, exclude, start, stop, show,
 
 
 def _update_epoch_data(params):
-    """Function for preparing the data on horizontal shift."""
+    """Prepare the data on horizontal shift."""
     start = params['t_start']
     n_epochs = params['n_epochs']
     end = start + n_epochs * len(params['epochs'].times)
@@ -923,7 +911,7 @@ def _update_epoch_data(params):
 
 
 def _close_epochs_event(events, params):
-    """Function for excluding the selected components on close."""
+    """Exclude the selected components on close."""
     info = params['info']
     exclude = [info['ch_names'].index(x) for x in info['bads']
                if x.startswith('IC')]
@@ -931,7 +919,7 @@ def _close_epochs_event(events, params):
 
 
 def _label_clicked(pos, params):
-    """Function for plotting independent components on click to label."""
+    """Plot independent components on click to label."""
     import matplotlib.pyplot as plt
     offsets = np.array(params['offsets']) + params['offsets'][0]
     line_idx = np.searchsorted(offsets, pos[1]) + params['ch_start']
@@ -969,7 +957,7 @@ def _label_clicked(pos, params):
         if merge_grads:
             from ..channels.layout import _merge_grad_data
         for ii, data_ in zip(ic_idx, this_data):
-            ax.set_title('IC #%03d ' % ii + ch_type, fontsize=12)
+            ax.set_title('%s %s' % (ica._ica_names[ii], ch_type), fontsize=12)
             data_ = _merge_grad_data(data_) if merge_grads else data_
             plot_topomap(data_.flatten(), pos, axes=ax, show=False)
             _hide_frame(ax)
diff --git a/mne/viz/misc.py b/mne/viz/misc.py
index 560f3c9..07e561b 100644
--- a/mne/viz/misc.py
+++ b/mne/viz/misc.py
@@ -1,5 +1,5 @@
-"""Functions to make simple plots with M/EEG data
-"""
+"""Functions to make simple plots with M/EEG data."""
+
 from __future__ import print_function
 
 # Authors: Alexandre Gramfort <alexandre.gramfort at telecom-paristech.fr>
@@ -15,6 +15,7 @@ import copy
 from glob import glob
 from itertools import cycle
 import os.path as op
+import warnings
 
 import numpy as np
 from scipy import linalg
@@ -25,13 +26,14 @@ from ..io.proj import make_projector
 from ..source_space import read_source_spaces, SourceSpaces
 from ..utils import logger, verbose, get_subjects_dir, warn
 from ..io.pick import pick_types
+from ..filter import estimate_ringing_samples
 from .utils import tight_layout, COLORS, _prepare_trellis, plt_show
 
 
 @verbose
 def plot_cov(cov, info, exclude=[], colorbar=True, proj=False, show_svd=True,
              show=True, verbose=None):
-    """Plot Covariance data
+    """Plot Covariance data.
 
     Parameters
     ----------
@@ -52,7 +54,8 @@ def plot_cov(cov, info, exclude=[], colorbar=True, proj=False, show_svd=True,
     show : bool
         Show figure if True.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -105,25 +108,27 @@ def plot_cov(cov, info, exclude=[], colorbar=True, proj=False, show_svd=True,
 
     import matplotlib.pyplot as plt
 
-    fig_cov = plt.figure(figsize=(2.5 * len(idx_names), 2.7))
+    fig_cov, axes = plt.subplots(1, len(idx_names), squeeze=False,
+                                 figsize=(2.5 * len(idx_names), 2.7))
     for k, (idx, name, _, _) in enumerate(idx_names):
-        plt.subplot(1, len(idx_names), k + 1)
-        plt.imshow(C[idx][:, idx], interpolation="nearest", cmap='RdBu_r')
-        plt.title(name)
-    plt.subplots_adjust(0.04, 0.0, 0.98, 0.94, 0.2, 0.26)
+        axes[0, k].imshow(C[idx][:, idx], interpolation="nearest",
+                          cmap='RdBu_r')
+        axes[0, k].set(title=name)
+    fig_cov.subplots_adjust(0.04, 0.0, 0.98, 0.94, 0.2, 0.26)
     tight_layout(fig=fig_cov)
 
     fig_svd = None
     if show_svd:
-        fig_svd = plt.figure()
+        fig_svd, axes = plt.subplots(1, len(idx_names), squeeze=False)
         for k, (idx, name, unit, scaling) in enumerate(idx_names):
             s = linalg.svd(C[idx][:, idx], compute_uv=False)
-            plt.subplot(1, len(idx_names), k + 1)
-            plt.ylabel('Noise std (%s)' % unit)
-            plt.xlabel('Eigenvalue index')
-            plt.semilogy(np.sqrt(s) * scaling)
-            plt.title(name)
-            tight_layout(fig=fig_svd)
+            # Protect against true zero singular values
+            s[s <= 0] = 1e-10 * s[s > 0].min()
+            s = np.sqrt(s) * scaling
+            axes[0, k].plot(s)
+            axes[0, k].set(ylabel='Noise std (%s)' % unit, yscale='log',
+                           xlabel='Eigenvalue index', title=name)
+        tight_layout(fig=fig_svd)
 
     plt_show(show)
     return fig_cov, fig_svd
@@ -383,6 +388,10 @@ def plot_bem(subject=None, subjects_dir=None, orientation='coronal',
     -------
     fig : Instance of matplotlib.figure.Figure
         The figure.
+
+    See Also
+    --------
+    mne.viz.plot_alignment
     """
     subjects_dir = get_subjects_dir(subjects_dir, raise_error=True)
 
@@ -442,7 +451,7 @@ def plot_bem(subject=None, subjects_dir=None, orientation='coronal',
 
 def plot_events(events, sfreq=None, first_samp=0, color=None, event_id=None,
                 axes=None, equal_spacing=True, show=True):
-    """Plot events to get a visual display of the paradigm
+    """Plot events to get a visual display of the paradigm.
 
     Parameters
     ----------
@@ -480,7 +489,6 @@ def plot_events(events, sfreq=None, first_samp=0, color=None, event_id=None,
     -----
     .. versionadded:: 0.9.0
     """
-
     if sfreq is None:
         sfreq = 1.0
         xlabel = 'samples'
@@ -509,25 +517,7 @@ def plot_events(events, sfreq=None, first_samp=0, color=None, event_id=None,
     else:
         unique_events_id = unique_events
 
-    if color is None:
-        if len(unique_events) > len(COLORS):
-            warn('More events than colors available. You should pass a list '
-                 'of unique colors.')
-        colors = cycle(COLORS)
-        color = dict()
-        for this_event, this_color in zip(unique_events_id, colors):
-            color[this_event] = this_color
-    else:
-        for this_event in color:
-            if this_event not in unique_events_id:
-                raise ValueError('%s from color is not present in events '
-                                 'or event_id.' % this_event)
-
-        for this_event in unique_events_id:
-            if this_event not in color:
-                warn('Color is not available for event %d. Default colors '
-                     'will be used.' % this_event)
-
+    color = _handle_event_colors(unique_events, color, unique_events_id)
     import matplotlib.pyplot as plt
 
     fig = None
@@ -578,7 +568,7 @@ def plot_events(events, sfreq=None, first_samp=0, color=None, event_id=None,
 
 
 def _get_presser(fig):
-    """Helper to get our press callback"""
+    """Get our press callback."""
     callbacks = fig.canvas.callbacks.callbacks['button_press_event']
     func = None
     for key, val in callbacks.items():
@@ -590,7 +580,7 @@ def _get_presser(fig):
 
 
 def plot_dipole_amplitudes(dipoles, colors=None, show=True):
-    """Plot the amplitude traces of a set of dipoles
+    """Plot the amplitude traces of a set of dipoles.
 
     Parameters
     ----------
@@ -616,7 +606,7 @@ def plot_dipole_amplitudes(dipoles, colors=None, show=True):
     fig, ax = plt.subplots(1, 1)
     xlim = [np.inf, -np.inf]
     for dip, color in zip(dipoles, colors):
-        ax.plot(dip.times, dip.amplitude, color=color, linewidth=1.5)
+        ax.plot(dip.times, dip.amplitude * 1e9, color=color, linewidth=1.5)
         xlim[0] = min(xlim[0], dip.times[0])
         xlim[1] = max(xlim[1], dip.times[-1])
     ax.set_xlim(xlim)
@@ -625,3 +615,304 @@ def plot_dipole_amplitudes(dipoles, colors=None, show=True):
     if show:
         fig.show(warn=False)
     return fig
+
+
+def adjust_axes(axes, remove_spines=('top', 'right'), grid=True):
+    """Adjust some properties of axes.
+
+    Parameters
+    ----------
+    axes : list
+        List of axes to process.
+    remove_spines : list of str
+        Which axis spines to remove.
+    grid : bool
+        Turn grid on (True) or off (False).
+    """
+    axes = [axes] if not isinstance(axes, (list, tuple, np.ndarray)) else axes
+    for ax in axes:
+        if grid:
+            ax.grid(zorder=0)
+        for key in remove_spines:
+            ax.spines[key].set_visible(False)
+
+
+def _filter_ticks(lims, fscale):
+    """Create approximately spaced ticks between lims."""
+    if fscale == 'linear':
+        return None, None  # let matplotlib handle it
+    lims = np.array(lims)
+    ticks = list()
+    for exp in range(int(np.floor(np.log10(lims[0]))),
+                     int(np.floor(np.log10(lims[1]))) + 1):
+        ticks += (np.array([1, 2, 4]) * (10 ** exp)).tolist()
+    ticks = np.array(ticks)
+    ticks = ticks[(ticks >= lims[0]) & (ticks <= lims[1])]
+    ticklabels = [('%g' if t < 1 else '%d') % t for t in ticks]
+    return ticks, ticklabels
+
+
+def _get_flim(flim, fscale, freq, sfreq=None):
+    """Get reasonable frequency limits."""
+    if flim is None:
+        if freq is None:
+            flim = [0.1 if fscale == 'log' else 0., sfreq / 2.]
+        else:
+            if fscale == 'linear':
+                flim = [freq[0]]
+            else:
+                flim = [freq[0] if freq[0] > 0 else 0.1 * freq[1]]
+            flim += [freq[-1]]
+    if fscale == 'log':
+        if flim[0] <= 0:
+            raise ValueError('flim[0] must be positive, got %s' % flim[0])
+    elif flim[0] < 0:
+        raise ValueError('flim[0] must be non-negative, got %s' % flim[0])
+    return flim
+
+
+def _check_fscale(fscale):
+    """Check for valid fscale."""
+    if not isinstance(fscale, string_types) or fscale not in ('log', 'linear'):
+        raise ValueError('fscale must be "log" or "linear", got %s'
+                         % (fscale,))
+
+
+def plot_filter(h, sfreq, freq=None, gain=None, title=None, color='#1f77b4',
+                flim=None, fscale='log', alim=(-60, 10), show=True):
+    """Plot properties of a filter.
+
+    Parameters
+    ----------
+    h : dict or ndarray
+        An IIR dict or 1D ndarray of coefficients (for FIR filter).
+    sfreq : float
+        Sample rate of the data (Hz).
+    freq : array-like or None
+        The ideal response frequencies to plot (must be in ascending order).
+        If None (default), do not plot the ideal response.
+    gain : array-like or None
+        The ideal response gains to plot.
+        If None (default), do not plot the ideal response.
+    title : str | None
+        The title to use. If None (default), deteremine the title based
+        on the type of the system.
+    color : color object
+        The color to use (default '#1f77b4').
+    flim : tuple or None
+        If not None, the x-axis frequency limits (Hz) to use.
+        If None, freq will be used. If None (default) and freq is None,
+        ``(0.1, sfreq / 2.)`` will be used.
+    fscale : str
+        Frequency scaling to use, can be "log" (default) or "linear".
+    alim : tuple
+        The y-axis amplitude limits (dB) to use (default: (-60, 10)).
+    show : bool
+        Show figure if True (default).
+
+    Returns
+    -------
+    fig : matplotlib.figure.Figure
+        The figure containing the plots.
+
+    See Also
+    --------
+    mne.filter.create_filter
+    plot_ideal_filter
+
+    Notes
+    -----
+    .. versionadded:: 0.14
+    """
+    from scipy.signal import freqz, group_delay
+    import matplotlib.pyplot as plt
+    sfreq = float(sfreq)
+    _check_fscale(fscale)
+    flim = _get_flim(flim, fscale, freq, sfreq)
+    if fscale == 'log':
+        omega = np.logspace(np.log10(flim[0]), np.log10(flim[1]), 1000)
+    else:
+        omega = np.linspace(flim[0], flim[1], 1000)
+    omega /= sfreq / (2 * np.pi)
+    if isinstance(h, dict):  # IIR h.ndim == 2:  # second-order sections
+        if 'sos' in h:
+            from scipy.signal import sosfilt
+            h = h['sos']
+            H = np.ones(len(omega), np.complex128)
+            gd = np.zeros(len(omega))
+            for section in h:
+                this_H = freqz(section[:3], section[3:], omega)[1]
+                H *= this_H
+                with warnings.catch_warnings(record=True):  # singular GD
+                    gd += group_delay((section[:3], section[3:]), omega)[1]
+            n = estimate_ringing_samples(h)
+            delta = np.zeros(n)
+            delta[0] = 1
+            h = sosfilt(h, delta)
+        else:
+            from scipy.signal import lfilter
+            n = estimate_ringing_samples((h['b'], h['a']))
+            delta = np.zeros(n)
+            delta[0] = 1
+            H = freqz(h['b'], h['a'], omega)[1]
+            with warnings.catch_warnings(record=True):  # singular GD
+                gd = group_delay((h['b'], h['a']), omega)[1]
+            h = lfilter(h['b'], h['a'], delta)
+        title = 'SOS (IIR) filter' if title is None else title
+    else:
+        H = freqz(h, worN=omega)[1]
+        with warnings.catch_warnings(record=True):  # singular GD
+            gd = group_delay((h, [1.]), omega)[1]
+        title = 'FIR filter' if title is None else title
+    gd /= sfreq
+    fig, axes = plt.subplots(3)  # eventually axes could be a parameter
+    t = np.arange(len(h)) / sfreq
+    f = omega * sfreq / (2 * np.pi)
+    axes[0].plot(t, h, color=color)
+    axes[0].set(xlim=t[[0, -1]], xlabel='Time (sec)',
+                ylabel='Amplitude h(n)', title=title)
+    mag = 10 * np.log10(np.maximum((H * H.conj()).real, 1e-20))
+    axes[1].plot(f, mag, color=color, linewidth=2, zorder=4)
+    if freq is not None and gain is not None:
+        plot_ideal_filter(freq, gain, axes[1], fscale=fscale,
+                          title=None, show=False)
+    axes[1].set(ylabel='Magnitude (dB)', xlabel='', xscale=fscale)
+    sl = slice(0 if fscale == 'linear' else 1, None, None)
+    axes[2].plot(f[sl], gd[sl], color=color, linewidth=2, zorder=4)
+    axes[2].set(xlim=flim, ylabel='Group delay (sec)', xlabel='Frequency (Hz)',
+                xscale=fscale)
+    xticks, xticklabels = _filter_ticks(flim, fscale)
+    dlim = [0, 1.05 * gd[1:].max()]
+    for ax, ylim, ylabel in zip(axes[1:], (alim, dlim),
+                                ('Amplitude (dB)', 'Delay (sec)')):
+        if xticks is not None:
+            ax.set(xticks=xticks)
+            ax.set(xticklabels=xticklabels)
+        ax.set(xlim=flim, ylim=ylim, xlabel='Frequency (Hz)', ylabel=ylabel)
+    adjust_axes(axes)
+    tight_layout()
+    plt_show(show)
+    return fig
+
+
+def plot_ideal_filter(freq, gain, axes=None, title='', flim=None, fscale='log',
+                      alim=(-60, 10), color='r', alpha=0.5, linestyle='--',
+                      show=True):
+    """Plot an ideal filter response.
+
+    Parameters
+    ----------
+    freq : array-like
+        The ideal response frequencies to plot (must be in ascending order).
+    gain : array-like or None
+        The ideal response gains to plot.
+    axes : instance of matplotlib.axes.AxesSubplot | None
+        The subplot handle. With None (default), axes are created.
+    title : str
+        The title to use, (default: '').
+    flim : tuple or None
+        If not None, the x-axis frequency limits (Hz) to use.
+        If None (default), freq used.
+    fscale : str
+        Frequency scaling to use, can be "log" (default) or "linear".
+    alim : tuple
+        If not None (default), the y-axis limits (dB) to use.
+    color : color object
+        The color to use (default: 'r').
+    alpha : float
+        The alpha to use (default: 0.5).
+    linestyle : str
+        The line style to use (default: '--').
+    show : bool
+        Show figure if True (default).
+
+    Returns
+    -------
+    fig : Instance of matplotlib.figure.Figure
+        The figure.
+
+    See Also
+    --------
+    plot_filter
+
+    Notes
+    -----
+    .. versionadded:: 0.14
+
+    Examples
+    --------
+    Plot a simple ideal band-pass filter::
+
+        >>> from mne.viz import plot_ideal_filter
+        >>> freq = [0, 1, 40, 50]
+        >>> gain = [0, 1, 1, 0]
+        >>> plot_ideal_filter(freq, gain, flim=(0.1, 100))  #doctest: +ELLIPSIS
+        <matplotlib.figure.Figure object at ...>
+
+    """
+    import matplotlib.pyplot as plt
+    xs, ys = list(), list()
+    my_freq, my_gain = list(), list()
+    if freq[0] != 0:
+        raise ValueError('freq should start with DC (zero) and end with '
+                         'Nyquist, but got %s for DC' % (freq[0],))
+    freq = np.array(freq)
+    # deal with semilogx problems @ x=0
+    _check_fscale(fscale)
+    if fscale == 'log':
+        freq[0] = 0.1 * freq[1] if flim is None else min(flim[0], freq[1])
+    flim = _get_flim(flim, fscale, freq)
+    for ii in range(len(freq)):
+        xs.append(freq[ii])
+        ys.append(alim[0])
+        if ii < len(freq) - 1 and gain[ii] != gain[ii + 1]:
+            xs += [freq[ii], freq[ii + 1]]
+            ys += [alim[1]] * 2
+            my_freq += np.linspace(freq[ii], freq[ii + 1], 20,
+                                   endpoint=False).tolist()
+            my_gain += np.linspace(gain[ii], gain[ii + 1], 20,
+                                   endpoint=False).tolist()
+        else:
+            my_freq.append(freq[ii])
+            my_gain.append(gain[ii])
+    my_gain = 10 * np.log10(np.maximum(my_gain, 10 ** (alim[0] / 10.)))
+    if axes is None:
+        axes = plt.subplots(1)[1]
+    xs = np.maximum(xs, flim[0])
+    axes.fill_between(xs, alim[0], ys, color=color, alpha=0.1)
+    axes.plot(my_freq, my_gain, color=color, linestyle=linestyle, alpha=0.5,
+              linewidth=4, zorder=3)
+    xticks, xticklabels = _filter_ticks(flim, fscale)
+    axes.set(ylim=alim, xlabel='Frequency (Hz)', ylabel='Amplitude (dB)',
+             xscale=fscale)
+    if xticks is not None:
+        axes.set(xticks=xticks)
+        axes.set(xticklabels=xticklabels)
+    axes.set(xlim=flim)
+    adjust_axes(axes)
+    tight_layout()
+    plt_show(show)
+    return axes.figure
+
+
+def _handle_event_colors(unique_events, color, unique_events_id):
+    """Handle event colors."""
+    if color is None:
+        if len(unique_events) > len(COLORS):
+            warn('More events than colors available. You should pass a list '
+                 'of unique colors.')
+        colors = cycle(COLORS)
+        color = dict()
+        for this_event, this_color in zip(sorted(unique_events_id), colors):
+            color[this_event] = this_color
+    else:
+        for this_event in color:
+            if this_event not in unique_events_id:
+                raise ValueError('%s from color is not present in events '
+                                 'or event_id.' % this_event)
+
+        for this_event in unique_events_id:
+            if this_event not in color:
+                warn('Color is not available for event %d. Default colors '
+                     'will be used.' % this_event)
+    return color
diff --git a/mne/viz/montage.py b/mne/viz/montage.py
index 1bcded2..021c391 100644
--- a/mne/viz/montage.py
+++ b/mne/viz/montage.py
@@ -1,21 +1,24 @@
-"""Functions to plot EEG sensor montages or digitizer montages
-"""
+"""Functions to plot EEG sensor montages or digitizer montages."""
+from copy import deepcopy
 import numpy as np
+from ..utils import check_version, logger
+from . import plot_sensors
 
-from .utils import plt_show
 
-
-def plot_montage(montage, scale_factor=1.5, show_names=False, show=True):
-    """Plot a montage
+def plot_montage(montage, scale_factor=20, show_names=True, kind='topomap',
+                 show=True):
+    """Plot a montage.
 
     Parameters
     ----------
-    montage : instance of Montage
+    montage : instance of Montage or DigMontage
         The montage to visualize.
     scale_factor : float
-        Determines the size of the points. Defaults to 1.5.
+        Determines the size of the points.
     show_names : bool
-        Whether to show the channel names. Defaults to False.
+        Whether to show the channel names.
+    kind : str
+        Whether to plot the montage as '3d' or 'topomap' (default).
     show : bool
         Show figure if True.
 
@@ -24,35 +27,48 @@ def plot_montage(montage, scale_factor=1.5, show_names=False, show=True):
     fig : Instance of matplotlib.figure.Figure
         The figure object.
     """
-    from ..channels.montage import Montage, DigMontage
-
-    import matplotlib.pyplot as plt
-    from mpl_toolkits.mplot3d import Axes3D  # noqa
-    fig = plt.figure()
-    ax = fig.add_subplot(111, projection='3d')
+    from scipy.spatial.distance import cdist
+    from ..channels import Montage, DigMontage
+    from .. import create_info
 
     if isinstance(montage, Montage):
-        pos = montage.pos
-        ax.scatter(pos[:, 0], pos[:, 1], pos[:, 2])
-        if show_names:
-            ch_names = montage.ch_names
-            for ch_name, x, y, z in zip(ch_names, pos[:, 0],
-                                        pos[:, 1], pos[:, 2]):
-                ax.text(x, y, z, ch_name)
+        ch_names = montage.ch_names
+        title = montage.kind
     elif isinstance(montage, DigMontage):
-        pos = np.vstack((montage.hsp, montage.elp))
-        ax.scatter(pos[:, 0], pos[:, 1], pos[:, 2])
-        if show_names:
-            if montage.point_names:
-                hpi_names = montage.point_names
-                for hpi_name, x, y, z in zip(hpi_names, montage.elp[:, 0],
-                                             montage.elp[:, 1],
-                                             montage.elp[:, 2]):
-                    ax.text(x, y, z, hpi_name)
+        ch_names = montage.point_names
+        title = None
+    else:
+        raise TypeError("montage must be an instance of "
+                        "mne.channels.montage.Montage or"
+                        "mne.channels.montage.DigMontage")
+    if kind not in ['topomap', '3d']:
+        raise ValueError("kind must be 'topomap' or '3d'")
 
-    ax.set_xlabel('x')
-    ax.set_ylabel('y')
-    ax.set_zlabel('z')
+    if isinstance(montage, Montage):  # check for duplicate labels
+        dists = cdist(montage.pos, montage.pos)
+        # only consider upper triangular part by setting the rest to np.nan
+        dists[np.tril_indices(dists.shape[0])] = np.nan
+        dupes = np.argwhere(np.isclose(dists, 0))
+        if dupes.any():
+            montage = deepcopy(montage)
+            n_chans = montage.pos.shape[0]
+            n_dupes = dupes.shape[0]
+            idx = np.setdiff1d(montage.selection, dupes[:, 1]).tolist()
+            logger.info("{} duplicate electrode labels found:".format(n_dupes))
+            logger.info(", ".join([ch_names[d[0]] + "/" + ch_names[d[1]]
+                                   for d in dupes]))
+            logger.info("Plotting {} unique labels.".format(n_chans - n_dupes))
+            montage.ch_names = [montage.ch_names[i] for i in idx]
+            ch_names = montage.ch_names
+            montage.pos = montage.pos[idx, :]
+            montage.selection = np.arange(n_chans - n_dupes)
 
-    plt_show(show)
+    info = create_info(ch_names, sfreq=256, ch_types="eeg", montage=montage)
+    fig = plot_sensors(info, kind=kind, show_names=show_names, show=show,
+                       title=title)
+    collection = fig.axes[0].collections[0]
+    if check_version("matplotlib", "1.4"):
+        collection.set_sizes([scale_factor])
+    else:
+        collection._sizes = [scale_factor]
     return fig
diff --git a/mne/viz/raw.py b/mne/viz/raw.py
index 47a4a92..0c84330 100644
--- a/mne/viz/raw.py
+++ b/mne/viz/raw.py
@@ -1,5 +1,4 @@
-"""Functions to plot raw M/EEG data
-"""
+"""Functions to plot raw M/EEG data."""
 from __future__ import print_function
 
 # Authors: Eric Larson <larson.eric.d at gmail.com>
@@ -9,15 +8,18 @@ from __future__ import print_function
 
 import copy
 from functools import partial
+from warnings import warn
 
 import numpy as np
 
 from ..externals.six import string_types
 from ..io.pick import (pick_types, _pick_data_channels, pick_info,
-                       _PICK_TYPES_KEYS, pick_channels)
+                       _PICK_TYPES_KEYS, pick_channels, channel_type)
 from ..io.proj import setup_proj
-from ..utils import verbose, get_config
+from ..io.meas_info import create_info
+from ..utils import verbose, get_config, _ensure_int
 from ..time_frequency import psd_welch
+from ..defaults import _handle_default
 from .topo import _plot_topo, _plot_timeseries, _plot_timeseries_unified
 from .utils import (_toggle_options, _toggle_proj, tight_layout,
                     _layout_figure, _plot_raw_onkey, figure_nobar, plt_show,
@@ -25,13 +27,13 @@ from .utils import (_toggle_options, _toggle_proj, tight_layout,
                     _helper_raw_resize, _select_bads, _onclick_help,
                     _setup_browser_offsets, _compute_scalings, plot_sensors,
                     _radio_clicked, _set_radio_button, _handle_topomap_bads,
-                    _change_channel_group)
-from ..defaults import _handle_default
-from ..annotations import _onset_to_seconds
+                    _change_channel_group, _plot_annotations, _setup_butterfly,
+                    _handle_decim)
+from .evoked import _plot_lines
 
 
 def _plot_update_raw_proj(params, bools):
-    """Helper only needs to be called when proj is changed"""
+    """Deal with changed proj."""
     if bools is not None:
         inds = np.where(bools)[0]
         params['info']['projs'] = [copy.deepcopy(params['projs'][ii])
@@ -44,9 +46,10 @@ def _plot_update_raw_proj(params, bools):
 
 
 def _update_raw_data(params):
-    """Helper only needs to be called when time or proj is changed"""
+    """Deal with time or proj changed."""
     from scipy.signal import filtfilt
     start = params['t_start']
+    start -= params['first_time']
     stop = params['raw'].time_as_index(start + params['duration'])[0]
     start = params['raw'].time_as_index(start)[0]
     data_picks = _pick_data_channels(params['raw'].info)
@@ -76,8 +79,10 @@ def _update_raw_data(params):
 
 
 def _pick_bad_channels(event, params):
-    """Helper for selecting / dropping bad channels onpick"""
+    """Select or drop bad channels onpick."""
     # Both bad lists are updated. params['info'] used for colors.
+    if params['fig_annotation'] is not None:
+        return
     bads = params['raw'].info['bads']
     params['info']['bads'] = _select_bads(event, params, bads)
     _plot_update_raw_proj(params, None)
@@ -85,10 +90,12 @@ def _pick_bad_channels(event, params):
 
 def plot_raw(raw, events=None, duration=10.0, start=0.0, n_channels=20,
              bgcolor='w', color=None, bad_color=(0.8, 0.8, 0.8),
-             event_color='cyan', scalings=None, remove_dc=True, order='type',
+             event_color='cyan', scalings=None, remove_dc=True, order=None,
              show_options=False, title=None, show=True, block=False,
-             highpass=None, lowpass=None, filtorder=4, clipping=None):
-    """Plot raw data
+             highpass=None, lowpass=None, filtorder=4, clipping=None,
+             show_first_samp=False, proj=True, group_by='type',
+             butterfly=False, decim='auto'):
+    """Plot raw data.
 
     Parameters
     ----------
@@ -100,10 +107,12 @@ def plot_raw(raw, events=None, duration=10.0, start=0.0, n_channels=20,
         Time window (sec) to plot. The lesser of this value and the duration
         of the raw file will be used.
     start : float
-        Initial time to show (can be changed dynamically once plotted).
+        Initial time to show (can be changed dynamically once plotted). If
+        show_first_samp is True, then it is taken relative to
+        ``raw.first_samp``.
     n_channels : int
         Number of channels to plot at once. Defaults to 20. Has no effect if
-        ``order`` is 'position' or 'selection'.
+        ``order`` is 'position', 'selection' or 'butterfly'.
     bgcolor : color object
         Color of the background.
     color : dict | color object | None
@@ -132,15 +141,12 @@ def plot_raw(raw, events=None, duration=10.0, start=0.0, n_channels=20,
 
     remove_dc : bool
         If True remove DC component when plotting data.
-    order : str | array of int
-        Order in which to plot data. 'type' groups by channel type, 'original'
-        plots in the order of ch_names, 'selection' uses Elekta's channel
-        groupings (only works for Neuromag data), 'position' groups the
-        channels by the positions of the sensors. 'selection' and 'position'
-        modes allow custom selections by using lasso selector on the topomap.
-        Pressing ``ctrl`` key while selecting allows appending to the current
-        selection. If array, only the channels in the array are plotted in the
-        given order. Defaults to 'type'.
+    order : array of int | None
+        Order in which to plot data. If the array is shorter than the number of
+        channels, only the given channels are plotted. If None (default), all
+        channels are plotted. If ``group_by`` is ``'position'`` or
+        ``'selection'``, the ``order`` parameter is used only for selecting the
+        channels to be plotted.
     show_options : bool
         If True, a dialog for options related to projection is shown.
     title : str | None
@@ -168,6 +174,34 @@ def plot_raw(raw, events=None, duration=10.0, start=0.0, n_channels=20,
         the plot. If "clamp", then values are clamped to the appropriate
         range for display, creating step-like artifacts. If "transparent",
         then excessive values are not shown, creating gaps in the traces.
+    show_first_samp : bool
+        If True, show time axis relative to the ``raw.first_samp``.
+    proj : bool
+        Whether to apply projectors prior to plotting (default is ``True``).
+        Individual projectors can be enabled/disabled interactively (see
+        Notes). This argument only affects the plot; use ``raw.apply_proj()``
+        to modify the data stored in the Raw object.
+    group_by : str
+        How to group channels. ``'type'`` groups by channel type,
+        ``'original'`` plots in the order of ch_names, ``'selection'`` uses
+        Elekta's channel groupings (only works for Neuromag data),
+        ``'position'`` groups the channels by the positions of the sensors.
+        ``'selection'`` and ``'position'`` modes allow custom selections by
+        using lasso selector on the topomap. Pressing ``ctrl`` key while
+        selecting allows appending to the current selection. Channels marked as
+        bad appear with red edges on the topomap. ``'type'`` and ``'original'``
+        groups the channels by type in butterfly mode whereas ``'selection'``
+        and ``'position'`` use regional grouping. ``'type'`` and ``'original'``
+        modes are overrided with ``order`` keyword.
+    butterfly : bool
+        Whether to start in butterfly mode. Defaults to False.
+    decim : int | 'auto'
+        Amount to decimate the data during display for speed purposes.
+        You should only decimate if the data are sufficiently low-passed,
+        otherwise aliasing can occur. The 'auto' mode (default) uses
+        the decimation that results in a sampling rate least three times
+        larger than ``min(info['lowpass'], lowpass)`` (e.g., a 40 Hz lowpass
+        will result in at least a 120 Hz displayed sample rate).
 
     Returns
     -------
@@ -184,6 +218,15 @@ def plot_raw(raw, events=None, duration=10.0, start=0.0, n_channels=20,
     can be to toggled with f11 key. To mark or un-mark a channel as bad, click
     on the rather flat segments of a channel's time series. The changes will be
     reflected immediately in the raw object's ``raw.info['bads']`` entry.
+
+    If projectors are present, a button labelled "Proj" in the lower right
+    corner of the plot window opens a secondary control window, which allows
+    enabling/disabling specific projectors individually. This provides a means
+    of interactively observing how each projector would affect the raw data if
+    it were applied.
+
+    Annotation mode is toggled by pressing 'a' and butterfly mode by pressing
+    'b'.
     """
     import matplotlib.pyplot as plt
     import matplotlib as mpl
@@ -220,7 +263,7 @@ def plot_raw(raw, events=None, duration=10.0, start=0.0, n_channels=20,
                         analog=False)
 
     # make a copy of info, remove projection (for now)
-    info = copy.deepcopy(raw.info)
+    info = raw.info.copy()
     projs = info['projs']
     info['projs'] = []
     n_times = raw.n_times
@@ -267,41 +310,53 @@ def plot_raw(raw, events=None, duration=10.0, start=0.0, n_channels=20,
         raise RuntimeError('Some channels not classified, please report '
                            'this problem')
 
-    # put them back to original or modified order for natral plotting
+    # put them back to original or modified order for natural plotting
     reord = np.argsort(inds)
     types = [types[ri] for ri in reord]
     if isinstance(order, string_types):
-        if order == 'original':
-            inds = inds[reord]
-        elif order in ['selection', 'position']:
-            selections, fig_selection = _setup_browser_selection(raw, order)
-        elif order != 'type':
-            raise ValueError('Unknown order type %s' % order)
+        group_by = order
+        warn('Using string order is deprecated and will not be allowed in '
+             '0.16. Use group_by instead.')
     elif isinstance(order, (np.ndarray, list)):
         # put back to original order first, then use new order
         inds = inds[reord][order]
+    elif order is not None:
+        raise ValueError('Unkown order type. Got %s.' % type(order))
+
+    if group_by in ['selection', 'position']:
+        selections, fig_selection = _setup_browser_selection(raw, group_by)
+        selections = {k: np.intersect1d(v, inds) for k, v in
+                      selections.items()}
+    elif group_by == 'original':
+        if order is None:
+            order = np.arange(len(inds))
+            inds = inds[reord[:len(order)]]
+    elif group_by != 'type':
+        raise ValueError('Unknown group_by type %s' % group_by)
 
     if not isinstance(event_color, dict):
         event_color = {-1: event_color}
-    else:
-        event_color = copy.deepcopy(event_color)  # we might modify it
+    event_color = dict((_ensure_int(key, 'event_color key'), event_color[key])
+                       for key in event_color)
     for key in event_color:
-        if not isinstance(key, int):
-            raise TypeError('event_color key "%s" was a %s not an int'
-                            % (key, type(key)))
         if key <= 0 and key != -1:
             raise KeyError('only key <= 0 allowed is -1 (cannot use %s)'
                            % key)
-
+    decim, data_picks = _handle_decim(info, decim, lowpass)
     # set up projection and data parameters
     duration = min(raw.times[-1], float(duration))
+    first_time = raw._first_time if show_first_samp else 0
+    start += first_time
     params = dict(raw=raw, ch_start=0, t_start=start, duration=duration,
                   info=info, projs=projs, remove_dc=remove_dc, ba=ba,
                   n_channels=n_channels, scalings=scalings, types=types,
                   n_times=n_times, event_times=event_times, inds=inds,
-                  event_nums=event_nums, clipping=clipping, fig_proj=None)
+                  event_nums=event_nums, clipping=clipping, fig_proj=None,
+                  first_time=first_time, added_label=list(), butterfly=False,
+                  group_by=group_by, orig_inds=inds.copy(), decim=decim,
+                  data_picks=data_picks)
 
-    if order in ['selection', 'position']:
+    if group_by in ['selection', 'position']:
         params['fig_selection'] = fig_selection
         params['selections'] = selections
         params['radio_clicked'] = partial(_radio_clicked, params=params)
@@ -320,31 +375,7 @@ def plot_raw(raw, events=None, duration=10.0, start=0.0, n_channels=20,
                                  bad_color=bad_color, event_lines=event_lines,
                                  event_color=event_color)
 
-    if raw.annotations is not None:
-        segments = list()
-        segment_colors = dict()
-        # sort the segments by start time
-        ann_order = raw.annotations.onset.argsort(axis=0)
-        descriptions = raw.annotations.description[ann_order]
-        color_keys = set(descriptions)
-        color_vals = np.linspace(0, 1, len(color_keys))
-        for idx, key in enumerate(color_keys):
-            if key.lower().startswith('bad'):
-                segment_colors[key] = 'red'
-            else:
-                segment_colors[key] = plt.cm.summer(color_vals[idx])
-        params['segment_colors'] = segment_colors
-        for idx, onset in enumerate(raw.annotations.onset[ann_order]):
-            annot_start = _onset_to_seconds(raw, onset)
-            annot_end = annot_start + raw.annotations.duration[ann_order][idx]
-            segments.append([annot_start, annot_end])
-            ylim = params['ax_hscroll'].get_ylim()
-            dscr = descriptions[idx]
-            params['ax_hscroll'].fill_betweenx(ylim, annot_start, annot_end,
-                                               alpha=0.3,
-                                               color=segment_colors[dscr])
-        params['segments'] = np.array(segments)
-        params['annot_description'] = descriptions
+    _plot_annotations(raw, params)
 
     params['update_fun'] = partial(_update_raw_data, params=params)
     params['pick_bads_fun'] = partial(_pick_bad_channels, params=params)
@@ -355,6 +386,7 @@ def plot_raw(raw, events=None, duration=10.0, start=0.0, n_channels=20,
     if len(raw.info['projs']) > 0 and not raw.proj:
         ax_button = plt.subplot2grid((10, 10), (9, 9))
         params['ax_button'] = ax_button
+        params['apply_proj'] = proj
         opt_button = mpl.widgets.Button(ax_button, 'Proj')
         callback_option = partial(_toggle_options, params=params)
         opt_button.on_clicked(callback_option)
@@ -383,21 +415,24 @@ def plot_raw(raw, events=None, duration=10.0, start=0.0, n_channels=20,
     _layout_figure(params)
 
     # deal with projectors
-    if show_options is True:
+    if show_options:
         _toggle_options(None, params)
+
+    callback_close = partial(_close_event, params=params)
+    params['fig'].canvas.mpl_connect('close_event', callback_close)
     # initialize the first selection set
-    if order in ['selection', 'position']:
+    if group_by in ['selection', 'position']:
         _radio_clicked(fig_selection.radio.labels[0]._text, params)
         callback_selection_key = partial(_selection_key_press, params=params)
         callback_selection_scroll = partial(_selection_scroll, params=params)
-        callback_close = partial(_close_event, params=params)
-        params['fig'].canvas.mpl_connect('close_event', callback_close)
         params['fig_selection'].canvas.mpl_connect('close_event',
                                                    callback_close)
         params['fig_selection'].canvas.mpl_connect('key_press_event',
                                                    callback_selection_key)
         params['fig_selection'].canvas.mpl_connect('scroll_event',
                                                    callback_selection_scroll)
+    if butterfly:
+        _setup_butterfly(params)
 
     try:
         plt_show(show, block=block)
@@ -408,7 +443,7 @@ def plot_raw(raw, events=None, duration=10.0, start=0.0, n_channels=20,
 
 
 def _selection_scroll(event, params):
-    """Callback for scroll in selection dialog."""
+    """Handle scroll in selection dialog."""
     if event.step < 0:
         _change_channel_group(-1, params)
     elif event.step > 0:
@@ -416,7 +451,7 @@ def _selection_scroll(event, params):
 
 
 def _selection_key_press(event, params):
-    """Callback for keys in selection dialog."""
+    """Handle keys in selection dialog."""
     if event.key == 'down':
         _change_channel_group(-1, params)
     elif event.key == 'up':
@@ -426,14 +461,20 @@ def _selection_key_press(event, params):
 
 
 def _close_event(event, params):
-    """Callback for closing of raw browser with selections."""
+    """Handle closing of raw browser with selections."""
     import matplotlib.pyplot as plt
-    plt.close(params['fig_selection'])
+    if 'fig_selection' in params:
+        plt.close(params['fig_selection'])
+    for fig in ['fig_annotation', 'fig_help', 'fig_proj']:
+        if params[fig] is not None:
+            plt.close(params[fig])
     plt.close(params['fig'])
 
 
 def _label_clicked(pos, params):
-    """Helper function for selecting bad channels."""
+    """Select bad channels."""
+    if params['butterfly']:
+        return
     labels = params['ax'].yaxis.get_ticklabels()
     offsets = np.array(params['offsets']) + params['offsets'][0]
     line_idx = np.searchsorted(offsets, pos[1])
@@ -462,24 +503,32 @@ def _label_clicked(pos, params):
 
 
 def _set_psd_plot_params(info, proj, picks, ax, area_mode):
-    """Aux function"""
+    """Set PSD plot params."""
     import matplotlib.pyplot as plt
     if area_mode not in [None, 'std', 'range']:
         raise ValueError('"area_mode" must be "std", "range", or None')
     if picks is None:
+        # XXX this could be refactored more with e.g., plot_evoked
         megs = ['mag', 'grad', False, False, False]
         eegs = [False, False, True, False, False]
         seegs = [False, False, False, True, False]
         ecogs = [False, False, False, False, True]
-        names = ['Magnetometers', 'Gradiometers', 'EEG', 'SEEG', 'ECoG']
+        names = ['mag', 'grad', 'eeg', 'seeg', 'ecog']
+        titles = _handle_default('titles', None)
+        units = _handle_default('units', None)
+        scalings = _handle_default('scalings', None)
         picks_list = list()
         titles_list = list()
+        units_list = list()
+        scalings_list = list()
         for meg, eeg, seeg, ecog, name in zip(megs, eegs, seegs, ecogs, names):
             picks = pick_types(info, meg=meg, eeg=eeg, seeg=seeg, ecog=ecog,
                                ref_meg=False)
             if len(picks) > 0:
                 picks_list.append(picks)
-                titles_list.append(name)
+                titles_list.append(titles[name])
+                units_list.append(units[name])
+                scalings_list.append(scalings[name])
         if len(picks_list) == 0:
             raise RuntimeError('No data channels found')
         if ax is not None:
@@ -493,6 +542,8 @@ def _set_psd_plot_params(info, proj, picks, ax, area_mode):
     else:
         picks_list = [picks]
         titles_list = ['Selected channels']
+        units_list = ['amplitude']
+        scalings_list = [1.]
         ax_list = [ax]
 
     make_label = False
@@ -511,15 +562,75 @@ def _set_psd_plot_params(info, proj, picks, ax, area_mode):
     else:
         fig = ax_list[0].get_figure()
 
-    return fig, picks_list, titles_list, ax_list, make_label
+    return (fig, picks_list, titles_list, units_list, scalings_list,
+            ax_list, make_label)
+
+
+def _convert_psds(psds, dB, estimate, scaling, unit, ch_names):
+    """Convert PSDs to dB (if necessary) and appropriate units.
+
+    The following table summarizes the relationship between the value of
+    parameters ``dB`` and ``estimate``, and the type of plot and corresponding
+    units.
+
+    | dB    | estimate    | plot | units             |
+    |-------+-------------+------+-------------------|
+    | True  | 'power'     | PSD  | amp**2/Hz (dB)    |
+    | True  | 'amplitude' | ASD  | amp/sqrt(Hz) (dB) |
+    | True  | 'auto'      | PSD  | amp**2/Hz (dB)    |
+    | False | 'power'     | PSD  | amp**2/Hz         |
+    | False | 'amplitude' | ASD  | amp/sqrt(Hz)      |
+    | False | 'auto'      | ASD  | amp/sqrt(Hz)      |
+
+    where amp are the units corresponding to the variable, as specified by
+    ``unit``.
+    """
+    where = np.where(psds.min(1) <= 0)[0]
+    dead_ch = ', '.join(ch_names[ii] for ii in where)
+    if len(where) > 0:
+        if dB:
+            msg = "Infinite value in PSD for channel(s) %s. " \
+                  "These channels might be dead." % dead_ch
+        else:
+            msg = "Zero value in PSD for channel(s) %s. " \
+                  "These channels might be dead." % dead_ch
+        warn(msg)
+
+    if estimate == 'auto':
+        if dB:
+            estimate = 'power'
+        else:
+            estimate = 'amplitude'
+
+    if estimate == 'amplitude':
+        np.sqrt(psds, out=psds)
+        psds *= scaling
+        ylabel = r'$\mathrm{%s / \sqrt{Hz}}$' % unit
+    else:
+        psds *= scaling * scaling
+        ylabel = r'$\mathrm{%s^2}/Hz}$' % unit
+
+    if dB:
+        np.log10(np.maximum(psds, np.finfo(float).tiny), out=psds)
+        psds *= 10
+        ylabel += r'$\ \mathrm{(dB)}$'
+
+    return ylabel
 
 
 @verbose
 def plot_raw_psd(raw, tmin=0., tmax=np.inf, fmin=0, fmax=np.inf, proj=False,
-                 n_fft=2048, picks=None, ax=None, color='black',
-                 area_mode='std', area_alpha=0.33,
-                 n_overlap=0, dB=True, show=True, n_jobs=1, verbose=None):
-    """Plot the power spectral density across channels
+                 n_fft=None, picks=None, ax=None, color='black',
+                 area_mode='std', area_alpha=0.33, n_overlap=0,
+                 dB=True, estimate='auto', average=None, show=True, n_jobs=1,
+                 line_alpha=None, spatial_colors=None, xscale='linear',
+                 reject_by_annotation=True, verbose=None):
+    """Plot the power spectral density across channels.
+
+    Different channel types are drawn in sub-plots. When the data has been
+    processed with a bandpass, lowpass or highpass filter, dashed lines
+    indicate the boundaries of the filter (--). The line noise frequency is
+    also indicated with a dashed line (-.).
 
     Parameters
     ----------
@@ -535,8 +646,10 @@ def plot_raw_psd(raw, tmin=0., tmax=np.inf, fmin=0, fmax=np.inf, proj=False,
         End frequency to consider.
     proj : bool
         Apply projection.
-    n_fft : int
+    n_fft : int | None
         Number of points to use in Welch FFT calculations.
+        Default is None, which uses the minimum of 2048 and the
+        number of time points.
     picks : array-like of int | None
         List of channels to use. Cannot be None if `ax` is supplied. If both
         `picks` and `ax` are None, separate subplots will be created for
@@ -544,73 +657,165 @@ def plot_raw_psd(raw, tmin=0., tmax=np.inf, fmin=0, fmax=np.inf, proj=False,
     ax : instance of matplotlib Axes | None
         Axes to plot into. If None, axes will be created.
     color : str | tuple
-        A matplotlib-compatible color to use.
+        A matplotlib-compatible color to use. Has no effect when
+        spatial_colors=True.
     area_mode : str | None
         Mode for plotting area. If 'std', the mean +/- 1 STD (across channels)
         will be plotted. If 'range', the min and max (across channels) will be
         plotted. Bad channels will be excluded from these calculations.
-        If None, no area will be plotted.
+        If None, no area will be plotted. If average=False, no area is plotted.
     area_alpha : float
         Alpha for the area.
     n_overlap : int
         The number of points of overlap between blocks. The default value
         is 0 (no overlap).
     dB : bool
-        If True, transform data to decibels.
+        Plot Power Spectral Density (PSD), in units (amplitude**2/Hz (dB)) if
+        ``dB=True``, and ``estimate='power'`` or ``estimate='auto'``. Plot PSD
+        in units (amplitude**2/Hz) if ``dB=False`` and,
+        ``estimate='power'``. Plot Amplitude Spectral Density (ASD), in units
+        (amplitude/sqrt(Hz)), if ``dB=False`` and ``estimate='amplitude'`` or
+        ``estimate='auto'``. Plot ASD, in units (amplitude/sqrt(Hz) (db)), if
+        ``dB=True`` and ``estimate='amplitude'``.
+    estimate : str, {'auto', 'power', 'amplitude'}
+        Can be "power" for power spectral density (PSD), "amplitude" for
+        amplitude spectrum density (ASD), or "auto" (default), which uses
+        "power" when dB is True and "amplitude" otherwise.
+    average : bool
+        If False, the PSDs of all channels is displayed. No averaging
+        is done and parameters area_mode and area_alpha are ignored. When
+        False, it is possible to paint an area (hold left mouse button and
+        drag) to plot a topomap.
     show : bool
         Show figure if True.
     n_jobs : int
         Number of jobs to run in parallel.
+    line_alpha : float | None
+        Alpha for the PSD line. Can be None (default) to use 1.0 when
+        ``average=True`` and 0.1 when ``average=False``.
+    spatial_colors : bool
+        Whether to use spatial colors. Only used when ``average=False``.
+    xscale : str
+        Can be 'linear' (default) or 'log'.
+    reject_by_annotation : bool
+        Whether to omit bad segments from the data while computing the
+        PSD. If True, annotated segments with a description that starts
+        with 'bad' are omitted. Has no effect if ``inst`` is an Epochs or
+        Evoked object. Defaults to True.
+
+        .. versionadded:: 0.15.0
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
     fig : instance of matplotlib figure
         Figure with frequency spectra of the data channels.
     """
-    fig, picks_list, titles_list, ax_list, make_label = _set_psd_plot_params(
-        raw.info, proj, picks, ax, area_mode)
-
-    for ii, (picks, title, ax) in enumerate(zip(picks_list, titles_list,
-                                                ax_list)):
+    from matplotlib.ticker import ScalarFormatter
+    if average is None:
+        warn('In version 0.15 average will default to False and '
+             'spatial_colors will default to True.', DeprecationWarning)
+        average = True
+
+    if average and spatial_colors:
+        raise ValueError('Average and spatial_colors cannot be enabled '
+                         'simultaneously.')
+    if spatial_colors is None:
+        spatial_colors = False if average else True
+
+    fig, picks_list, titles_list, units_list, scalings_list, ax_list, \
+        make_label = _set_psd_plot_params(raw.info, proj, picks, ax, area_mode)
+    del ax
+    if line_alpha is None:
+        line_alpha = 1.0 if average else 0.1
+    line_alpha = float(line_alpha)
+
+    psd_list = list()
+    ylabels = list()
+    if n_fft is None:
+        tmax = raw.times[-1] if not np.isfinite(tmax) else tmax
+        n_fft = min(np.diff(raw.time_as_index([tmin, tmax]))[0] + 1, 2048)
+    for ii, picks in enumerate(picks_list):
+        ax = ax_list[ii]
         psds, freqs = psd_welch(raw, tmin=tmin, tmax=tmax, picks=picks,
-                                fmin=fmin, fmax=fmax, proj=proj,
-                                n_fft=n_fft, n_overlap=n_overlap,
-                                n_jobs=n_jobs)
-
-        # Convert PSDs to dB
-        if dB:
-            psds = 10 * np.log10(psds)
-            if np.any(np.isinf(psds)):
-                where = np.flatnonzero(np.isinf(psds.min(1)))
-                chs = [raw.ch_names[i] for i in picks[where]]
-                raise ValueError("Infinite value in PSD for channel(s) %s. "
-                                 "These channels might be dead." %
-                                 ', '.join(chs))
-            unit = 'dB'
+                                fmin=fmin, fmax=fmax, proj=proj, n_fft=n_fft,
+                                n_overlap=n_overlap, n_jobs=n_jobs,
+                                reject_by_annotation=reject_by_annotation)
+
+        ylabel = _convert_psds(psds, dB, estimate, scalings_list[ii],
+                               units_list[ii],
+                               [raw.ch_names[pi] for pi in picks])
+
+        if average:
+            psd_mean = np.mean(psds, axis=0)
+            if area_mode == 'std':
+                psd_std = np.std(psds, axis=0)
+                hyp_limits = (psd_mean - psd_std, psd_mean + psd_std)
+            elif area_mode == 'range':
+                hyp_limits = (np.min(psds, axis=0), np.max(psds, axis=0))
+            else:  # area_mode is None
+                hyp_limits = None
+
+            ax.plot(freqs, psd_mean, color=color, alpha=line_alpha,
+                    linewidth=0.5)
+            if hyp_limits is not None:
+                ax.fill_between(freqs, hyp_limits[0], y2=hyp_limits[1],
+                                color=color, alpha=area_alpha)
         else:
-            unit = 'power'
-        psd_mean = np.mean(psds, axis=0)
-        if area_mode == 'std':
-            psd_std = np.std(psds, axis=0)
-            hyp_limits = (psd_mean - psd_std, psd_mean + psd_std)
-        elif area_mode == 'range':
-            hyp_limits = (np.min(psds, axis=0), np.max(psds, axis=0))
-        else:  # area_mode is None
-            hyp_limits = None
-
-        ax.plot(freqs, psd_mean, color=color)
-        if hyp_limits is not None:
-            ax.fill_between(freqs, hyp_limits[0], y2=hyp_limits[1],
-                            color=color, alpha=area_alpha)
+            psd_list.append(psds)
+
         if make_label:
             if ii == len(picks_list) - 1:
-                ax.set_xlabel('Freq (Hz)')
-            if ii == len(picks_list) // 2:
-                ax.set_ylabel('Power Spectral Density (%s/Hz)' % unit)
-            ax.set_title(title)
+                ax.set_xlabel('Frequency (Hz)')
+            ax.set_ylabel(ylabel)
+            ax.set_title(titles_list[ii])
             ax.set_xlim(freqs[0], freqs[-1])
+
+        ylabels.append(ylabel)
+
+    for key, ls in zip(['lowpass', 'highpass', 'line_freq'],
+                       ['--', '--', '-.']):
+        if raw.info[key] is not None:
+            for ax in ax_list:
+                ax.axvline(raw.info[key], color='k', linestyle=ls, alpha=0.25,
+                           linewidth=2, zorder=2)
+
+    if not average:
+        picks = np.concatenate(picks_list)
+
+        psd_list = np.concatenate(psd_list)
+        types = np.array([channel_type(raw.info, idx) for idx in picks])
+        # Needed because the data does not match the info anymore.
+        info = create_info([raw.ch_names[p] for p in picks], raw.info['sfreq'],
+                           types)
+        info['chs'] = [raw.info['chs'][p] for p in picks]
+        valid_channel_types = ['mag', 'grad', 'eeg', 'seeg', 'eog', 'ecg',
+                               'emg', 'dipole', 'gof', 'bio', 'ecog', 'hbo',
+                               'hbr', 'misc']
+        ch_types_used = list()
+        for this_type in valid_channel_types:
+            if this_type in types:
+                ch_types_used.append(this_type)
+        unit = ''
+        units = {t: yl for t, yl in zip(ch_types_used, ylabels)}
+        titles = {c: t for c, t in zip(ch_types_used, titles_list)}
+        picks = np.arange(len(psd_list))
+        if not spatial_colors:
+            spatial_colors = color
+        _plot_lines(psd_list, info, picks, fig, ax_list, spatial_colors,
+                    unit, units=units, scalings=None, hline=None, gfp=False,
+                    types=types, zorder='std', xlim=(freqs[0], freqs[-1]),
+                    ylim=None, times=freqs, bad_ch_idx=[], titles=titles,
+                    ch_types_used=ch_types_used, selectable=True, psd=True,
+                    line_alpha=line_alpha)
+    for ax in ax_list:
+        ax.grid(True, linestyle=':')
+        if xscale == 'log':
+            ax.set(xscale='log')
+            ax.set(xlim=[freqs[1] if freqs[0] == 0 else freqs[0], freqs[-1]])
+            ax.get_xaxis().set_major_formatter(ScalarFormatter())
     if make_label:
         tight_layout(pad=0.1, h_pad=0.1, w_pad=0.1, fig=fig)
     plt_show(show)
@@ -619,18 +824,18 @@ def plot_raw_psd(raw, tmin=0., tmax=np.inf, fmin=0, fmax=np.inf, proj=False,
 
 def _prepare_mne_browse_raw(params, title, bgcolor, color, bad_color, inds,
                             n_channels):
-    """Helper for setting up the mne_browse_raw window."""
+    """Set up the mne_browse_raw window."""
     import matplotlib.pyplot as plt
     import matplotlib as mpl
     size = get_config('MNE_BROWSE_RAW_SIZE')
     if size is not None:
         size = size.split(',')
         size = tuple([float(s) for s in size])
+        size = tuple([float(s) for s in size])
 
     fig = figure_nobar(facecolor=bgcolor, figsize=size)
-    fig.canvas.set_window_title('mne_browse_raw')
+    fig.canvas.set_window_title(title if title else "Raw")
     ax = plt.subplot2grid((10, 10), (0, 1), colspan=8, rowspan=9)
-    ax.set_title(title, fontsize=12)
     ax_hscroll = plt.subplot2grid((10, 10), (9, 1), colspan=8)
     ax_hscroll.get_yaxis().set_visible(False)
     ax_hscroll.set_xlabel('Time (s)')
@@ -689,15 +894,17 @@ def _prepare_mne_browse_raw(params, title, bgcolor, color, bad_color, inds,
                                        alpha=0.25, linewidth=1, clip_on=False)
     ax_hscroll.add_patch(hsel_patch)
     params['hsel_patch'] = hsel_patch
-    ax_hscroll.set_xlim(0, params['n_times'] / float(info['sfreq']))
+    ax_hscroll.set_xlim(params['first_time'], params['first_time'] +
+                        params['n_times'] / float(info['sfreq']))
 
     ax_vscroll.set_title('Ch.')
 
     vertline_color = (0., 0.75, 0.)
     params['ax_vertline'] = ax.plot([0, 0], ax.get_ylim(),
-                                    color=vertline_color, zorder=-1)[0]
+                                    color=vertline_color, zorder=4)[0]
     params['ax_vertline'].ch_name = ''
-    params['vertline_t'] = ax_hscroll.text(0, 1, '', color=vertline_color,
+    params['vertline_t'] = ax_hscroll.text(params['first_time'], 1, '',
+                                           color=vertline_color,
                                            va='bottom', ha='right')
     params['ax_hscroll_vertline'] = ax_hscroll.plot([0, 0], [0, 1],
                                                     color=vertline_color,
@@ -707,24 +914,38 @@ def _prepare_mne_browse_raw(params, title, bgcolor, color, bad_color, inds,
     ax.set_xlim(params['t_start'], params['t_start'] + params['duration'],
                 False)
 
-    params['lines'] = [ax.plot([np.nan], antialiased=False, linewidth=0.5)[0]
+    params['lines'] = [ax.plot([np.nan], antialiased=True, linewidth=0.5)[0]
                        for _ in range(n_ch)]
     ax.set_yticklabels(['X' * max([len(ch) for ch in info['ch_names']])])
+    params['fig_annotation'] = None
+    params['fig_help'] = None
+    params['segment_line'] = None
+
+    # default key to close window
+    params['close_key'] = 'escape'
 
 
 def _plot_raw_traces(params, color, bad_color, event_lines=None,
                      event_color=None):
-    """Helper for plotting raw"""
+    """Plot raw traces."""
     lines = params['lines']
     info = params['info']
     inds = params['inds']
-    n_channels = params['n_channels']
+    butterfly = params['butterfly']
+    if butterfly:
+        n_channels = len(params['offsets'])
+        ch_start = 0
+        offsets = params['offsets'][inds]
+    else:
+        n_channels = params['n_channels']
+        ch_start = params['ch_start']
+        offsets = params['offsets']
     params['bad_color'] = bad_color
     labels = params['ax'].yaxis.get_ticklabels()
     # do the plotting
     tick_list = list()
     for ii in range(n_channels):
-        ch_ind = ii + params['ch_start']
+        ch_ind = ii + ch_start
         # let's be generous here and allow users to pass
         # n_channels per view >= the number of traces available
         if ii >= len(lines):
@@ -733,26 +954,39 @@ def _plot_raw_traces(params, color, bad_color, event_lines=None,
             # scale to fit
             ch_name = info['ch_names'][inds[ch_ind]]
             tick_list += [ch_name]
-            offset = params['offsets'][ii]
-
+            offset = offsets[ii]
             # do NOT operate in-place lest this get screwed up
             this_data = params['data'][inds[ch_ind]] * params['scale_factor']
             this_color = bad_color if ch_name in info['bads'] else color
-            this_z = 0 if ch_name in info['bads'] else 1
+
             if isinstance(this_color, dict):
                 this_color = this_color[params['types'][inds[ch_ind]]]
 
-            # subtraction here gets corect orientation for flipped ylim
-            lines[ii].set_ydata(offset - this_data)
-            lines[ii].set_xdata(params['times'])
+            if inds[ch_ind] in params['data_picks']:
+                this_decim = params['decim']
+            else:
+                this_decim = 1
+            this_t = params['times'][::this_decim] + params['first_time']
+            # subtraction here gets correct orientation for flipped ylim
+            lines[ii].set_ydata(offset - this_data[..., ::this_decim])
+            lines[ii].set_xdata(this_t)
             lines[ii].set_color(this_color)
-            lines[ii].set_zorder(this_z)
             vars(lines[ii])['ch_name'] = ch_name
             vars(lines[ii])['def_color'] = color[params['types'][inds[ch_ind]]]
-
-            # set label color
-            this_color = bad_color if ch_name in info['bads'] else 'black'
-            labels[ii].set_color(this_color)
+            this_z = 0 if ch_name in info['bads'] else 1
+            if butterfly:
+                if ch_name not in info['bads']:
+                    if params['types'][ii] == 'mag':
+                        this_z = 2
+                    elif params['types'][ii] == 'grad':
+                        this_z = 3
+                for label in labels:
+                    label.set_color('black')
+            else:
+                # set label color
+                this_color = bad_color if ch_name in info['bads'] else 'black'
+                labels[ii].set_color(this_color)
+            lines[ii].set_zorder(this_z)
         else:
             # "remove" lines
             lines[ii].set_xdata([])
@@ -774,7 +1008,7 @@ def _plot_raw_traces(params, color, bad_color, event_lines=None,
             mask = (event_nums == ev_num) if ev_num >= 0 else ~used
             assert not np.any(used[mask])
             used[mask] = True
-            t = event_times[mask]
+            t = event_times[mask] + params['first_time']
             if len(t) > 0:
                 xs = list()
                 ys = list()
@@ -787,20 +1021,27 @@ def _plot_raw_traces(params, color, bad_color, event_lines=None,
                 line.set_xdata([])
                 line.set_ydata([])
 
+        params['ax'].texts = []   # delete event and annotation texts
+        # don't add event numbers for more than 50 visible events
+        if len(event_times) <= 50:
+            for ev_time, ev_num in zip(event_times, event_nums):
+                if -1 in event_color or ev_num in event_color:
+                    params['ax'].text(ev_time, -0.05, ev_num, fontsize=8,
+                                      ha='center')
+
     if 'segments' in params:
-        while len(params['ax'].collections) > 0:
-            params['ax'].collections.pop(0)
-            params['ax'].texts.pop(0)
+        while len(params['ax'].collections) > 0:  # delete previous annotations
+            params['ax'].collections.pop(-1)
         segments = params['segments']
         times = params['times']
         ylim = params['ax'].get_ylim()
         for idx, segment in enumerate(segments):
-            if segment[0] > times[-1]:
+            if segment[0] > times[-1] + params['first_time']:
                 break  # Since the segments are sorted by t_start
-            if segment[1] < times[0]:
+            if segment[1] < times[0] + params['first_time']:
                 continue
-            start = segment[0]
-            end = segment[1]
+            start = max(segment[0], times[0] + params['first_time'])
+            end = min(times[-1] + params['first_time'], segment[1])
             dscr = params['annot_description'][idx]
             segment_color = params['segment_colors'][dscr]
             params['ax'].fill_betweenx(ylim, start, end, color=segment_color,
@@ -808,9 +1049,11 @@ def _plot_raw_traces(params, color, bad_color, event_lines=None,
             params['ax'].text((start + end) / 2., ylim[0], dscr, ha='center')
 
     # finalize plot
-    params['ax'].set_xlim(params['times'][0],
-                          params['times'][0] + params['duration'], False)
-    params['ax'].set_yticklabels(tick_list)
+    params['ax'].set_xlim(params['times'][0] + params['first_time'],
+                          params['times'][0] + params['first_time'] +
+                          params['duration'], False)
+    if not butterfly:
+        params['ax'].set_yticklabels(tick_list, rotation=0)
     if 'fig_selection' not in params:
         params['vsel_patch'].set_y(params['ch_start'])
     params['fig'].canvas.draw()
@@ -824,8 +1067,9 @@ def _plot_raw_traces(params, color, bad_color, event_lines=None,
 def plot_raw_psd_topo(raw, tmin=0., tmax=None, fmin=0., fmax=100., proj=False,
                       n_fft=2048, n_overlap=0, layout=None, color='w',
                       fig_facecolor='k', axis_facecolor='k', dB=True,
-                      show=True, block=False, n_jobs=1, verbose=None):
-    """Function for plotting channel wise frequency spectra as topography.
+                      show=True, block=False, n_jobs=1, axes=None,
+                      verbose=None):
+    """Plot channel-wise frequency spectra as topography.
 
     Parameters
     ----------
@@ -867,8 +1111,11 @@ def plot_raw_psd_topo(raw, tmin=0., tmax=None, fmin=0., fmax=100., proj=False,
         May not work on all systems / platforms. Defaults to False.
     n_jobs : int
         Number of jobs to run in parallel. Defaults to 1.
+    axes : instance of matplotlib Axes | None
+        Axes to plot into. If None, axes will be created.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -898,7 +1145,7 @@ def plot_raw_psd_topo(raw, tmin=0., tmax=None, fmin=0., fmax=100., proj=False,
                      click_func=click_func, layout=layout,
                      axis_facecolor=axis_facecolor,
                      fig_facecolor=fig_facecolor, x_label='Frequency (Hz)',
-                     unified=True, y_label=y_label)
+                     unified=True, y_label=y_label, axes=axes)
 
     try:
         plt_show(show, block=block)
@@ -908,7 +1155,7 @@ def plot_raw_psd_topo(raw, tmin=0., tmax=None, fmin=0., fmax=100., proj=False,
 
 
 def _set_custom_selection(params):
-    """Callback for setting custom selection by lasso selector."""
+    """Set custom selection by lasso selector."""
     chs = params['fig_selection'].lasso.selection
     if len(chs) == 0:
         return
@@ -919,16 +1166,17 @@ def _set_custom_selection(params):
     _set_radio_button(labels.index('Custom'), params=params)
 
 
-def _setup_browser_selection(raw, kind):
-    """Helper for organizing browser selections."""
+def _setup_browser_selection(raw, kind, selector=True):
+    """Organize browser selections."""
     import matplotlib.pyplot as plt
     from matplotlib.widgets import RadioButtons
     from ..selection import (read_selection, _SELECTIONS, _EEG_SELECTIONS,
                              _divide_to_regions)
     from ..utils import _get_stim_channel
-    if kind in ('position'):
+    if kind == 'position':
         order = _divide_to_regions(raw.info)
         keys = _SELECTIONS[1:]  # no 'Vertex'
+        kind = 'position'
     elif 'selection':
         from ..io import RawFIF, RawArray
         if not isinstance(raw, (RawFIF, RawArray)):
@@ -955,6 +1203,8 @@ def _setup_browser_selection(raw, kind):
     if len(misc) > 0:
         order['Misc'] = misc
     keys = np.concatenate([keys, ['Misc']])
+    if not selector:
+        return order
     fig_selection = figure_nobar(figsize=(2, 6), dpi=80)
     fig_selection.canvas.set_window_title('Selection')
     rax = plt.subplot2grid((6, 1), (2, 0), rowspan=4, colspan=1)
diff --git a/mne/viz/tests/__init__py b/mne/viz/tests/__init__py
deleted file mode 100644
index e69de29..0000000
diff --git a/mne/viz/tests/test_3d.py b/mne/viz/tests/test_3d.py
index 4add583..4a38187 100644
--- a/mne/viz/tests/test_3d.py
+++ b/mne/viz/tests/test_3d.py
@@ -15,13 +15,20 @@ import numpy as np
 from numpy.testing import assert_raises, assert_equal
 
 from mne import (make_field_map, pick_channels_evoked, read_evokeds,
-                 read_trans, read_dipole, SourceEstimate)
-from mne.io import read_raw_ctf, read_raw_bti, read_raw_kit
+                 read_trans, read_dipole, SourceEstimate, VectorSourceEstimate,
+                 make_sphere_model)
+from mne.io import read_raw_ctf, read_raw_bti, read_raw_kit, read_info
+from mne.io.meas_info import write_dig
 from mne.viz import (plot_sparse_source_estimates, plot_source_estimates,
-                     plot_trans)
-from mne.utils import requires_mayavi, requires_pysurfer, run_tests_if_main
+                     plot_trans, snapshot_brain_montage, plot_head_positions,
+                     plot_alignment)
+from mne.viz.utils import _fake_click
+from mne.utils import (requires_mayavi, requires_pysurfer, run_tests_if_main,
+                       _import_mlab, _TempDir, requires_nibabel, check_version,
+                       requires_version)
 from mne.datasets import testing
 from mne.source_space import read_source_spaces
+from mne.bem import read_bem_solution, read_bem_surfaces
 
 
 # Set our plotters to test mode
@@ -49,12 +56,26 @@ sqd_fname = op.join(io_dir, 'kit', 'tests', 'data', 'test.sqd')
 warnings.simplefilter('always')  # enable b/c these tests throw warnings
 
 
+def test_plot_head_positions():
+    """Test plotting of head positions."""
+    import matplotlib.pyplot as plt
+    pos = np.random.RandomState(0).randn(4, 10)
+    pos[:, 0] = np.arange(len(pos))
+    with warnings.catch_warnings(record=True):  # old MPL will cause a warning
+        plot_head_positions(pos)
+        if check_version('matplotlib', '1.4'):
+            plot_head_positions(pos, mode='field')
+        else:
+            assert_raises(RuntimeError, plot_head_positions, pos, mode='field')
+    assert_raises(ValueError, plot_head_positions, pos, 'foo')
+    plt.close('all')
+
+
 @testing.requires_testing_data
 @requires_pysurfer
 @requires_mayavi
 def test_plot_sparse_source_estimates():
-    """Test plotting of (sparse) source estimates
-    """
+    """Test plotting of (sparse) source estimates."""
     sample_src = read_source_spaces(src_fname)
 
     # dense version
@@ -63,10 +84,11 @@ def test_plot_sparse_source_estimates():
     n_verts = sum(len(v) for v in vertices)
     stc_data = np.zeros((n_verts * n_time))
     stc_size = stc_data.size
-    stc_data[(np.random.rand(stc_size / 20) * stc_size).astype(int)] = \
-        np.random.RandomState(0).rand(stc_data.size / 20)
+    stc_data[(np.random.rand(stc_size // 20) * stc_size).astype(int)] = \
+        np.random.RandomState(0).rand(stc_data.size // 20)
     stc_data.shape = (n_verts, n_time)
     stc = SourceEstimate(stc_data, vertices, 1, 1)
+
     colormap = 'mne_analyze'
     plot_source_estimates(stc, 'sample', colormap=colormap,
                           background=(1, 1, 0),
@@ -91,8 +113,7 @@ def test_plot_sparse_source_estimates():
 @testing.requires_testing_data
 @requires_mayavi
 def test_plot_evoked_field():
-    """Test plotting evoked field
-    """
+    """Test plotting evoked field."""
     evoked = read_evokeds(evoked_fname, condition='Left Auditory',
                           baseline=(-0.2, 0.0))
     evoked = pick_channels_evoked(evoked, evoked.ch_names[::10])  # speed
@@ -106,10 +127,22 @@ def test_plot_evoked_field():
 
 @testing.requires_testing_data
 @requires_mayavi
-def test_plot_trans():
-    """Test plotting of -trans.fif files and MEG sensor layouts
-    """
+def test_plot_alignment():
+    """Test plotting of -trans.fif files and MEG sensor layouts."""
+    # generate fiducials file for testing
+    tempdir = _TempDir()
+    fiducials_path = op.join(tempdir, 'fiducials.fif')
+    fid = [{'coord_frame': 5, 'ident': 1, 'kind': 1,
+            'r': [-0.08061612, -0.02908875, -0.04131077]},
+           {'coord_frame': 5, 'ident': 2, 'kind': 1,
+            'r': [0.00146763, 0.08506715, -0.03483611]},
+           {'coord_frame': 5, 'ident': 3, 'kind': 1,
+            'r': [0.08436285, -0.02850276, -0.04127743]}]
+    write_dig(fiducials_path, fid, 5)
+
+    mlab = _import_mlab()
     evoked = read_evokeds(evoked_fname)[0]
+    sample_src = read_source_spaces(src_fname)
     with warnings.catch_warnings(record=True):  # 4D weight tables
         bti = read_raw_bti(pdf_fname, config_fname, hs_fname, convert=True,
                            preload=False).info
@@ -120,33 +153,94 @@ def test_plot_trans():
         KIT=read_raw_kit(sqd_fname).info,
     )
     for system, info in infos.items():
-        ref_meg = False if system == 'KIT' else True
-        plot_trans(info, trans_fname, subject='sample', meg_sensors=True,
-                   subjects_dir=subjects_dir, ref_meg=ref_meg)
+        meg = ['helmet', 'sensors']
+        if system == 'KIT':
+            meg.append('ref')
+        plot_alignment(info, trans_fname, subject='sample',
+                       subjects_dir=subjects_dir, meg=meg)
+        mlab.close(all=True)
     # KIT ref sensor coil def is defined
-    plot_trans(infos['KIT'], None, meg_sensors=True, ref_meg=True)
+    with warnings.catch_warnings(record=True):  # deprecated
+        plot_trans(infos['KIT'], None, meg_sensors=True, ref_meg=True)
+    mlab.close(all=True)
     info = infos['Neuromag']
-    assert_raises(ValueError, plot_trans, info, trans_fname,
-                  subject='sample', subjects_dir=subjects_dir,
-                  ch_type='bad-chtype')
-    assert_raises(TypeError, plot_trans, 'foo', trans_fname,
+    assert_raises(TypeError, plot_alignment, 'foo', trans_fname,
                   subject='sample', subjects_dir=subjects_dir)
+    assert_raises(TypeError, plot_alignment, info, trans_fname,
+                  subject='sample', subjects_dir=subjects_dir, src='foo')
+    assert_raises(ValueError, plot_alignment, info, trans_fname,
+                  subject='fsaverage', subjects_dir=subjects_dir,
+                  src=sample_src)
+    sample_src.plot(subjects_dir=subjects_dir, head=True, skull=True,
+                    brain='white')
+    mlab.close(all=True)
     # no-head version
-    plot_trans(info, None, meg_sensors=True, dig=True, coord_frame='head')
+    with warnings.catch_warnings(record=True):  # deprecated
+        plot_trans(info, None, meg_sensors=True, dig=True, coord_frame='head')
+    mlab.close(all=True)
+    # all coord frames
+    for coord_frame in ('meg', 'head', 'mri'):
+        plot_alignment(info, meg=['helmet', 'sensors'], dig=True,
+                       coord_frame=coord_frame, trans=trans_fname,
+                       subject='sample', mri_fiducials=fiducials_path,
+                       subjects_dir=subjects_dir, src=sample_src)
+        mlab.close(all=True)
     # EEG only with strange options
+    evoked_eeg_ecog = evoked.copy().pick_types(meg=False, eeg=True)
+    evoked_eeg_ecog.info['projs'] = []  # "remove" avg proj
+    evoked_eeg_ecog.set_channel_types({'EEG 001': 'ecog'})
     with warnings.catch_warnings(record=True) as w:
-        plot_trans(evoked.copy().pick_types(meg=False, eeg=True).info,
-                   trans=trans_fname, meg_sensors=True)
+        plot_alignment(evoked_eeg_ecog.info, subject='sample',
+                       trans=trans_fname, subjects_dir=subjects_dir,
+                       surfaces=['white', 'outer_skin', 'outer_skull'],
+                       meg=['helmet', 'sensors'],
+                       eeg=['original', 'projected'], ecog=True)
+    mlab.close(all=True)
     assert_true(['Cannot plot MEG' in str(ww.message) for ww in w])
 
+    sphere = make_sphere_model(info=evoked.info, r0='auto', head_radius='auto')
+    bem_sol = read_bem_solution(op.join(subjects_dir, 'sample', 'bem',
+                                        'sample-1280-1280-1280-bem-sol.fif'))
+    bem_surfs = read_bem_surfaces(op.join(subjects_dir, 'sample', 'bem',
+                                          'sample-1280-1280-1280-bem.fif'))
+    sample_src[0]['coord_frame'] = 4  # hack for coverage
+    plot_alignment(info, trans_fname, subject='sample', meg='helmet',
+                   subjects_dir=subjects_dir, eeg='projected', bem=sphere,
+                   surfaces=['head', 'brain', 'inner_skull', 'outer_skull'],
+                   src=sample_src)
+    plot_alignment(info, trans_fname, subject='sample', meg=[],
+                   subjects_dir=subjects_dir, bem=bem_sol, eeg=True,
+                   surfaces=['head', 'inflated', 'outer_skull', 'inner_skull'])
+    plot_alignment(info, trans_fname, subject='sample',
+                   meg=True, subjects_dir=subjects_dir,
+                   surfaces=['head', 'inner_skull'], bem=bem_surfs)
+    sphere = make_sphere_model('auto', None, evoked.info)  # one layer
+    plot_alignment(info, trans_fname, subject='sample', meg=False,
+                   coord_frame='mri', subjects_dir=subjects_dir,
+                   surfaces=['brain'], bem=sphere)
+    # one layer bem with skull surfaces:
+    assert_raises(ValueError, plot_alignment, info=info, trans=trans_fname,
+                  subject='sample', subjects_dir=subjects_dir,
+                  surfaces=['brain', 'head', 'inner_skull'], bem=sphere)
+    # wrong eeg value:
+    assert_raises(ValueError, plot_alignment, info=info, trans=trans_fname,
+                  subject='sample', subjects_dir=subjects_dir, eeg='foo')
+    # wrong meg value:
+    assert_raises(ValueError, plot_alignment, info=info, trans=trans_fname,
+                  subject='sample', subjects_dir=subjects_dir, meg='bar')
+    # multiple brain surfaces:
+    assert_raises(ValueError, plot_alignment, info=info, trans=trans_fname,
+                  subject='sample', subjects_dir=subjects_dir,
+                  surfaces=['white', 'pial'])
+
 
 @testing.requires_testing_data
 @requires_pysurfer
 @requires_mayavi
 def test_limits_to_control_points():
-    """Test functionality for determing control points
-    """
+    """Test functionality for determing control points."""
     sample_src = read_source_spaces(src_fname)
+    kwargs = dict(subjects_dir=subjects_dir, smoothing_steps=1)
 
     vertices = [s['vertno'] for s in sample_src]
     n_time = 5
@@ -156,72 +250,139 @@ def test_limits_to_control_points():
     stc = SourceEstimate(stc_data, vertices, 1, 1, 'sample')
 
     # Test for simple use cases
-    from mayavi import mlab
-    stc.plot(subjects_dir=subjects_dir)
-    stc.plot(clim=dict(pos_lims=(10, 50, 90)), subjects_dir=subjects_dir)
-    stc.plot(clim=dict(kind='value', lims=(10, 50, 90)), figure=99,
-             subjects_dir=subjects_dir)
-    stc.plot(colormap='hot', clim='auto', subjects_dir=subjects_dir)
-    stc.plot(colormap='mne', clim='auto', subjects_dir=subjects_dir)
+    mlab = _import_mlab()
+    stc.plot(**kwargs)
+    stc.plot(clim=dict(pos_lims=(10, 50, 90)), **kwargs)
+    stc.plot(colormap='hot', clim='auto', **kwargs)
+    stc.plot(colormap='mne', clim='auto', **kwargs)
     figs = [mlab.figure(), mlab.figure()]
-    assert_raises(ValueError, stc.plot, clim='auto', figure=figs,
-                  subjects_dir=subjects_dir)
+    stc.plot(clim=dict(kind='value', lims=(10, 50, 90)), figure=99, **kwargs)
+    assert_raises(ValueError, stc.plot, clim='auto', figure=figs, **kwargs)
 
     # Test both types of incorrect limits key (lims/pos_lims)
     assert_raises(KeyError, plot_source_estimates, stc, colormap='mne',
-                  clim=dict(kind='value', lims=(5, 10, 15)),
-                  subjects_dir=subjects_dir)
+                  clim=dict(kind='value', lims=(5, 10, 15)), **kwargs)
     assert_raises(KeyError, plot_source_estimates, stc, colormap='hot',
-                  clim=dict(kind='value', pos_lims=(5, 10, 15)),
-                  subjects_dir=subjects_dir)
+                  clim=dict(kind='value', pos_lims=(5, 10, 15)), **kwargs)
 
     # Test for correct clim values
     assert_raises(ValueError, stc.plot,
-                  clim=dict(kind='value', pos_lims=[0, 1, 0]),
-                  subjects_dir=subjects_dir)
+                  clim=dict(kind='value', pos_lims=[0, 1, 0]), **kwargs)
     assert_raises(ValueError, stc.plot, colormap='mne',
-                  clim=dict(pos_lims=(5, 10, 15, 20)),
-                  subjects_dir=subjects_dir)
+                  clim=dict(pos_lims=(5, 10, 15, 20)), **kwargs)
     assert_raises(ValueError, stc.plot,
-                  clim=dict(pos_lims=(5, 10, 15), kind='foo'),
-                  subjects_dir=subjects_dir)
-    assert_raises(ValueError, stc.plot, colormap='mne', clim='foo',
-                  subjects_dir=subjects_dir)
-    assert_raises(ValueError, stc.plot, clim=(5, 10, 15),
-                  subjects_dir=subjects_dir)
+                  clim=dict(pos_lims=(5, 10, 15), kind='foo'), **kwargs)
+    assert_raises(ValueError, stc.plot, colormap='mne', clim='foo', **kwargs)
+    assert_raises(ValueError, stc.plot, clim=(5, 10, 15), **kwargs)
     assert_raises(ValueError, plot_source_estimates, 'foo', clim='auto',
-                  subjects_dir=subjects_dir)
-    assert_raises(ValueError, stc.plot, hemi='foo', clim='auto',
-                  subjects_dir=subjects_dir)
+                  **kwargs)
+    assert_raises(ValueError, stc.plot, hemi='foo', clim='auto', **kwargs)
 
     # Test handling of degenerate data
-    stc.plot(clim=dict(kind='value', lims=[0, 0, 1]),
-             subjects_dir=subjects_dir)  # ok
     with warnings.catch_warnings(record=True) as w:
         warnings.simplefilter('always')
         # thresholded maps
-        stc._data.fill(1.)
-        plot_source_estimates(stc, subjects_dir=subjects_dir, time_unit='s')
-        assert_equal(len(w), 0)
-        stc._data[0].fill(0.)
-        plot_source_estimates(stc, subjects_dir=subjects_dir, time_unit='s')
-        assert_equal(len(w), 0)
         stc._data.fill(0.)
-        plot_source_estimates(stc, subjects_dir=subjects_dir, time_unit='s')
+        plot_source_estimates(stc, **kwargs)
         assert_equal(len(w), 1)
-    mlab.close()
+    mlab.close(all=True)
 
 
 @testing.requires_testing_data
- at requires_mayavi
-def test_plot_dipole_locations():
-    """Test plotting dipole locations
-    """
+ at requires_nibabel()
+def test_stc_mpl():
+    """Test plotting source estimates with matplotlib."""
+    import matplotlib.pyplot as plt
+    sample_src = read_source_spaces(src_fname)
+
+    vertices = [s['vertno'] for s in sample_src]
+    n_time = 5
+    n_verts = sum(len(v) for v in vertices)
+    stc_data = np.ones((n_verts * n_time))
+    stc_data.shape = (n_verts, n_time)
+    stc = SourceEstimate(stc_data, vertices, 1, 1, 'sample')
+    with warnings.catch_warnings(record=True):  # vertices not included
+        stc.plot(subjects_dir=subjects_dir, time_unit='s', views='ven',
+                 hemi='rh', smoothing_steps=2, subject='sample',
+                 backend='matplotlib', spacing='oct1', initial_time=0.001,
+                 colormap='Reds')
+        fig = stc.plot(subjects_dir=subjects_dir, time_unit='ms', views='dor',
+                       hemi='lh', smoothing_steps=2, subject='sample',
+                       backend='matplotlib', spacing='ico2', time_viewer=True)
+        time_viewer = fig.time_viewer
+        _fake_click(time_viewer, time_viewer.axes[0], (0.5, 0.5))  # change t
+        time_viewer.canvas.key_press_event('ctrl+right')
+        time_viewer.canvas.key_press_event('left')
+    assert_raises(ValueError, stc.plot, subjects_dir=subjects_dir,
+                  hemi='both', subject='sample', backend='matplotlib')
+    assert_raises(ValueError, stc.plot, subjects_dir=subjects_dir,
+                  time_unit='ss', subject='sample', backend='matplotlib')
+    plt.close('all')
+
+
+ at testing.requires_testing_data
+ at requires_nibabel()
+def test_plot_dipole_mri_orthoview():
+    """Test mpl dipole plotting."""
+    import matplotlib.pyplot as plt
     dipoles = read_dipole(dip_fname)
     trans = read_trans(trans_fname)
-    dipoles.plot_locations(trans, 'sample', subjects_dir, fig_name='foo')
+    for coord_frame, idx, show_all in zip(['head', 'mri'],
+                                          ['gof', 'amplitude'], [True, False]):
+        fig = dipoles.plot_locations(trans, 'sample', subjects_dir,
+                                     coord_frame=coord_frame, idx=idx,
+                                     show_all=show_all, mode='orthoview')
+        fig.canvas.scroll_event(0.5, 0.5, 1)  # scroll up
+        fig.canvas.scroll_event(0.5, 0.5, -1)  # scroll down
+        fig.canvas.key_press_event('up')
+        fig.canvas.key_press_event('down')
+        fig.canvas.key_press_event('a')  # some other key
+    ax = plt.subplot(111)
     assert_raises(ValueError, dipoles.plot_locations, trans, 'sample',
-                  subjects_dir, mode='foo')
+                  subjects_dir, ax=ax)
+    plt.close('all')
+
+
+ at requires_mayavi
+def test_snapshot_brain_montage():
+    """Test snapshot brain montage."""
+    info = read_info(evoked_fname)
+    with warnings.catch_warnings(record=True):  # deprecated
+        fig = plot_trans(
+            info, trans=None, subject='sample', subjects_dir=subjects_dir,
+            skull=['outer_skull', 'inner_skull'])
+
+    xyz = np.vstack([ich['loc'][:3] for ich in info['chs']])
+    ch_names = [ich['ch_name'] for ich in info['chs']]
+    xyz_dict = dict(zip(ch_names, xyz))
+    xyz_dict[info['chs'][0]['ch_name']] = [1, 2]  # Set one ch to only 2 vals
+
+    # Make sure wrong types are checked
+    assert_raises(ValueError, snapshot_brain_montage, fig, xyz)
+
+    # All chs must have 3 position values
+    assert_raises(ValueError, snapshot_brain_montage, fig, xyz_dict)
+
+    # Make sure we raise error if the figure has no scene
+    assert_raises(TypeError, snapshot_brain_montage, fig, info)
+
+
+ at testing.requires_testing_data
+ at requires_version('surfer', '0.8')
+ at requires_mayavi
+def test_plot_vec_source_estimates():
+    """Test plotting of vector source estimates."""
+    sample_src = read_source_spaces(src_fname)
+
+    vertices = [s['vertno'] for s in sample_src]
+    n_verts = sum(len(v) for v in vertices)
+    n_time = 5
+    data = np.random.RandomState(0).rand(n_verts, 3, n_time)
+    stc = VectorSourceEstimate(data, vertices, 1, 1)
+
+    with warnings.catch_warnings(record=True):
+        warnings.simplefilter('always')
+        stc.plot('sample', subjects_dir=subjects_dir)
 
 
 run_tests_if_main()
diff --git a/mne/viz/tests/test_decoding.py b/mne/viz/tests/test_decoding.py
index 401f2da..910280c 100644
--- a/mne/viz/tests/test_decoding.py
+++ b/mne/viz/tests/test_decoding.py
@@ -6,7 +6,7 @@
 import os.path as op
 import warnings
 
-from nose.tools import assert_raises, assert_equals
+from nose.tools import assert_raises, assert_equal
 
 import numpy as np
 
@@ -14,7 +14,7 @@ from mne.epochs import equalize_epoch_counts, concatenate_epochs
 from mne.decoding import GeneralizationAcrossTime
 from mne import Epochs, read_events, pick_types
 from mne.io import read_raw_fif
-from mne.utils import requires_sklearn, run_tests_if_main
+from mne.utils import requires_version, run_tests_if_main
 import matplotlib
 matplotlib.use('Agg')  # for testing don't use X server
 
@@ -29,9 +29,10 @@ warnings.simplefilter('always')  # enable b/c these tests throw warnings
 
 def _get_data(tmin=-0.2, tmax=0.5, event_id=dict(aud_l=1, vis_l=3),
               event_id_gen=dict(aud_l=2, vis_l=4), test_times=None):
-    """Aux function for testing GAT viz"""
-    gat = GeneralizationAcrossTime()
-    raw = read_raw_fif(raw_fname, preload=False, add_eeg_ref=False)
+    """Aux function for testing GAT viz."""
+    with warnings.catch_warnings(record=True):  # deprecated
+        gat = GeneralizationAcrossTime()
+    raw = read_raw_fif(raw_fname)
     raw.add_proj([], remove_existing=True)
     events = read_events(event_name)
     picks = pick_types(raw.info, meg='mag', stim=False, ecg=False,
@@ -41,87 +42,94 @@ def _get_data(tmin=-0.2, tmax=0.5, event_id=dict(aud_l=1, vis_l=3),
     # Test on time generalization within one condition
     with warnings.catch_warnings(record=True):
         epochs = Epochs(raw, events, event_id, tmin, tmax, picks=picks,
-                        baseline=(None, 0), preload=True, decim=decim,
-                        add_eeg_ref=False)
+                        preload=True, decim=decim)
     epochs_list = [epochs[k] for k in event_id]
     equalize_epoch_counts(epochs_list)
     epochs = concatenate_epochs(epochs_list)
 
     # Test default running
-    gat = GeneralizationAcrossTime(test_times=test_times)
+    with warnings.catch_warnings(record=True):  # deprecated
+        gat = GeneralizationAcrossTime(test_times=test_times)
     gat.fit(epochs)
     gat.score(epochs)
     return gat
 
 
- at requires_sklearn
+ at requires_version('sklearn', '0.17')
 def test_gat_plot_matrix():
-    """Test GAT matrix plot"""
+    """Test GAT matrix plot."""
     gat = _get_data()
-    gat.plot()
-    del gat.scores_
-    assert_raises(RuntimeError, gat.plot)
+    with warnings.catch_warnings(record=True):  # deprecated
+        gat.plot()
+        del gat.scores_
+        assert_raises(RuntimeError, gat.plot)
 
 
- at requires_sklearn
+ at requires_version('sklearn', '0.17')
 def test_gat_plot_diagonal():
-    """Test GAT diagonal plot"""
+    """Test GAT diagonal plot."""
     gat = _get_data()
-    gat.plot_diagonal()
-    del gat.scores_
-    assert_raises(RuntimeError, gat.plot)
+    with warnings.catch_warnings(record=True):  # deprecated
+        gat.plot_diagonal()
+        del gat.scores_
+        assert_raises(RuntimeError, gat.plot)
 
 
- at requires_sklearn
+ at requires_version('sklearn', '0.17')
 def test_gat_plot_times():
-    """Test GAT times plot"""
+    """Test GAT times plot."""
     gat = _get_data()
     # test one line
-    gat.plot_times(gat.train_times_['times'][0])
-    # test multiple lines
-    gat.plot_times(gat.train_times_['times'])
+    with warnings.catch_warnings(record=True):  # deprecated
+        gat.plot_times(gat.train_times_['times'][0])
+        # test multiple lines
+        gat.plot_times(gat.train_times_['times'])
     # test multiple colors
     n_times = len(gat.train_times_['times'])
     colors = np.tile(['r', 'g', 'b'],
                      int(np.ceil(n_times / 3)))[:n_times]
-    gat.plot_times(gat.train_times_['times'], color=colors)
-    # test invalid time point
-    assert_raises(ValueError, gat.plot_times, -1.)
-    # test float type
-    assert_raises(ValueError, gat.plot_times, 1)
-    assert_raises(ValueError, gat.plot_times, 'diagonal')
-    del gat.scores_
-    assert_raises(RuntimeError, gat.plot)
+    with warnings.catch_warnings(record=True):
+        gat.plot_times(gat.train_times_['times'], color=colors)
+        # test invalid time point
+        assert_raises(ValueError, gat.plot_times, -1.)
+        # test float type
+        assert_raises(ValueError, gat.plot_times, 1)
+        assert_raises(ValueError, gat.plot_times, 'diagonal')
+        del gat.scores_
+        assert_raises(RuntimeError, gat.plot)
 
 
 def chance(ax):
     return ax.get_children()[1].get_lines()[0].get_ydata()[0]
 
 
- at requires_sklearn
+ at requires_version('sklearn', '0.17')
 def test_gat_chance_level():
-    """Test GAT plot_times chance level"""
+    """Test GAT plot_times chance level."""
     gat = _get_data()
-    ax = gat.plot_diagonal(chance=False)
-    ax = gat.plot_diagonal()
-    assert_equals(chance(ax), .5)
-    gat = _get_data(event_id=dict(aud_l=1, vis_l=3, aud_r=2, vis_r=4))
-    ax = gat.plot_diagonal()
-    assert_equals(chance(ax), .25)
-    ax = gat.plot_diagonal(chance=1.234)
-    assert_equals(chance(ax), 1.234)
-    assert_raises(ValueError, gat.plot_diagonal, chance='foo')
-    del gat.scores_
-    assert_raises(RuntimeError, gat.plot)
-
-
- at requires_sklearn
+    with warnings.catch_warnings(record=True):  # deprecated
+        ax = gat.plot_diagonal(chance=False)
+        ax = gat.plot_diagonal()
+        assert_equal(chance(ax), .5)
+        gat = _get_data(event_id=dict(aud_l=1, vis_l=3, aud_r=2, vis_r=4))
+        ax = gat.plot_diagonal()
+        assert_equal(chance(ax), .25)
+        ax = gat.plot_diagonal(chance=1.234)
+        assert_equal(chance(ax), 1.234)
+        assert_raises(ValueError, gat.plot_diagonal, chance='foo')
+        del gat.scores_
+        assert_raises(RuntimeError, gat.plot)
+
+
+ at requires_version('sklearn', '0.17')
 def test_gat_plot_nonsquared():
-    """Test GAT diagonal plot"""
+    """Test GAT diagonal plot."""
     gat = _get_data(test_times=dict(start=0.))
-    gat.plot()
-    ax = gat.plot_diagonal()
+    with warnings.catch_warnings(record=True):  # deprecated
+        gat.plot()
+        ax = gat.plot_diagonal()
     scores = ax.get_children()[1].get_lines()[2].get_ydata()
-    assert_equals(len(scores), len(gat.estimators_))
+    assert_equal(len(scores), len(gat.estimators_))
+
 
 run_tests_if_main()
diff --git a/mne/viz/tests/test_epochs.py b/mne/viz/tests/test_epochs.py
index ce36a5e..b88513b 100644
--- a/mne/viz/tests/test_epochs.py
+++ b/mne/viz/tests/test_epochs.py
@@ -37,52 +37,26 @@ n_chan = 15
 layout = read_layout('Vectorview-all')
 
 
-def _get_raw():
-    """Get raw data."""
-    return read_raw_fif(raw_fname, preload=False, add_eeg_ref=False)
-
-
-def _get_events():
-    """Get events."""
-    return read_events(event_name)
-
-
-def _get_picks(raw):
-    """Get picks."""
-    return pick_types(raw.info, meg=True, eeg=False, stim=False,
-                      ecg=False, eog=False, exclude='bads')
-
-
 def _get_epochs():
     """Get epochs."""
-    raw = _get_raw()
-    events = _get_events()
-    picks = _get_picks(raw)
+    raw = read_raw_fif(raw_fname)
+    events = read_events(event_name)
+    picks = pick_types(raw.info, meg=True, eeg=False, stim=False,
+                       ecg=False, eog=False, exclude='bads')
     # Use a subset of channels for plotting speed
     picks = np.round(np.linspace(0, len(picks) + 1, n_chan)).astype(int)
     with warnings.catch_warnings(record=True):  # bad proj
         epochs = Epochs(raw, events[:5], event_id, tmin, tmax, picks=picks,
-                        baseline=(None, 0), add_eeg_ref=False)
+                        proj=False)
+    epochs.info.normalize_proj()  # avoid warnings
     return epochs
 
 
-def _get_epochs_delayed_ssp():
-    """Get epochs with delayed SSP."""
-    raw = _get_raw()
-    events = _get_events()
-    picks = _get_picks(raw)
-    reject = dict(mag=4e-12)
-    epochs_delayed_ssp = Epochs(
-        raw, events[:10], event_id, tmin, tmax, picks=picks,
-        baseline=(None, 0), proj='delayed', reject=reject, add_eeg_ref=False)
-    return epochs_delayed_ssp
-
-
 def test_plot_epochs():
-    """Test epoch plotting"""
+    """Test epoch plotting."""
     import matplotlib.pyplot as plt
     epochs = _get_epochs()
-    epochs.info.normalize_proj()  # avoid warnings
+    epochs.info['lowpass'] = 10.  # allow heavy decim during plotting
     epochs.plot(scalings=None, title='Epochs')
     plt.close('all')
     fig = epochs[0].plot(picks=[0, 2, 3], scalings=None)
@@ -113,7 +87,7 @@ def test_plot_epochs():
     assert_raises(RuntimeError, epochs.plot, picks=[])
     plt.close('all')
     with warnings.catch_warnings(record=True):
-        fig = epochs.plot()
+        fig = epochs.plot(events=epochs.events)
         # test mouse clicks
         x = fig.get_axes()[0].get_xlim()[1] / 2
         y = fig.get_axes()[0].get_ylim()[0] / 2
@@ -134,29 +108,49 @@ def test_plot_epochs():
 
 
 def test_plot_epochs_image():
-    """Test plotting of epochs image
-    """
+    """Test plotting of epochs image."""
     import matplotlib.pyplot as plt
     epochs = _get_epochs()
     epochs.plot_image(picks=[1, 2])
     overlay_times = [0.1]
-    epochs.plot_image(order=[0], overlay_times=overlay_times, vmin=0.01)
-    epochs.plot_image(overlay_times=overlay_times, vmin=-0.001, vmax=0.001)
+    epochs.plot_image(picks=[1], order=[0], overlay_times=overlay_times,
+                      vmin=0.01, title="test"
+                      )
+    epochs.plot_image(picks=[1], overlay_times=overlay_times, vmin=-0.001,
+                      vmax=0.001)
     assert_raises(ValueError, epochs.plot_image,
-                  overlay_times=[0.1, 0.2])
+                  picks=[1], overlay_times=[0.1, 0.2])
     assert_raises(ValueError, epochs.plot_image,
-                  order=[0, 1])
+                  picks=[1], order=[0, 1])
+    assert_raises(ValueError, epochs.plot_image, axes=dict(), group_by=list(),
+                  combine='mean')
+    assert_raises(ValueError, epochs.plot_image, axes=list(), group_by=dict(),
+                  combine='mean')
+    with warnings.catch_warnings(record=True):  # deprecated combine as str
+        assert_raises(ValueError, epochs.plot_image, combine='error',
+                      picks=[1, 2])
+    assert_raises(ValueError, epochs.plot_image, units={"hi": 1},
+                  scalings={"ho": 1})
+    epochs.load_data().pick_types(meg='mag')
+    epochs.info.normalize_proj()
+    with warnings.catch_warnings(record=True):  # projs
+        epochs.plot_image(group_by='type', combine='mean')
+        epochs.plot_image(group_by={"1": [1, 2], "2": [1, 2]}, combine='mean')
+        epochs.plot_image(vmin=lambda x: x.min())
+        assert_raises(ValueError, epochs.plot_image, axes=1, fig=2)
+    ts_args = dict(show_sensors=False)
     with warnings.catch_warnings(record=True) as w:
-        epochs.plot_image(overlay_times=[1.1])
+        epochs.plot_image(overlay_times=[1.1], combine="gfp", ts_args=ts_args)
+        assert_raises(ValueError, epochs.plot_image, combine='error',
+                      ts_args=ts_args)
         warnings.simplefilter('always')
-    assert_equal(len(w), 1)
+    assert_equal(len(w), 4)
 
     plt.close('all')
 
 
 def test_plot_drop_log():
-    """Test plotting a drop log
-    """
+    """Test plotting a drop log."""
     import matplotlib.pyplot as plt
     epochs = _get_epochs()
     assert_raises(ValueError, epochs.plot_drop_log)
@@ -174,8 +168,7 @@ def test_plot_drop_log():
 
 @requires_version('scipy', '0.12')
 def test_plot_psd_epochs():
-    """Test plotting epochs psd (+topomap)
-    """
+    """Test plotting epochs psd (+topomap)."""
     import matplotlib.pyplot as plt
     epochs = _get_epochs()
     epochs.plot_psd()
diff --git a/mne/viz/tests/test_evoked.py b/mne/viz/tests/test_evoked.py
index 9ce246d..9f8acbb 100644
--- a/mne/viz/tests/test_evoked.py
+++ b/mne/viz/tests/test_evoked.py
@@ -13,14 +13,16 @@ import warnings
 
 import numpy as np
 from numpy.testing import assert_raises
-
+from nose.tools import assert_true
+import pytest
 
 from mne import read_events, Epochs, pick_types, read_cov
 from mne.channels import read_layout
 from mne.io import read_raw_fif
-from mne.utils import slow_test, run_tests_if_main
-from mne.viz.evoked import _butterfly_onselect, plot_compare_evokeds
+from mne.utils import run_tests_if_main, catch_logging
+from mne.viz.evoked import _line_plot_onselect, plot_compare_evokeds
 from mne.viz.utils import _fake_click
+from mne.stats import _parametric_ci
 
 # Set our plotters to test mode
 import matplotlib
@@ -39,16 +41,6 @@ n_chan = 6
 layout = read_layout('Vectorview-all')
 
 
-def _get_raw():
-    """Get raw data."""
-    return read_raw_fif(raw_fname, preload=False, add_eeg_ref=False)
-
-
-def _get_events():
-    """Get events."""
-    return read_events(event_name)
-
-
 def _get_picks(raw):
     """Get picks."""
     return pick_types(raw.info, meg=True, eeg=False, stim=False,
@@ -57,33 +49,31 @@ def _get_picks(raw):
 
 def _get_epochs():
     """Get epochs."""
-    raw = _get_raw()
+    raw = read_raw_fif(raw_fname)
     raw.add_proj([], remove_existing=True)
-    events = _get_events()
+    events = read_events(event_name)
     picks = _get_picks(raw)
     # Use a subset of channels for plotting speed
     picks = picks[np.round(np.linspace(0, len(picks) - 1, n_chan)).astype(int)]
-    picks[0] = 2  # make sure we have a magnetometer
-    epochs = Epochs(raw, events[:5], event_id, tmin, tmax, picks=picks,
-                    baseline=(None, 0), add_eeg_ref=False)
+    # make sure we have a magnetometer and a pair of grad pairs for topomap.
+    picks = np.concatenate([[2, 3, 4, 6, 7], picks])
+    epochs = Epochs(raw, events[:5], event_id, tmin, tmax, picks=picks)
     epochs.info['bads'] = [epochs.ch_names[-1]]
     return epochs
 
 
 def _get_epochs_delayed_ssp():
     """Get epochs with delayed SSP."""
-    raw = _get_raw()
-    events = _get_events()
+    raw = read_raw_fif(raw_fname)
+    events = read_events(event_name)
     picks = _get_picks(raw)
     reject = dict(mag=4e-12)
     epochs_delayed_ssp = Epochs(raw, events[:10], event_id, tmin, tmax,
-                                picks=picks, baseline=(None, 0),
-                                proj='delayed', reject=reject,
-                                add_eeg_ref=False)
+                                picks=picks, proj='delayed', reject=reject)
     return epochs_delayed_ssp
 
 
- at slow_test
+ at pytest.mark.slowtest
 def test_plot_evoked():
     """Test plotting of evoked."""
     import matplotlib.pyplot as plt
@@ -99,8 +89,6 @@ def test_plot_evoked():
                     [ax.get_xlim()[0], ax.get_ylim()[1]], 'data')
         # plot with bad channels excluded & spatial_colors & zorder
         evoked.plot(exclude='bads')
-        evoked.plot(exclude=evoked.info['bads'], spatial_colors=True, gfp=True,
-                    zorder='std')
 
         # test selective updating of dict keys is working.
         evoked.plot(hline=[1], units=dict(mag='femto foo'))
@@ -127,16 +115,28 @@ def test_plot_evoked():
         plt.close('all')
 
         evoked.plot_topo()  # should auto-find layout
-        _butterfly_onselect(0, 200, ['mag', 'grad'], evoked)
+        _line_plot_onselect(0, 200, ['mag', 'grad'], evoked.info, evoked.data,
+                            evoked.times)
         plt.close('all')
 
         cov = read_cov(cov_fname)
         cov['method'] = 'empirical'
-        evoked.plot_white(cov)
+        # test rank param.
+        evoked.plot_white(cov, rank={'mag': 101, 'grad': 201})
+        evoked.plot_white(cov, rank={'mag': 101})  # test rank param.
+        evoked.plot_white(cov, rank={'grad': 201})  # test rank param.
+        assert_raises(
+            ValueError, evoked.plot_white, cov,
+            rank={'mag': 101, 'grad': 201, 'meg': 306})
+        assert_raises(
+            ValueError, evoked.plot_white, cov, rank={'meg': 306})
+
         evoked.plot_white([cov, cov])
 
         # plot_compare_evokeds: test condition contrast, CI, color assignment
         plot_compare_evokeds(evoked.copy().pick_types(meg='mag'))
+        plot_compare_evokeds(evoked.copy().pick_types(meg='grad'),
+                             picks=[1, 2])
         evoked.rename_channels({'MEG 2142': "MEG 1642"})
         assert len(plot_compare_evokeds(evoked)) == 2
         colors = dict(red='r', blue='b')
@@ -145,6 +145,7 @@ def test_plot_evoked():
         red.data *= 1.1
         blue.data *= 0.9
         plot_compare_evokeds([red, blue], picks=3)  # list of evokeds
+        plot_compare_evokeds([red, blue], picks=3, truncate_yaxis=True)
         plot_compare_evokeds([[red, evoked], [blue, evoked]],
                              picks=3)  # list of lists
         # test picking & plotting grads
@@ -155,7 +156,8 @@ def test_plot_evoked():
         plot_compare_evokeds(contrast, colors=colors, linestyles=linestyles,
                              picks=[0, 2], vlines=[.01, -.04], invert_y=True,
                              truncate_yaxis=False, ylim=dict(mag=(-10, 10)),
-                             styles={"red/stim": {"linewidth": 1}})
+                             styles={"red/stim": {"linewidth": 1}},
+                             show_sensors=True)
         assert_raises(ValueError, plot_compare_evokeds,
                       contrast, picks='str')  # bad picks: not int
         assert_raises(ValueError, plot_compare_evokeds, evoked, picks=3,
@@ -170,18 +172,37 @@ def test_plot_evoked():
                       gfp=True)  # no single-channel GFP
         assert_raises(TypeError, plot_compare_evokeds, evoked, picks=3,
                       ci='fake')  # ci must be float or None
+        assert_raises(TypeError, plot_compare_evokeds, evoked, picks=3,
+                      show_sensors='a')  # show_sensors must be int or bool
         contrast["red/stim"] = red
         contrast["blue/stim"] = blue
         plot_compare_evokeds(contrast, picks=[0], colors=['r', 'b'],
-                             ylim=dict(mag=(1, 10)))
+                             ylim=dict(mag=(1, 10)), ci=_parametric_ci,
+                             truncate_yaxis='max_ticks')
 
         # Hack to test plotting of maxfiltered data
         evoked_sss = evoked.copy()
-        evoked_sss.info['proc_history'] = [dict(max_info=None)]
-        evoked_sss.plot_white(cov)
+        sss = dict(sss_info=dict(in_order=80, components=np.arange(80)))
+        evoked_sss.info['proc_history'] = [dict(max_info=sss)]
+        evoked_sss.plot_white(cov, rank={'meg': 64})
+        assert_raises(
+            ValueError, evoked_sss.plot_white, cov, rank={'grad': 201})
         evoked_sss.plot_white(cov_fname)
+
+        # plot with bad channels excluded, spatial_colors, zorder & pos. layout
+        evoked.rename_channels({'MEG 0133': 'MEG 0000'})
+        evoked.plot(exclude=evoked.info['bads'], spatial_colors=True, gfp=True,
+                    zorder='std')
+        evoked.plot(exclude=[], spatial_colors=True, zorder='unsorted')
+        assert_raises(TypeError, evoked.plot, zorder='asdf')
         plt.close('all')
     evoked.plot_sensors()  # Test plot_sensors
     plt.close('all')
 
+    evoked.pick_channels(evoked.ch_names[:4])
+    with catch_logging() as log_file:
+        evoked.plot(verbose=True)
+    assert_true('Need more than one' in log_file.getvalue())
+
+
 run_tests_if_main()
diff --git a/mne/viz/tests/test_ica.py b/mne/viz/tests/test_ica.py
index 181ff1b..1d0b7f4 100644
--- a/mne/viz/tests/test_ica.py
+++ b/mne/viz/tests/test_ica.py
@@ -32,7 +32,7 @@ event_id, tmin, tmax = 1, -0.1, 0.2
 
 def _get_raw(preload=False):
     """Get raw data."""
-    return read_raw_fif(raw_fname, preload=preload, add_eeg_ref=False)
+    return read_raw_fif(raw_fname, preload=preload)
 
 
 def _get_events():
@@ -51,8 +51,7 @@ def _get_epochs():
     events = _get_events()
     picks = _get_picks(raw)
     with warnings.catch_warnings(record=True):  # bad proj
-        epochs = Epochs(raw, events[:10], event_id, tmin, tmax, picks=picks,
-                        baseline=(None, 0), add_eeg_ref=False)
+        epochs = Epochs(raw, events[:10], event_id, tmin, tmax, picks=picks)
     return epochs
 
 
@@ -60,6 +59,8 @@ def _get_epochs():
 def test_plot_ica_components():
     """Test plotting of ICA solutions."""
     import matplotlib.pyplot as plt
+    res = 8
+    fast_test = {"res": res, "contours": 0, "sensors": False}
     raw = _get_raw()
     ica = ICA(noise_cov=read_cov(cov_fname), n_components=2,
               max_pca_components=3, n_pca_components=3)
@@ -69,12 +70,12 @@ def test_plot_ica_components():
     warnings.simplefilter('always', UserWarning)
     with warnings.catch_warnings(record=True):
         for components in [0, [0], [0, 1], [0, 1] * 2, None]:
-            ica.plot_components(components, image_interp='bilinear', res=16,
-                                colorbar=True)
+            ica.plot_components(components, image_interp='bilinear',
+                                colorbar=True, **fast_test)
 
         # test interactive mode (passing 'inst' arg)
         plt.close('all')
-        ica.plot_components([0, 1], image_interp='bilinear', res=16, inst=raw)
+        ica.plot_components([0, 1], image_interp='bilinear', inst=raw, res=16)
 
         fig = plt.gcf()
         ax = [a for a in fig.get_children() if isinstance(a, plt.Axes)]
@@ -103,6 +104,7 @@ def test_plot_ica_properties():
     """Test plotting of ICA properties."""
     import matplotlib.pyplot as plt
 
+    res = 8
     raw = _get_raw(preload=True)
     raw.add_proj([], remove_existing=True)
     events = _get_events()
@@ -123,7 +125,7 @@ def test_plot_ica_properties():
     fig, ax = _create_properties_layout()
     assert_equal(len(ax), 5)
 
-    topoargs = dict(topomap_args={'res': 10})
+    topoargs = dict(topomap_args={'res': res, 'contours': 0, "sensors": False})
     ica.plot_properties(raw, picks=0, **topoargs)
     ica.plot_properties(epochs, picks=1, dB=False, plot_std=1.5, **topoargs)
     ica.plot_properties(epochs, picks=1, image_args={'sigma': 1.5},
@@ -142,7 +144,7 @@ def test_plot_ica_properties():
 
     fig, ax = plt.subplots(2, 3)
     ax = ax.ravel()[:-1]
-    ica.plot_properties(epochs, picks=1, axes=ax)
+    ica.plot_properties(epochs, picks=1, axes=ax, **topoargs)
     fig = ica.plot_properties(raw, picks=[0, 1], **topoargs)
     assert_equal(len(fig), 2)
     assert_raises(ValueError, plot_ica_properties, epochs, ica, picks=[0, 1],
@@ -150,13 +152,20 @@ def test_plot_ica_properties():
     assert_raises(ValueError, ica.plot_properties, epochs, axes='not axes')
     plt.close('all')
 
+    # Test merging grads.
+    raw = _get_raw(preload=True)
+    picks = pick_types(raw.info, meg='grad')[:10]
+    ica = ICA(n_components=2)
+    ica.fit(raw, picks=picks)
+    ica.plot_properties(raw)
+    plt.close('all')
+
 
 @requires_sklearn
 def test_plot_ica_sources():
     """Test plotting of ICA panel."""
     import matplotlib.pyplot as plt
-    raw = read_raw_fif(raw_fname, preload=False, add_eeg_ref=False)
-    raw.crop(0, 1, copy=False).load_data()
+    raw = read_raw_fif(raw_fname).crop(0, 1).load_data()
     picks = _get_picks(raw)
     epochs = _get_epochs()
     raw.pick_channels([raw.ch_names[k] for k in picks])
@@ -259,19 +268,9 @@ def test_plot_instance_components():
     with warnings.catch_warnings(record=True):  # bad proj
         ica.fit(raw, picks=picks)
     fig = ica.plot_sources(raw, exclude=[0], title='Components')
-    fig.canvas.key_press_event('down')
-    fig.canvas.key_press_event('up')
-    fig.canvas.key_press_event('right')
-    fig.canvas.key_press_event('left')
-    fig.canvas.key_press_event('o')
-    fig.canvas.key_press_event('-')
-    fig.canvas.key_press_event('+')
-    fig.canvas.key_press_event('=')
-    fig.canvas.key_press_event('pageup')
-    fig.canvas.key_press_event('pagedown')
-    fig.canvas.key_press_event('home')
-    fig.canvas.key_press_event('end')
-    fig.canvas.key_press_event('f11')
+    for key in ['down', 'up', 'right', 'left', 'o', '-', '+', '=', 'pageup',
+                'pagedown', 'home', 'end', 'f11', 'b']:
+        fig.canvas.key_press_event(key)
     ax = fig.get_axes()[0]
     line = ax.lines[0]
     _fake_click(fig, ax, [line.get_xdata()[0], line.get_ydata()[0]], 'data')
@@ -280,19 +279,9 @@ def test_plot_instance_components():
     plt.close('all')
     epochs = _get_epochs()
     fig = ica.plot_sources(epochs, exclude=[0], title='Components')
-    fig.canvas.key_press_event('down')
-    fig.canvas.key_press_event('up')
-    fig.canvas.key_press_event('right')
-    fig.canvas.key_press_event('left')
-    fig.canvas.key_press_event('o')
-    fig.canvas.key_press_event('-')
-    fig.canvas.key_press_event('+')
-    fig.canvas.key_press_event('=')
-    fig.canvas.key_press_event('pageup')
-    fig.canvas.key_press_event('pagedown')
-    fig.canvas.key_press_event('home')
-    fig.canvas.key_press_event('end')
-    fig.canvas.key_press_event('f11')
+    for key in ['down', 'up', 'right', 'left', 'o', '-', '+', '=', 'pageup',
+                'pagedown', 'home', 'end', 'f11', 'b']:
+        fig.canvas.key_press_event(key)
     # Test a click
     ax = fig.get_axes()[0]
     line = ax.lines[0]
diff --git a/mne/viz/tests/test_misc.py b/mne/viz/tests/test_misc.py
index 6834924..b2e2796 100644
--- a/mne/viz/tests/test_misc.py
+++ b/mne/viz/tests/test_misc.py
@@ -12,15 +12,17 @@ import warnings
 
 import numpy as np
 from numpy.testing import assert_raises
+import pytest
 
 from mne import (read_events, read_cov, read_source_spaces, read_evokeds,
                  read_dipole, SourceEstimate)
 from mne.datasets import testing
+from mne.filter import create_filter
 from mne.io import read_raw_fif
 from mne.minimum_norm import read_inverse_operator
 from mne.viz import (plot_bem, plot_events, plot_source_spectrogram,
-                     plot_snr_estimate)
-from mne.utils import requires_nibabel, run_tests_if_main, slow_test
+                     plot_snr_estimate, plot_filter)
+from mne.utils import requires_nibabel, run_tests_if_main, requires_version
 
 # Set our plotters to test mode
 import matplotlib
@@ -44,7 +46,7 @@ event_fname = op.join(base_dir, 'test-eve.fif')
 
 def _get_raw():
     """Get raw data."""
-    return read_raw_fif(raw_fname, preload=True, add_eeg_ref=False)
+    return read_raw_fif(raw_fname, preload=True)
 
 
 def _get_events():
@@ -52,6 +54,32 @@ def _get_events():
     return read_events(event_fname)
 
 
+ at requires_version('scipy', '0.16')
+def test_plot_filter():
+    """Test filter plotting."""
+    import matplotlib.pyplot as plt
+    l_freq, h_freq, sfreq = 2., 40., 1000.
+    data = np.zeros(5000)
+    freq = [0, 2, 40, 50, 500]
+    gain = [0, 1, 1, 0, 0]
+    h = create_filter(data, sfreq, l_freq, h_freq, fir_design='firwin2')
+    plot_filter(h, sfreq)
+    plt.close('all')
+    plot_filter(h, sfreq, freq, gain)
+    plt.close('all')
+    iir = create_filter(data, sfreq, l_freq, h_freq, method='iir')
+    plot_filter(iir, sfreq)
+    plt.close('all')
+    plot_filter(iir, sfreq,  freq, gain)
+    plt.close('all')
+    iir_ba = create_filter(data, sfreq, l_freq, h_freq, method='iir',
+                           iir_params=dict(output='ba'))
+    plot_filter(iir_ba, sfreq,  freq, gain)
+    plt.close('all')
+    plot_filter(h, sfreq, freq, gain, fscale='linear')
+    plt.close('all')
+
+
 def test_plot_cov():
     """Test plotting of covariances."""
     raw = _get_raw()
@@ -121,7 +149,7 @@ def test_plot_source_spectrogram():
                   [[1, 2], [3, 4]], tmax=7)
 
 
- at slow_test
+ at pytest.mark.slowtest
 @testing.requires_testing_data
 def test_plot_snr():
     """Test plotting SNR estimate."""
@@ -136,4 +164,5 @@ def test_plot_dipole_amplitudes():
     dipoles = read_dipole(dip_fname)
     dipoles.plot_amplitudes(show=False)
 
+
 run_tests_if_main()
diff --git a/mne/viz/tests/test_montage.py b/mne/viz/tests/test_montage.py
index 6ea5b44..49e8b27 100644
--- a/mne/viz/tests/test_montage.py
+++ b/mne/viz/tests/test_montage.py
@@ -5,11 +5,13 @@
 # License: Simplified BSD
 
 # Set our plotters to test mode
-import matplotlib
 import os.path as op
-matplotlib.use('Agg')  # for testing don't use X server
 
-from mne.channels import read_montage, read_dig_montage  # noqa
+import matplotlib
+
+from mne.channels import read_montage, read_dig_montage
+
+matplotlib.use('Agg')  # for testing don't use X server
 
 
 p_dir = op.join(op.dirname(__file__), '..', '..', 'io', 'kit', 'tests', 'data')
@@ -20,11 +22,39 @@ point_names = ['nasion', 'lpa', 'rpa', '1', '2', '3', '4', '5']
 
 
 def test_plot_montage():
-    """Test plotting montages
+    """Test plotting montages.
     """
+    import matplotlib.pyplot as plt
     m = read_montage('easycap-M1')
     m.plot()
-    m.plot(show_names=True)
+    plt.close('all')
+    m.plot(kind='3d')
+    plt.close('all')
+    m.plot(kind='3d', show_names=True)
+    plt.close('all')
+    m.plot(kind='topomap')
+    plt.close('all')
+    m.plot(kind='topomap', show_names=True)
+    plt.close('all')
     d = read_dig_montage(hsp, hpi, elp, point_names)
     d.plot()
-    d.plot(show_names=True)
+    plt.close('all')
+    d.plot(kind='3d')
+    plt.close('all')
+    d.plot(kind='3d', show_names=True)
+    plt.close('all')
+
+
+def test_plot_defect_montage():
+    """Test plotting defect montages (i.e. with duplicate labels).
+    """
+    # montage name and number of unique labels
+    montages = [('standard_1005', 342), ('standard_postfixed', 85),
+                ('standard_primed', 85), ('standard_1020', 93)]
+    for name, n in montages:
+        m = read_montage(name)
+        fig = m.plot()
+        collection = fig.axes[0].collections[0]
+        assert collection._edgecolors.shape[0] == n
+        assert collection._facecolors.shape[0] == n
+        assert collection._offsets.shape[0] == n
diff --git a/mne/viz/tests/test_raw.py b/mne/viz/tests/test_raw.py
index 7c4641e..8374dbb 100644
--- a/mne/viz/tests/test_raw.py
+++ b/mne/viz/tests/test_raw.py
@@ -5,13 +5,15 @@
 import numpy as np
 import os.path as op
 import warnings
+import itertools
 
 from numpy.testing import assert_raises, assert_equal
 
 from mne import read_events, pick_types, Annotations
-from mne.io import read_raw_fif
+from mne.datasets import testing
+from mne.io import read_raw_fif, read_raw_ctf
 from mne.utils import requires_version, run_tests_if_main
-from mne.viz.utils import _fake_click
+from mne.viz.utils import _fake_click, _annotation_radio_clicked
 from mne.viz import plot_raw, plot_sensors
 
 # Set our plotters to test mode
@@ -20,6 +22,9 @@ matplotlib.use('Agg')  # for testing don't use X server
 
 warnings.simplefilter('always')  # enable b/c these tests throw warnings
 
+ctf_dir = op.join(testing.data_path(download=False), 'CTF')
+ctf_fname_continuous = op.join(ctf_dir, 'testdata_ctf.ds')
+
 base_dir = op.join(op.dirname(__file__), '..', '..', 'io', 'tests', 'data')
 raw_fname = op.join(base_dir, 'test_raw.fif')
 event_name = op.join(base_dir, 'test-eve.fif')
@@ -27,7 +32,7 @@ event_name = op.join(base_dir, 'test-eve.fif')
 
 def _get_raw():
     """Get raw data."""
-    raw = read_raw_fif(raw_fname, preload=True, add_eeg_ref=False)
+    raw = read_raw_fif(raw_fname, preload=True)
     # Throws a warning about a changed unit.
     with warnings.catch_warnings(record=True):
         raw.set_channel_types({raw.ch_names[0]: 'ias'})
@@ -41,19 +46,77 @@ def _get_events():
     return read_events(event_name)
 
 
- at requires_version('matplotlib', '1.2')
+def _annotation_helper(raw):
+    """Helper for testing interactive annotations."""
+    import matplotlib.pyplot as plt
+    n_anns = 0 if raw.annotations is None else len(raw.annotations.onset)
+
+    fig = raw.plot()
+    data_ax = fig.axes[0]
+    fig.canvas.key_press_event('a')  # annotation mode
+    # modify description
+    ann_fig = plt.gcf()
+    for key in ' test':
+        ann_fig.canvas.key_press_event(key)
+    ann_fig.canvas.key_press_event('enter')
+
+    ann_fig = plt.gcf()
+    # XXX: _fake_click raises an error on Agg backend
+    _annotation_radio_clicked('', ann_fig.radio, data_ax.selector)
+
+    # draw annotation
+    _fake_click(fig, data_ax, [1., 1.], xform='data', button=1, kind='press')
+    _fake_click(fig, data_ax, [5., 1.], xform='data', button=1, kind='motion')
+    _fake_click(fig, data_ax, [5., 1.], xform='data', button=1, kind='release')
+    # hover event
+    _fake_click(fig, data_ax, [4.5, 1.], xform='data', button=None,
+                kind='motion')
+    _fake_click(fig, data_ax, [4.7, 1.], xform='data', button=None,
+                kind='motion')
+    # modify annotation from end
+    _fake_click(fig, data_ax, [5., 1.], xform='data', button=1, kind='press')
+    _fake_click(fig, data_ax, [2.5, 1.], xform='data', button=1, kind='motion')
+    _fake_click(fig, data_ax, [2.5, 1.], xform='data', button=1,
+                kind='release')
+    # modify annotation from beginning
+    _fake_click(fig, data_ax, [1., 1.], xform='data', button=1, kind='press')
+    _fake_click(fig, data_ax, [1.1, 1.], xform='data', button=1, kind='motion')
+    _fake_click(fig, data_ax, [1.1, 1.], xform='data', button=1,
+                kind='release')
+    assert_equal(len(raw.annotations.onset), n_anns + 1)
+    assert_equal(len(raw.annotations.duration), n_anns + 1)
+    assert_equal(len(raw.annotations.description), n_anns + 1)
+    assert_equal(raw.annotations.description[n_anns], 'BAD test')
+
+    # draw another annotation merging the two
+    _fake_click(fig, data_ax, [5.5, 1.], xform='data', button=1, kind='press')
+    _fake_click(fig, data_ax, [2., 1.], xform='data', button=1, kind='motion')
+    _fake_click(fig, data_ax, [2., 1.], xform='data', button=1, kind='release')
+    # delete the annotation
+    _fake_click(fig, data_ax, [1.5, 1.], xform='data', button=3, kind='press')
+    fig.canvas.key_press_event('a')  # exit annotation mode
+
+    assert_equal(len(raw.annotations.onset), n_anns)
+    assert_equal(len(raw.annotations.duration), n_anns)
+    assert_equal(len(raw.annotations.description), n_anns)
+    plt.close('all')
+
+
 def test_plot_raw():
     """Test plotting of raw data."""
     import matplotlib.pyplot as plt
     raw = _get_raw()
+    raw.info['lowpass'] = 10.  # allow heavy decim during plotting
     events = _get_events()
     plt.close('all')  # ensure all are closed
     with warnings.catch_warnings(record=True):
-        fig = raw.plot(events=events, show_options=True)
+        fig = raw.plot(events=events, show_options=True, order=[1, 7, 3],
+                       group_by='original')
         # test mouse clicks
         x = fig.get_axes()[0].lines[1].get_xdata().mean()
         y = fig.get_axes()[0].lines[1].get_ydata().mean()
-        data_ax = fig.get_axes()[0]
+        data_ax = fig.axes[0]
+
         _fake_click(fig, data_ax, [x, y], xform='data')  # mark a bad channel
         _fake_click(fig, data_ax, [x, y], xform='data')  # unmark a bad channel
         _fake_click(fig, data_ax, [0.5, 0.999])  # click elsewhere in 1st axes
@@ -75,22 +138,14 @@ def test_plot_raw():
         # _fake_click(ssp_fig, ssp_fig.get_axes()[0], pos, xform='data')  # off
         # _fake_click(ssp_fig, ssp_fig.get_axes()[0], pos, xform='data')  # on
         #  test keypresses
-        fig.canvas.key_press_event('escape')
-        fig.canvas.key_press_event('down')
-        fig.canvas.key_press_event('up')
-        fig.canvas.key_press_event('right')
-        fig.canvas.key_press_event('left')
-        fig.canvas.key_press_event('o')
-        fig.canvas.key_press_event('-')
-        fig.canvas.key_press_event('+')
-        fig.canvas.key_press_event('=')
-        fig.canvas.key_press_event('pageup')
-        fig.canvas.key_press_event('pagedown')
-        fig.canvas.key_press_event('home')
-        fig.canvas.key_press_event('end')
-        fig.canvas.key_press_event('?')
-        fig.canvas.key_press_event('f11')
-        fig.canvas.key_press_event('escape')
+        for key in ['down', 'up', 'right', 'left', 'o', '-', '+', '=',
+                    'pageup', 'pagedown', 'home', 'end', '?', 'f11', 'escape']:
+            fig.canvas.key_press_event(key)
+        fig = plot_raw(raw, events=events, group_by='selection')
+        for key in ['b', 'down', 'up', 'right', 'left', 'o', '-', '+', '=',
+                    'pageup', 'pagedown', 'home', 'end', '?', 'f11', 'b',
+                    'escape']:
+            fig.canvas.key_press_event(key)
         # Color setting
         assert_raises(KeyError, raw.plot, event_color={0: 'r'})
         assert_raises(TypeError, raw.plot, event_color={'foo': 'r'})
@@ -99,30 +154,30 @@ def test_plot_raw():
         raw.annotations = annot
         fig = plot_raw(raw, events=events, event_color={-1: 'r', 998: 'b'})
         plt.close('all')
-        for order in ['position', 'selection', range(len(raw.ch_names))[::-4],
-                      [1, 2, 4, 6]]:
-            fig = raw.plot(order=order)
+        for group_by, order in zip(['position', 'selection'],
+                                   [np.arange(len(raw.ch_names))[::-3],
+                                    [1, 2, 4, 6]]):
+            fig = raw.plot(group_by=group_by, order=order)
             x = fig.get_axes()[0].lines[1].get_xdata()[10]
             y = fig.get_axes()[0].lines[1].get_ydata()[10]
             _fake_click(fig, data_ax, [x, y], xform='data')  # mark bad
             fig.canvas.key_press_event('down')  # change selection
             _fake_click(fig, fig.get_axes()[2], [0.5, 0.5])  # change channels
-            if order in ('position', 'selection'):
-                sel_fig = plt.figure(1)
-                topo_ax = sel_fig.axes[1]
-                _fake_click(sel_fig, topo_ax, [-0.425, 0.20223853],
-                            xform='data')
-                fig.canvas.key_press_event('down')
-                fig.canvas.key_press_event('up')
-                fig.canvas.scroll_event(0.5, 0.5, -1)  # scroll down
-                fig.canvas.scroll_event(0.5, 0.5, 1)  # scroll up
-                _fake_click(sel_fig, topo_ax, [-0.5, 0.], xform='data')
-                _fake_click(sel_fig, topo_ax, [0.5, 0.], xform='data',
-                            kind='motion')
-                _fake_click(sel_fig, topo_ax, [0.5, 0.5], xform='data',
-                            kind='motion')
-                _fake_click(sel_fig, topo_ax, [-0.5, 0.5], xform='data',
-                            kind='release')
+            sel_fig = plt.figure(1)
+            topo_ax = sel_fig.axes[1]
+            _fake_click(sel_fig, topo_ax, [-0.425, 0.20223853],
+                        xform='data')
+            fig.canvas.key_press_event('down')
+            fig.canvas.key_press_event('up')
+            fig.canvas.scroll_event(0.5, 0.5, -1)  # scroll down
+            fig.canvas.scroll_event(0.5, 0.5, 1)  # scroll up
+            _fake_click(sel_fig, topo_ax, [-0.5, 0.], xform='data')
+            _fake_click(sel_fig, topo_ax, [0.5, 0.], xform='data',
+                        kind='motion')
+            _fake_click(sel_fig, topo_ax, [0.5, 0.5], xform='data',
+                        kind='motion')
+            _fake_click(sel_fig, topo_ax, [-0.5, 0.5], xform='data',
+                        kind='release')
 
             plt.close('all')
         # test if meas_date has only one element
@@ -130,10 +185,39 @@ def test_plot_raw():
                                          dtype=np.int32)
         raw.annotations = Annotations([1 + raw.first_samp / raw.info['sfreq']],
                                       [5], ['bad'])
-        raw.plot()
+        raw.plot(group_by='position', order=np.arange(8))
+        for fig_num in plt.get_fignums():
+            fig = plt.figure(fig_num)
+            if hasattr(fig, 'radio'):  # Get access to selection fig.
+                break
+        for key in ['down', 'up', 'escape']:
+            fig.canvas.key_press_event(key)
         plt.close('all')
 
 
+ at testing.requires_testing_data
+def test_plot_ref_meg():
+    """Test plotting ref_meg."""
+    import matplotlib.pyplot as plt
+    raw_ctf = read_raw_ctf(ctf_fname_continuous).crop(0, 1).load_data()
+    raw_ctf.plot()
+    plt.close('all')
+    assert_raises(ValueError, raw_ctf.plot, group_by='selection')
+
+
+def test_plot_annotations():
+    """Test annotation mode of the plotter."""
+    raw = _get_raw()
+    raw.info['lowpass'] = 10.
+    with warnings.catch_warnings(record=True):  # matplotlib
+        _annotation_helper(raw)
+
+    with warnings.catch_warnings(record=True):  # cut off
+        raw.annotations = Annotations([42], [1], 'test', raw.info['meas_date'])
+    with warnings.catch_warnings(record=True):  # matplotlib
+        _annotation_helper(raw)
+
+
 @requires_version('scipy', '0.10')
 def test_plot_raw_filtered():
     """Test filtering of raw plots."""
@@ -145,7 +229,7 @@ def test_plot_raw_filtered():
     assert_raises(ValueError, raw.plot, clipping='foo')
     raw.plot(lowpass=1, clipping='transparent')
     raw.plot(highpass=1, clipping='clamp')
-    raw.plot(highpass=1, lowpass=2)
+    raw.plot(highpass=1, lowpass=2, butterfly=True)
 
 
 @requires_version('scipy', '0.12')
@@ -154,39 +238,56 @@ def test_plot_raw_psd():
     import matplotlib.pyplot as plt
     raw = _get_raw()
     # normal mode
-    raw.plot_psd(tmax=2.0)
+    raw.plot_psd(average=False)
     # specific mode
     picks = pick_types(raw.info, meg='mag', eeg=False)[:4]
-    raw.plot_psd(picks=picks, area_mode='range')
+    raw.plot_psd(tmax=np.inf, picks=picks, area_mode='range', average=False,
+                 spatial_colors=True)
+    raw.plot_psd(tmax=20., color='yellow', dB=False, line_alpha=0.4,
+                 n_overlap=0.1, average=False)
+    plt.close('all')
     ax = plt.axes()
     # if ax is supplied:
-    assert_raises(ValueError, raw.plot_psd, ax=ax)
-    raw.plot_psd(picks=picks, ax=ax)
+    assert_raises(ValueError, raw.plot_psd, ax=ax, average=True)
+    assert_raises(ValueError, raw.plot_psd, average=True, spatial_colors=True)
+    raw.plot_psd(tmax=np.inf, picks=picks, ax=ax, average=True)
     plt.close('all')
     ax = plt.axes()
-    assert_raises(ValueError, raw.plot_psd, ax=ax)
-    ax = [ax, plt.axes()]
-    raw.plot_psd(ax=ax)
+    assert_raises(ValueError, raw.plot_psd, ax=ax, average=True)
+    plt.close('all')
+    ax = plt.subplots(2)[1]
+    raw.plot_psd(tmax=np.inf, ax=ax, average=True)
     plt.close('all')
     # topo psd
-    raw.plot_psd_topo()
+    ax = plt.subplot()
+    raw.plot_psd_topo(axes=ax)
     plt.close('all')
+    # with channel information not available
+    for idx in range(len(raw.info['chs'])):
+        raw.info['chs'][idx]['loc'] = np.zeros(12)
+    with warnings.catch_warnings(record=True):  # missing channel locations
+        raw.plot_psd(spatial_colors=True, average=False)
     # with a flat channel
     raw[5, :] = 0
-    assert_raises(ValueError, raw.plot_psd)
+    with warnings.catch_warnings(record=True) as w:
+        for dB, estimate in itertools.product((True, False),
+                                              ('power', 'amplitude')):
+            raw.plot_psd(average=True, dB=dB, estimate=estimate)
+    assert_equal(len(w), 4)
 
 
- at requires_version('matplotlib', '1.2')
 def test_plot_sensors():
     """Test plotting of sensor array."""
     import matplotlib.pyplot as plt
     raw = _get_raw()
     fig = raw.plot_sensors('3d')
     _fake_click(fig, fig.gca(), (-0.08, 0.67))
-    raw.plot_sensors('topomap', ch_type='mag')
+    raw.plot_sensors('topomap', ch_type='mag',
+                     show_names=['MEG 0111', 'MEG 0131'])
+    plt.close('all')
     ax = plt.subplot(111)
     raw.plot_sensors(ch_groups='position', axes=ax)
-    raw.plot_sensors(ch_groups='selection')
+    raw.plot_sensors(ch_groups='selection', to_sphere=False)
     raw.plot_sensors(ch_groups=[[0, 1, 2], [3, 4]])
     assert_raises(ValueError, raw.plot_sensors, ch_groups='asd')
     assert_raises(TypeError, plot_sensors, raw)  # needs to be info
@@ -215,4 +316,5 @@ def test_plot_sensors():
     assert_equal(len(fig.lasso.selection), 1)
     plt.close('all')
 
+
 run_tests_if_main()
diff --git a/mne/viz/tests/test_topo.py b/mne/viz/tests/test_topo.py
index 8c60a38..430f678 100644
--- a/mne/viz/tests/test_topo.py
+++ b/mne/viz/tests/test_topo.py
@@ -10,7 +10,8 @@ import warnings
 from collections import namedtuple
 
 import numpy as np
-from numpy.testing import assert_raises
+from numpy.testing import assert_raises, assert_equal
+from nose.tools import assert_true
 
 from mne import read_events, Epochs, pick_channels_evoked
 from mne.channels import read_layout
@@ -21,7 +22,8 @@ from mne.utils import run_tests_if_main
 from mne.viz import (plot_topo_image_epochs, _get_presser,
                      mne_analyze_colormap, plot_evoked_topo)
 from mne.viz.utils import _fake_click
-from mne.viz.topo import _plot_update_evoked_topo_proj, iter_topography
+from mne.viz.topo import (_plot_update_evoked_topo_proj, iter_topography,
+                          _imshow_tfr)
 
 # Set our plotters to test mode
 import matplotlib
@@ -38,11 +40,6 @@ event_id, tmin, tmax = 1, -0.2, 0.2
 layout = read_layout('Vectorview-all')
 
 
-def _get_raw():
-    """Get raw data."""
-    return read_raw_fif(raw_fname, preload=False, add_eeg_ref=False)
-
-
 def _get_events():
     """Get events."""
     return read_events(event_name)
@@ -55,25 +52,24 @@ def _get_picks(raw):
 
 def _get_epochs():
     """Get epochs."""
-    raw = _get_raw()
+    raw = read_raw_fif(raw_fname)
     raw.add_proj([], remove_existing=True)
     events = _get_events()
     picks = _get_picks(raw)
     # bad proj warning
-    epochs = Epochs(raw, events[:10], event_id, tmin, tmax, picks=picks,
-                    baseline=(None, 0), add_eeg_ref=False)
+    epochs = Epochs(raw, events[:10], event_id, tmin, tmax, picks=picks)
     return epochs
 
 
 def _get_epochs_delayed_ssp():
     """Get epochs with delayed SSP."""
-    raw = _get_raw()
+    raw = read_raw_fif(raw_fname)
     events = _get_events()
     picks = _get_picks(raw)
     reject = dict(mag=4e-12)
     epochs_delayed_ssp = Epochs(
         raw, events[:10], event_id, tmin, tmax, picks=picks,
-        baseline=(None, 0), proj='delayed', reject=reject, add_eeg_ref=False)
+        proj='delayed', reject=reject)
     return epochs_delayed_ssp
 
 
@@ -83,15 +79,15 @@ def test_plot_topo():
     # Show topography
     evoked = _get_epochs().average()
     # should auto-find layout
-    plot_evoked_topo([evoked, evoked], merge_grads=True)
+    plot_evoked_topo([evoked, evoked], merge_grads=True, background_color='w')
     # Test jointplot
     evoked.plot_joint()
 
     def return_inds(d):  # to test function kwarg to zorder arg of evoked.plot
         return list(range(d.shape[0]))
-    ts_args = dict(spatial_colors=True, zorder=return_inds)
-    evoked.plot_joint(title='test', ts_args=ts_args,
-                      topomap_args=dict(colorbar=True, times=[0.]))
+    evoked.plot_joint(title='test', topomap_args=dict(contours=0, res=8),
+                      ts_args=dict(spatial_colors=True, zorder=return_inds))
+    assert_raises(ValueError, evoked.plot_joint, ts_args=dict(axes=True))
 
     warnings.simplefilter('always', UserWarning)
     picked_evoked = evoked.copy().pick_channels(evoked.ch_names[:3])
@@ -125,36 +121,104 @@ def test_plot_topo():
         _plot_update_evoked_topo_proj(params, bools)
     # should auto-generate layout
     plot_evoked_topo(picked_evoked_eeg.copy(),
-                     fig_background=np.zeros((4, 3, 3)), proj=True)
-    picked_evoked.plot_topo(merge_grads=True)  # Test RMS plot of grad pairs
+                     fig_background=np.zeros((4, 3, 3)), proj=True,
+                     background_color='k')
+    # Test RMS plot of grad pairs
+    picked_evoked.plot_topo(merge_grads=True, background_color='w')
     plt.close('all')
     for ax, idx in iter_topography(evoked.info):
         ax.plot(evoked.data[idx], color='red')
     plt.close('all')
 
 
+def test_plot_topo_single_ch():
+    """Test single channel topoplot with time cursor"""
+    import matplotlib.pyplot as plt
+    evoked = _get_epochs().average()
+    fig = plot_evoked_topo(evoked, background_color='w')
+    num_figures_before = len(plt.get_fignums())
+    _fake_click(fig, fig.axes[0], (0.08, 0.65))
+    assert_equal(num_figures_before + 1, len(plt.get_fignums()))
+    fig = plt.gcf()
+    ax = plt.gca()
+    _fake_click(fig, ax, (.5, .5), kind='motion')  # cursor should appear
+    assert_true(isinstance(ax._cursorline, matplotlib.lines.Line2D))
+    _fake_click(fig, ax, (1.5, 1.5), kind='motion')  # cursor should disappear
+    assert_equal(ax._cursorline, None)
+    plt.close('all')
+
+
 def test_plot_topo_image_epochs():
     """Test plotting of epochs image topography."""
     import matplotlib.pyplot as plt
     title = 'ERF images - MNE sample data'
     epochs = _get_epochs()
+    epochs.load_data()
     cmap = mne_analyze_colormap(format='matplotlib')
+    data_min = epochs._data.min()
+    plt.close('all')
     fig = plot_topo_image_epochs(epochs, sigma=0.5, vmin=-200, vmax=200,
                                  colorbar=True, title=title, cmap=cmap)
-    _fake_click(fig, fig.axes[2], (0.08, 0.64))
+    assert_equal(epochs._data.min(), data_min)
+    num_figures_before = len(plt.get_fignums())
+    _fake_click(fig, fig.axes[0], (0.08, 0.64))
+    assert_equal(num_figures_before + 1, len(plt.get_fignums()))
     plt.close('all')
 
 
 def test_plot_tfr_topo():
     """Test plotting of TFR data."""
+    import matplotlib.pyplot as plt
+
     epochs = _get_epochs()
     n_freqs = 3
     nave = 1
     data = np.random.RandomState(0).randn(len(epochs.ch_names),
                                           n_freqs, len(epochs.times))
     tfr = AverageTFR(epochs.info, data, epochs.times, np.arange(n_freqs), nave)
-    tfr.plot_topo(baseline=(None, 0), mode='ratio', title='Average power',
-                  vmin=0., vmax=14., show=False)
+    plt.close('all')
+    fig = tfr.plot_topo(baseline=(None, 0), mode='ratio',
+                        title='Average power', vmin=0., vmax=14.)
+
+    # test opening tfr by clicking
+    num_figures_before = len(plt.get_fignums())
+    # could use np.reshape(fig.axes[-1].images[0].get_extent(), (2, 2)).mean(1)
+    _fake_click(fig, fig.axes[0], (0.08, 0.65))
+    assert_equal(num_figures_before + 1, len(plt.get_fignums()))
+    plt.close('all')
+
     tfr.plot([4], baseline=(None, 0), mode='ratio', show=False, title='foo')
+    assert_raises(ValueError, tfr.plot, [4], yscale='lin', show=False)
+
+    # nonuniform freqs
+    freqs = np.logspace(*np.log10([3, 10]), num=3)
+    tfr = AverageTFR(epochs.info, data, epochs.times, freqs, nave)
+    fig = tfr.plot([4], baseline=(None, 0), mode='mean', vmax=14., show=False)
+    assert_equal(fig.axes[0].get_yaxis().get_scale(), 'log')
+
+    # one timesample
+    tfr = AverageTFR(epochs.info, data[:, :, [0]], epochs.times[[1]],
+                     freqs, nave)
+    tfr.plot([4], baseline=None, vmax=14., show=False, yscale='linear')
+
+    # one freqency bin, log scale required: as it doesn't make sense
+    # to plot log scale for one value, we test whether yscale is set to linear
+    vmin, vmax = 0., 2.
+    fig, ax = plt.subplots()
+    tmin, tmax = epochs.times[0], epochs.times[-1]
+    _imshow_tfr(ax, 3, tmin, tmax, vmin, vmax, None, tfr=data[:, [0], :],
+                freq=freqs[[-1]], x_label=None, y_label=None,
+                colorbar=False, cmap=('RdBu_r', True), yscale='log')
+    fig = plt.gcf()
+    assert_equal(fig.axes[0].get_yaxis().get_scale(), 'linear')
+
+    # ValueError when freq[0] == 0 and yscale == 'log'
+    these_freqs = freqs[:3].copy()
+    these_freqs[0] = 0
+    assert_raises(ValueError, _imshow_tfr, ax, 3, tmin, tmax, vmin, vmax,
+                  None, tfr=data[:, :3, :], freq=these_freqs, x_label=None,
+                  y_label=None, colorbar=False, cmap=('RdBu_r', True),
+                  yscale='log')
+
 
 run_tests_if_main()
diff --git a/mne/viz/tests/test_topomap.py b/mne/viz/tests/test_topomap.py
index d7ead26..3fd704c 100644
--- a/mne/viz/tests/test_topomap.py
+++ b/mne/viz/tests/test_topomap.py
@@ -7,21 +7,23 @@
 
 import os.path as op
 import warnings
+from functools import partial
 
 import numpy as np
 from numpy.testing import assert_raises, assert_array_equal
 
 from nose.tools import assert_true, assert_equal
-
+import pytest
 
 from mne import read_evokeds, read_proj
-from mne.io import read_raw_fif
+from mne.io.proj import make_eeg_average_ref_proj
+from mne.io import read_raw_fif, read_info
 from mne.io.constants import FIFF
 from mne.io.pick import pick_info, channel_indices_by_type
 from mne.channels import read_layout, make_eeg_layout
 from mne.datasets import testing
 from mne.time_frequency.tfr import AverageTFR
-from mne.utils import slow_test, run_tests_if_main
+from mne.utils import run_tests_if_main
 
 from mne.viz import plot_evoked_topomap, plot_projs_topomap
 from mne.viz.topomap import (_check_outlines, _onselect, plot_topomap,
@@ -39,21 +41,41 @@ warnings.simplefilter('always')  # enable b/c these tests throw warnings
 data_dir = testing.data_path(download=False)
 subjects_dir = op.join(data_dir, 'subjects')
 ecg_fname = op.join(data_dir, 'MEG', 'sample', 'sample_audvis_ecg-proj.fif')
+triux_fname = op.join(data_dir, 'SSS', 'TRIUX', 'triux_bmlhus_erm_raw.fif')
 
 base_dir = op.join(op.dirname(__file__), '..', '..', 'io', 'tests', 'data')
 evoked_fname = op.join(base_dir, 'test-ave.fif')
-fname = op.join(base_dir, 'test-ave.fif')
 raw_fname = op.join(base_dir, 'test_raw.fif')
 event_name = op.join(base_dir, 'test-eve.fif')
 layout = read_layout('Vectorview-all')
 
 
-def _get_raw():
-    """Get raw data."""
-    return read_raw_fif(raw_fname, preload=False, add_eeg_ref=False)
+ at testing.requires_testing_data
+def test_plot_projs_topomap():
+    """Test plot_projs_topomap."""
+    import matplotlib.pyplot as plt
+    with warnings.catch_warnings(record=True):  # file conventions
+        warnings.simplefilter('always')
+        projs = read_proj(ecg_fname)
+    info = read_info(raw_fname)
+    fast_test = {"res": 8, "contours": 0, "sensors": False}
+    plot_projs_topomap(projs, info=info, colorbar=True, **fast_test)
+    plt.close('all')
+    ax = plt.subplot(111)
+    projs[3].plot_topomap()
+    plot_projs_topomap(projs[:1], axes=ax, **fast_test)  # test axes param
+    plt.close('all')
+    plot_projs_topomap(read_info(triux_fname)['projs'][-1:], **fast_test)
+    plt.close('all')
+    plot_projs_topomap(read_info(triux_fname)['projs'][:1], ** fast_test)
+    plt.close('all')
+    eeg_avg = make_eeg_average_ref_proj(info)
+    assert_raises(RuntimeError, eeg_avg.plot_topomap)  # no layout
+    eeg_avg.plot_topomap(info=info, **fast_test)
+    plt.close('all')
 
 
- at slow_test
+ at pytest.mark.slowtest
 @testing.requires_testing_data
 def test_plot_topomap():
     """Test topomap plotting."""
@@ -61,7 +83,8 @@ def test_plot_topomap():
     from matplotlib.patches import Circle
     # evoked
     warnings.simplefilter('always')
-    res = 16
+    res = 8
+    fast_test = {"res": res, "contours": 0, "sensors": False}
     evoked = read_evokeds(evoked_fname, 'Left Auditory',
                           baseline=(None, 0))
 
@@ -73,41 +96,53 @@ def test_plot_topomap():
 
     ev_bad = evoked.copy().pick_types(meg=False, eeg=True)
     ev_bad.pick_channels(ev_bad.ch_names[:2])
-    ev_bad.plot_topomap(times=ev_bad.times[:2] - 1e-6)  # auto, plots EEG
-    assert_raises(ValueError, ev_bad.plot_topomap, ch_type='mag')
-    assert_raises(TypeError, ev_bad.plot_topomap, head_pos='foo')
-    assert_raises(KeyError, ev_bad.plot_topomap, head_pos=dict(foo='bar'))
-    assert_raises(ValueError, ev_bad.plot_topomap, head_pos=dict(center=0))
-    assert_raises(ValueError, ev_bad.plot_topomap, times=[-100])  # bad time
-    assert_raises(ValueError, ev_bad.plot_topomap, times=[[0]])  # bad time
-    assert_raises(ValueError, ev_bad.plot_topomap, times=[[0]])  # bad time
-
-    evoked.plot_topomap(0.1, layout=layout, scale=dict(mag=0.1))
+    plt_topomap = partial(ev_bad.plot_topomap, **fast_test)
+    plt_topomap(times=ev_bad.times[:2] - 1e-6)  # auto, plots EEG
+    assert_raises(ValueError, plt_topomap, ch_type='mag')
+    assert_raises(TypeError, plt_topomap, head_pos='foo')
+    assert_raises(KeyError, plt_topomap, head_pos=dict(foo='bar'))
+    assert_raises(ValueError, plt_topomap, head_pos=dict(center=0))
+    assert_raises(ValueError, plt_topomap, times=[-100])  # bad time
+    assert_raises(ValueError, plt_topomap, times=[[0]])  # bad time
+
+    evoked.plot_topomap([0.1], ch_type='eeg', scalings=1, res=res,
+                        contours=[-100, 0, 100])
+    plt_topomap = partial(evoked.plot_topomap, **fast_test)
+    plt_topomap(0.1, layout=layout, scalings=dict(mag=0.1))
     plt.close('all')
     axes = [plt.subplot(221), plt.subplot(222)]
-    evoked.plot_topomap(axes=axes, colorbar=False)
+    plt_topomap(axes=axes, colorbar=False)
     plt.close('all')
-    evoked.plot_topomap(times=[-0.1, 0.2])
+    plt_topomap(times=[-0.1, 0.2])
     plt.close('all')
+    evoked_grad = evoked.copy().crop(0, 0).pick_types(meg='grad')
+    mask = np.zeros((204, 1), bool)
+    mask[[0, 3, 5, 6]] = True
+    names = []
+
+    def proc_names(x):
+        names.append(x)
+        return x[4:]
+
+    evoked_grad.plot_topomap(ch_type='grad', times=[0], mask=mask,
+                             show_names=proc_names, **fast_test)
+    assert_equal(sorted(names),
+                 ['MEG 011x', 'MEG 012x', 'MEG 013x', 'MEG 014x'])
     mask = np.zeros_like(evoked.data, dtype=bool)
     mask[[1, 5], :] = True
-    evoked.plot_topomap(ch_type='mag', outlines=None)
+    plt_topomap(ch_type='mag', outlines=None)
     times = [0.1]
-    evoked.plot_topomap(times, ch_type='eeg', res=res, scale=1)
-    evoked.plot_topomap(times, ch_type='grad', mask=mask, res=res)
-    evoked.plot_topomap(times, ch_type='planar1', res=res)
-    evoked.plot_topomap(times, ch_type='planar2', res=res)
-    evoked.plot_topomap(times, ch_type='grad', mask=mask, res=res,
-                        show_names=True, mask_params={'marker': 'x'})
+    plt_topomap(times, ch_type='grad', mask=mask)
+    plt_topomap(times, ch_type='planar1')
+    plt_topomap(times, ch_type='planar2')
+    plt_topomap(times, ch_type='grad', mask=mask, show_names=True,
+                mask_params={'marker': 'x'})
     plt.close('all')
-    assert_raises(ValueError, evoked.plot_topomap, times, ch_type='eeg',
-                  res=res, average=-1000)
-    assert_raises(ValueError, evoked.plot_topomap, times, ch_type='eeg',
-                  res=res, average='hahahahah')
-
-    p = evoked.plot_topomap(times, ch_type='grad', res=res,
-                            show_names=lambda x: x.replace('MEG', ''),
-                            image_interp='bilinear')
+    assert_raises(ValueError, plt_topomap, times, ch_type='eeg', average=-1e3)
+    assert_raises(ValueError, plt_topomap, times, ch_type='eeg', average='x')
+
+    p = plt_topomap(times, ch_type='grad', image_interp='bilinear',
+                    show_names=lambda x: x.replace('MEG', ''))
     subplot = [x for x in p.get_children() if
                isinstance(x, matplotlib.axes.Subplot)][0]
     assert_true(all('MEG' not in x.get_text()
@@ -117,7 +152,7 @@ def test_plot_topomap():
     # Plot array
     for ch_type in ('mag', 'grad'):
         evoked_ = evoked.copy().pick_types(eeg=False, meg=ch_type)
-        plot_topomap(evoked_.data[:, 0], evoked_.info)
+        plot_topomap(evoked_.data[:, 0], evoked_.info, **fast_test)
     # fail with multiple channel types
     assert_raises(ValueError, plot_topomap, evoked.data[0, :], evoked.info)
 
@@ -126,9 +161,9 @@ def test_plot_topomap():
         return [x.get_text() for x in p.get_children() if
                 isinstance(x, matplotlib.text.Text)]
 
-    p = evoked.plot_topomap(times, ch_type='eeg', res=res, average=0.01)
+    p = plt_topomap(times, ch_type='eeg', average=0.01)
     assert_equal(len(get_texts(p)), 0)
-    p = evoked.plot_topomap(times, ch_type='eeg', title='Custom', res=res)
+    p = plt_topomap(times, ch_type='eeg', title='Custom')
     texts = get_texts(p)
     assert_equal(len(texts), 1)
     assert_equal(texts[0], 'Custom')
@@ -137,7 +172,7 @@ def test_plot_topomap():
     # delaunay triangulation warning
     with warnings.catch_warnings(record=True):  # can't show
         warnings.simplefilter('always')
-        evoked.plot_topomap(times, ch_type='mag', layout=None, res=res)
+        plt_topomap(times, ch_type='mag', layout=None)
     assert_raises(RuntimeError, plot_evoked_topomap, evoked, 0.1, 'mag',
                   proj='interactive')  # projs have already been applied
 
@@ -146,20 +181,19 @@ def test_plot_topomap():
                           baseline=(None, 0), proj=False)
     with warnings.catch_warnings(record=True):
         warnings.simplefilter('always')
-        evoked.plot_topomap(0.1, 'mag', proj='interactive', res=res)
+        fig1 = evoked.plot_topomap('interactive', 'mag', proj='interactive',
+                                   **fast_test)
+        _fake_click(fig1, fig1.axes[1], (0.5, 0.5))  # click slider
+    data_max = np.max(fig1.axes[0].images[0]._A)
+    fig2 = plt.gcf()
+    _fake_click(fig2, fig2.axes[0], (0.075, 0.775))  # toggle projector
+    # make sure projector gets toggled
+    assert_true(np.max(fig1.axes[0].images[0]._A) != data_max)
+
     assert_raises(RuntimeError, plot_evoked_topomap, evoked,
                   np.repeat(.1, 50))
     assert_raises(ValueError, plot_evoked_topomap, evoked, [-3e12, 15e6])
 
-    with warnings.catch_warnings(record=True):  # file conventions
-        warnings.simplefilter('always')
-        projs = read_proj(ecg_fname)
-    projs = [pp for pp in projs if pp['desc'].lower().find('eeg') < 0]
-    plot_projs_topomap(projs, res=res, colorbar=True)
-    plt.close('all')
-    ax = plt.subplot(111)
-    plot_projs_topomap([projs[0]], res=res, axes=ax)  # test axes param
-    plt.close('all')
     for ch in evoked.info['chs']:
         if ch['coil_type'] == FIFF.FIFFV_COIL_EEG:
             ch['loc'].fill(0)
@@ -194,15 +228,15 @@ def test_plot_topomap():
     assert_array_equal(outlines['clip_radius'], 0.75)
 
     # Plot skirt
-    evoked.plot_topomap(times, ch_type='eeg', outlines='skirt')
+    evoked.plot_topomap(times, ch_type='eeg', outlines='skirt', **fast_test)
 
     # Pass custom outlines without patch
-    evoked.plot_topomap(times, ch_type='eeg', outlines=outlines)
+    evoked.plot_topomap(times, ch_type='eeg', outlines=outlines, **fast_test)
     plt.close('all')
 
     # Test interactive cmap
     fig = plot_evoked_topomap(evoked, times=[0., 0.1], ch_type='eeg',
-                              cmap=('Reds', True), title='title')
+                              cmap=('Reds', True), title='title', **fast_test)
     fig.canvas.key_press_event('up')
     fig.canvas.key_press_event(' ')
     fig.canvas.key_press_event('down')
@@ -226,7 +260,8 @@ def test_plot_topomap():
         return Circle((0.5, 0.4687), radius=.46,
                       clip_on=True, transform=plt.gca().transAxes)
     outlines['patch'] = patch
-    plot_evoked_topomap(evoked, times, ch_type='eeg', outlines=outlines)
+    plot_evoked_topomap(evoked, times, ch_type='eeg', outlines=outlines,
+                        **fast_test)
 
     # Remove digitization points. Now topomap should fail
     evoked.info['dig'] = None
@@ -260,7 +295,7 @@ def test_plot_topomap():
     # Test peak finder
     axes = [plt.subplot(131), plt.subplot(132)]
     with warnings.catch_warnings(record=True):  # rightmost column
-        evoked.plot_topomap(times='peaks', axes=axes)
+        evoked.plot_topomap(times='peaks', axes=axes, **fast_test)
     plt.close('all')
     evoked.data = np.zeros(evoked.data.shape)
     evoked.data[50][1] = 1
@@ -276,15 +311,18 @@ def test_plot_tfr_topomap():
     """Test plotting of TFR data."""
     import matplotlib as mpl
     import matplotlib.pyplot as plt
-    raw = _get_raw()
+    raw = read_raw_fif(raw_fname)
     times = np.linspace(-0.1, 0.1, 200)
+    res = 8
     n_freqs = 3
     nave = 1
     rng = np.random.RandomState(42)
-    data = rng.randn(len(raw.ch_names), n_freqs, len(times))
-    tfr = AverageTFR(raw.info, data, times, np.arange(n_freqs), nave)
+    picks = [93, 94, 96, 97, 21, 22, 24, 25, 129, 130, 315, 316, 2, 5, 8, 11]
+    info = pick_info(raw.info, picks)
+    data = rng.randn(len(picks), n_freqs, len(times))
+    tfr = AverageTFR(info, data, times, np.arange(n_freqs), nave)
     tfr.plot_topomap(ch_type='mag', tmin=0.05, tmax=0.150, fmin=0, fmax=10,
-                     res=16)
+                     res=res, contours=0)
 
     eclick = mpl.backend_bases.MouseEvent('button_press_event',
                                           plt.gcf().canvas, 0, 0, 1)
diff --git a/mne/viz/tests/test_utils.py b/mne/viz/tests/test_utils.py
index 336661e..5166b88 100644
--- a/mne/viz/tests/test_utils.py
+++ b/mne/viz/tests/test_utils.py
@@ -68,35 +68,40 @@ def test_add_background_image():
     """Test adding background image to a figure."""
     import matplotlib.pyplot as plt
     rng = np.random.RandomState(0)
-    f, axs = plt.subplots(1, 2)
-    x, y = rng.randn(2, 10)
-    im = rng.randn(10, 10)
-    axs[0].scatter(x, y)
-    axs[1].scatter(y, x)
-    for ax in axs:
-        ax.set_aspect(1)
-
-    # Background without changing aspect
-    ax_im = add_background_image(f, im)
-    assert_true(ax_im.get_aspect() == 'auto')
-    for ax in axs:
-        assert_true(ax.get_aspect() == 1)
-
-    # Background with changing aspect
-    ax_im_asp = add_background_image(f, im, set_ratios='auto')
-    assert_true(ax_im_asp.get_aspect() == 'auto')
-    for ax in axs:
-        assert_true(ax.get_aspect() == 'auto')
+    for ii in range(2):
+        f, axs = plt.subplots(1, 2)
+        x, y = rng.randn(2, 10)
+        im = rng.randn(10, 10)
+        axs[0].scatter(x, y)
+        axs[1].scatter(y, x)
+        for ax in axs:
+            ax.set_aspect(1)
+
+        # Background without changing aspect
+        if ii == 0:
+            ax_im = add_background_image(f, im)
+            return
+            assert_true(ax_im.get_aspect() == 'auto')
+            for ax in axs:
+                assert_true(ax.get_aspect() == 1)
+        else:
+            # Background with changing aspect
+            ax_im_asp = add_background_image(f, im, set_ratios='auto')
+            assert_true(ax_im_asp.get_aspect() == 'auto')
+            for ax in axs:
+                assert_true(ax.get_aspect() == 'auto')
+        plt.close('all')
 
     # Make sure passing None as image returns None
+    f, axs = plt.subplots(1, 2)
     assert_true(add_background_image(f, None) is None)
+    plt.close('all')
 
 
 def test_auto_scale():
     """Test auto-scaling of channels for quick plotting."""
-    raw = read_raw_fif(raw_fname, preload=False, add_eeg_ref=False)
-    ev = read_events(ev_fname)
-    epochs = Epochs(raw, ev, add_eeg_ref=False)
+    raw = read_raw_fif(raw_fname)
+    epochs = Epochs(raw, read_events(ev_fname))
     rand_data = np.random.randn(10, 100)
 
     for inst in [raw, epochs]:
@@ -138,6 +143,7 @@ def test_validate_if_list_of_axes():
     ax_flat[2] = 23
     assert_raises(ValueError, _validate_if_list_of_axes, ax_flat)
     _validate_if_list_of_axes(ax, 4)
+    plt.close('all')
 
 
 run_tests_if_main()
diff --git a/mne/viz/topo.py b/mne/viz/topo.py
index 5da18bf..7a39ecc 100644
--- a/mne/viz/topo.py
+++ b/mne/viz/topo.py
@@ -1,5 +1,4 @@
-"""Functions to plot M/EEG data on topo (one axes per channel)
-"""
+"""Functions to plot M/EEG data on topo (one axes per channel)."""
 from __future__ import print_function
 
 # Authors: Alexandre Gramfort <alexandre.gramfort at telecom-paristech.fr>
@@ -11,7 +10,8 @@ from __future__ import print_function
 
 from functools import partial
 from itertools import cycle
-
+from copy import deepcopy
+from matplotlib.colors import colorConverter
 import numpy as np
 
 from ..io.constants import Bunch
@@ -21,13 +21,13 @@ from ..channels.layout import _merge_grad_data, _pair_grad_sensors, find_layout
 from ..defaults import _handle_default
 from .utils import (_check_delayed_ssp, COLORS, _draw_proj_checkbox,
                     add_background_image, plt_show, _setup_vmin_vmax,
-                    DraggableColorbar)
+                    DraggableColorbar, _set_ax_facecolor, _setup_ax_spines)
 
 
 def iter_topography(info, layout=None, on_pick=None, fig=None,
                     fig_facecolor='k', axis_facecolor='k',
                     axis_spinecolor='k', layout_scale=None):
-    """ Create iterator over channel positions
+    """Create iterator over channel positions.
 
     This function returns a generator that unpacks into
     a series of matplotlib axis objects and data / channel
@@ -76,8 +76,8 @@ def iter_topography(info, layout=None, on_pick=None, fig=None,
 
 def _iter_topography(info, layout, on_pick, fig, fig_facecolor='k',
                      axis_facecolor='k', axis_spinecolor='k',
-                     layout_scale=None, unified=False, img=False):
-    """Private helper to iterate over topography
+                     layout_scale=None, unified=False, img=False, axes=None):
+    """Iterate over topography.
 
     Has the same parameters as iter_topography, plus:
 
@@ -106,9 +106,12 @@ def _iter_topography(info, layout, on_pick, fig, fig_facecolor='k',
     ch_names = _clean_names(info['ch_names'])
     iter_ch = [(x, y) for x, y in enumerate(layout.names) if y in ch_names]
     if unified:
-        under_ax = plt.axes([0, 0, 1, 1])
+        if axes is None:
+            under_ax = plt.axes([0, 0, 1, 1])
+            under_ax.axis('off')
+        else:
+            under_ax = axes
         under_ax.set(xlim=[0, 1], ylim=[0, 1])
-        under_ax.axis('off')
         axs = list()
     for idx, name in iter_ch:
         ch_idx = ch_names.index(name)
@@ -146,13 +149,18 @@ def _iter_topography(info, layout, on_pick, fig, fig_facecolor='k',
 
 
 def _plot_topo(info, times, show_func, click_func=None, layout=None,
-               vmin=None, vmax=None, ylim=None, colorbar=None,
-               border='none', axis_facecolor='k', fig_facecolor='k',
-               cmap='RdBu_r', layout_scale=None, title=None, x_label=None,
-               y_label=None, font_color='w', unified=False, img=False):
-    """Helper function to plot on sensor layout"""
+               vmin=None, vmax=None, ylim=None, colorbar=None, border='none',
+               axis_facecolor='k', fig_facecolor='k', cmap='RdBu_r',
+               layout_scale=None, title=None, x_label=None, y_label=None,
+               font_color='w', unified=False, img=False, axes=None):
+    """Plot on sensor layout."""
     import matplotlib.pyplot as plt
 
+    if layout.kind == 'custom':
+        layout = deepcopy(layout)
+        layout.pos[:, :2] -= layout.pos[:, :2].min(0)
+        layout.pos[:, :2] /= layout.pos[:, :2].max(0)
+
     # prepare callbacks
     tmin, tmax = times[[0, -1]]
     click_func = show_func if click_func is None else click_func
@@ -160,22 +168,27 @@ def _plot_topo(info, times, show_func, click_func=None, layout=None,
                       vmax=vmax, ylim=ylim, x_label=x_label,
                       y_label=y_label, colorbar=colorbar)
 
-    fig = plt.figure()
+    if axes is None:
+        fig = plt.figure()
+        axes = plt.axes([0.015, 0.025, 0.97, 0.95])
+        _set_ax_facecolor(axes, fig_facecolor)
+    else:
+        fig = axes.figure
     if colorbar:
         sm = plt.cm.ScalarMappable(cmap=cmap, norm=plt.Normalize(vmin, vmax))
         sm.set_array(np.linspace(vmin, vmax))
-        ax = plt.axes([0.015, 0.025, 1.05, .8], axisbg=fig_facecolor)
-        cb = fig.colorbar(sm, ax=ax)
+        cb = fig.colorbar(sm, ax=axes, pad=0.025, fraction=0.075, shrink=0.5,
+                          anchor=(-1, 0.5))
         cb_yticks = plt.getp(cb.ax.axes, 'yticklabels')
         plt.setp(cb_yticks, color=font_color)
-        ax.axis('off')
+    axes.axis('off')
 
     my_topo_plot = _iter_topography(info, layout=layout, on_pick=on_pick,
                                     fig=fig, layout_scale=layout_scale,
                                     axis_spinecolor=border,
                                     axis_facecolor=axis_facecolor,
                                     fig_facecolor=fig_facecolor,
-                                    unified=unified, img=img)
+                                    unified=unified, img=img, axes=axes)
 
     for ax, ch_idx in my_topo_plot:
         if layout.kind == 'Vectorview-all' and ylim is not None:
@@ -188,20 +201,15 @@ def _plot_topo(info, times, show_func, click_func=None, layout=None,
                   vmax=vmax, ylim=ylim_)
 
     if title is not None:
-        plt.figtext(0.03, 0.9, title, color=font_color, fontsize=19)
+        plt.figtext(0.03, 0.95, title, color=font_color, fontsize=15, va='top')
 
     return fig
 
 
 def _plot_topo_onpick(event, show_func):
-    """Onpick callback that shows a single channel in a new figure"""
-
+    """Onpick callback that shows a single channel in a new figure."""
     # make sure that the swipe gesture in OS-X doesn't open many figures
     orig_ax = event.inaxes
-    if event.inaxes is None or (not hasattr(orig_ax, '_mne_ch_idx') and
-                                not hasattr(orig_ax, '_mne_axs')):
-        return
-
     import matplotlib.pyplot as plt
     try:
         if hasattr(orig_ax, '_mne_axs'):  # in unified, single-axes mode
@@ -213,16 +221,20 @@ def _plot_topo_onpick(event, show_func):
                     orig_ax = ax
                     break
             else:
+                # no axis found
                 return
+        elif not hasattr(orig_ax, '_mne_ch_idx'):
+            # neither old nor new mode
+            return
         ch_idx = orig_ax._mne_ch_idx
         face_color = orig_ax._mne_ax_face_color
         fig, ax = plt.subplots(1)
 
         plt.title(orig_ax._mne_ch_name)
-        ax.set_axis_bgcolor(face_color)
+        _set_ax_facecolor(ax, face_color)
 
         # allow custom function to override parameters
-        show_func(plt, ch_idx)
+        show_func(ax, ch_idx)
 
     except Exception as err:
         # matplotlib silently ignores exceptions in event handlers,
@@ -233,7 +245,7 @@ def _plot_topo_onpick(event, show_func):
 
 
 def _compute_scalings(bn, xlim, ylim):
-    """Compute scale factors for a unified plot"""
+    """Compute scale factors for a unified plot."""
     if isinstance(ylim[0], (tuple, list, np.ndarray)):
         ylim = (ylim[0][0], ylim[1][0])
     pos = bn.pos
@@ -244,33 +256,80 @@ def _compute_scalings(bn, xlim, ylim):
 
 
 def _check_vlim(vlim):
-    """AUX function"""
+    """Check the vlim."""
     return not np.isscalar(vlim) and vlim is not None
 
 
 def _imshow_tfr(ax, ch_idx, tmin, tmax, vmin, vmax, onselect, ylim=None,
-                tfr=None, freq=None, vline=None, x_label=None, y_label=None,
-                colorbar=False, picker=True, cmap=('RdBu_r', True), title=None,
-                hline=None):
-    """ Aux function to show time-freq map on topo """
-    import matplotlib.pyplot as plt
+                tfr=None, freq=None, x_label=None, y_label=None,
+                colorbar=False, cmap=('RdBu_r', True), yscale='auto'):
+    """Show time-frequency map as two-dimensional image."""
+    from matplotlib import pyplot as plt, ticker
     from matplotlib.widgets import RectangleSelector
 
-    extent = (tmin, tmax, freq[0], freq[-1])
-    cmap, interactive_cmap = cmap
+    if yscale not in ['auto', 'linear', 'log']:
+        raise ValueError("yscale should be either 'auto', 'linear', or 'log'"
+                         ", got {}".format(yscale))
 
-    img = ax.imshow(tfr[ch_idx], extent=extent, aspect="auto", origin="lower",
-                    vmin=vmin, vmax=vmax, picker=picker, cmap=cmap)
-    if isinstance(ax, plt.Axes):
-        if x_label is not None:
-            ax.set_xlabel(x_label)
-        if y_label is not None:
-            ax.set_ylabel(y_label)
+    cmap, interactive_cmap = cmap
+    times = np.linspace(tmin, tmax, num=tfr[ch_idx].shape[1])
+
+    # test yscale
+    if yscale == 'log' and not freq[0] > 0:
+        raise ValueError('Using log scale for frequency axis requires all your'
+                         ' frequencies to be positive (you cannot include'
+                         ' the DC component (0 Hz) in the TFR).')
+    if len(freq) < 2 or freq[0] == 0:
+        yscale = 'linear'
+    elif yscale != 'linear':
+        ratio = freq[1:] / freq[:-1]
+    if yscale == 'auto':
+        if freq[0] > 0 and np.allclose(ratio, ratio[0]):
+            yscale = 'log'
+        else:
+            yscale = 'linear'
+
+    # compute bounds between time samples
+    time_diff = np.diff(times) / 2. if len(times) > 1 else [0.0005]
+    time_lims = np.concatenate([[times[0] - time_diff[0]], times[:-1] +
+                               time_diff, [times[-1] + time_diff[-1]]])
+
+    # the same for frequency - depending on whether yscale is log
+    if yscale == 'linear':
+        freq_diff = np.diff(freq) / 2. if len(freq) > 1 else [0.5]
+        freq_lims = np.concatenate([[freq[0] - freq_diff[0]], freq[:-1] +
+                                   freq_diff, [freq[-1] + freq_diff[-1]]])
     else:
-        if x_label is not None:
-            plt.xlabel(x_label)
-        if y_label is not None:
-            plt.ylabel(y_label)
+        log_freqs = np.concatenate([[freq[0] / ratio[0]], freq,
+                                   [freq[-1] * ratio[0]]])
+        freq_lims = np.sqrt(log_freqs[:-1] * log_freqs[1:])
+
+    # construct a time-frequency bounds grid
+    time_mesh, freq_mesh = np.meshgrid(time_lims, freq_lims)
+
+    img = ax.pcolormesh(time_mesh, freq_mesh, tfr[ch_idx], cmap=cmap,
+                        vmin=vmin, vmax=vmax)
+
+    # limits, yscale and yticks
+    ax.set_xlim(time_lims[0], time_lims[-1])
+    if ylim is None:
+        ylim = (freq_lims[0], freq_lims[-1])
+    ax.set_ylim(ylim)
+
+    if yscale == 'log':
+        ax.set_yscale('log')
+        ax.get_yaxis().set_major_formatter(ticker.ScalarFormatter())
+
+    ax.yaxis.set_minor_formatter(ticker.NullFormatter())
+    ax.yaxis.set_minor_locator(ticker.NullLocator())  # get rid of minor ticks
+    tick_vals = freq[np.unique(np.linspace(
+        0, len(freq) - 1, 12).round().astype('int'))]
+    ax.set_yticks(tick_vals)
+
+    if x_label is not None:
+        ax.set_xlabel(x_label)
+    if y_label is not None:
+        ax.set_ylabel(y_label)
     if colorbar:
         if isinstance(colorbar, DraggableColorbar):
             cbar = colorbar.cbar  # this happens with multiaxes case
@@ -278,10 +337,6 @@ def _imshow_tfr(ax, ch_idx, tmin, tmax, vmin, vmax, onselect, ylim=None,
             cbar = plt.colorbar(mappable=img)
         if interactive_cmap:
             ax.CB = DraggableColorbar(cbar, img)
-    if title:
-        plt.title(title)
-    if not isinstance(ax, plt.Axes):
-        ax = plt.gca()
     ax.RS = RectangleSelector(ax, onselect=onselect)  # reference must be kept
 
 
@@ -289,7 +344,7 @@ def _imshow_tfr_unified(bn, ch_idx, tmin, tmax, vmin, vmax, onselect,
                         ylim=None, tfr=None, freq=None, vline=None,
                         x_label=None, y_label=None, colorbar=False,
                         picker=True, cmap='RdBu_r', title=None, hline=None):
-    """Aux function to show multiple tfrs on topo using a single axes"""
+    """Show multiple tfrs on topo using a single axes."""
     _compute_scalings(bn, (tmin, tmax), (freq[0], freq[-1]))
     ax = bn.ax
     data_lines = bn.data_lines
@@ -302,38 +357,104 @@ def _imshow_tfr_unified(bn, ch_idx, tmin, tmax, vmin, vmax, onselect,
 
 def _plot_timeseries(ax, ch_idx, tmin, tmax, vmin, vmax, ylim, data, color,
                      times, vline=None, x_label=None, y_label=None,
-                     colorbar=False, hline=None):
-    """Aux function to show time series on topo split across multiple axes"""
+                     colorbar=False, hline=None, hvline_color='w',
+                     labels=None):
+    """Show time series on topo split across multiple axes."""
     import matplotlib.pyplot as plt
     picker_flag = False
     for data_, color_ in zip(data, color):
         if not picker_flag:
             # use large tol for picker so we can click anywhere in the axes
-            ax.plot(times, data_[ch_idx], color_, picker=1e9)
+            ax.plot(times, data_[ch_idx], color=color_, picker=1e9)
             picker_flag = True
         else:
-            ax.plot(times, data_[ch_idx], color_)
-    if vline:
-        for x in vline:
-            plt.axvline(x, color='w', linewidth=0.5)
-    if hline:
-        for y in hline:
-            plt.axhline(y, color='w', linewidth=0.5)
+            ax.plot(times, data_[ch_idx], color=color_)
+
     if x_label is not None:
         plt.xlabel(x_label)
+
     if y_label is not None:
         if isinstance(y_label, list):
-            plt.ylabel(y_label[ch_idx])
+            ax.set_ylabel(y_label[ch_idx])
         else:
-            plt.ylabel(y_label)
+            ax.set_ylabel(y_label)
+
+    def _format_coord(x, y, labels, ax):
+        """Create status string based on cursor coordinates."""
+        idx = np.abs(times - x).argmin()
+        ylabel = ax.get_ylabel()
+        unit = (ylabel[ylabel.find('(') + 1:ylabel.find(')')]
+                if '(' in ylabel and ')' in ylabel else '')
+        labels = [''] * len(data) if labels is None else labels
+        # try to estimate whether to truncate condition labels
+        slen = 10 + sum([12 + len(unit) + len(label) for label in labels])
+        bar_width = (ax.figure.get_size_inches() * ax.figure.dpi)[0] / 5.5
+        trunc_labels = bar_width < slen
+        s = '%6.3f s: ' % times[idx]
+        for data_, label in zip(data, labels):
+            s += '%7.2f %s' % (data_[ch_idx, idx], unit)
+            if trunc_labels:
+                label = (label if len(label) <= 10 else
+                         '%s..%s' % (label[:6], label[-2:]))
+            s += ' [%s] ' % label if label else ' '
+        return s
+
+    ax.format_coord = lambda x, y: _format_coord(x, y, labels=labels, ax=ax)
+
+    def _cursor_vline(event):
+        """Draw cursor (vertical line)."""
+        ax = event.inaxes
+        if not ax:
+            return
+        if ax._cursorline is not None:
+            ax._cursorline.remove()
+        ax._cursorline = ax.axvline(event.xdata, color=ax._cursorcolor)
+        ax.figure.canvas.draw()
+
+    def _rm_cursor(event):
+        ax = event.inaxes
+        if ax._cursorline is not None:
+            ax._cursorline.remove()
+            ax._cursorline = None
+        ax.figure.canvas.draw()
+
+    ax._cursorline = None
+    # choose cursor color based on perceived brightness of background
+    try:
+        facecol = colorConverter.to_rgb(ax.get_facecolor())
+    except AttributeError:  # older MPL
+        facecol = colorConverter.to_rgb(ax.get_axis_bgcolor())
+    face_brightness = np.dot(facecol, np.array([299, 587, 114]))
+    ax._cursorcolor = 'white' if face_brightness < 150 else 'black'
+
+    plt.connect('motion_notify_event', _cursor_vline)
+    plt.connect('axes_leave_event', _rm_cursor)
+
+    _setup_ax_spines(ax, vline, tmin, tmax)
+    ax.figure.set_facecolor('k' if hvline_color is 'w' else 'w')
+    ax.spines['bottom'].set_color(hvline_color)
+    ax.spines['left'].set_color(hvline_color)
+    ax.tick_params(axis='x', colors=hvline_color, which='both')
+    ax.tick_params(axis='y', colors=hvline_color, which='both')
+    ax.title.set_color(hvline_color)
+    ax.xaxis.label.set_color(hvline_color)
+    ax.yaxis.label.set_color(hvline_color)
+
+    if vline:
+        plt.axvline(vline, color=hvline_color, linewidth=1.0,
+                    linestyle='--')
+    if hline:
+        plt.axhline(hline, color=hvline_color, linewidth=1.0, zorder=10)
+
     if colorbar:
         plt.colorbar()
 
 
 def _plot_timeseries_unified(bn, ch_idx, tmin, tmax, vmin, vmax, ylim, data,
                              color, times, vline=None, x_label=None,
-                             y_label=None, colorbar=False, hline=None):
-    """Aux function to show multiple time series on topo using a single axes"""
+                             y_label=None, colorbar=False, hline=None,
+                             hvline_color='w'):
+    """Show multiple time series on topo using a single axes."""
     import matplotlib.pyplot as plt
     if not (ylim and not any(v is None for v in ylim)):
         ylim = np.array([np.min(data), np.max(data)])
@@ -346,13 +467,15 @@ def _plot_timeseries_unified(bn, ch_idx, tmin, tmax, vmin, vmax, ylim, data,
     for data_, color_ in zip(data, color):
         data_lines.append(ax.plot(
             bn.x_t + bn.x_s * times, bn.y_t + bn.y_s * data_[ch_idx],
-            color_, clip_on=True, clip_box=pos)[0])
+            color=color_, clip_on=True, clip_box=pos)[0])
     if vline:
         vline = np.array(vline) * bn.x_s + bn.x_t
-        ax.vlines(vline, pos[1], pos[1] + pos[3], color='w', linewidth=0.5)
+        ax.vlines(vline, pos[1], pos[1] + pos[3], color=hvline_color,
+                  linewidth=0.5, linestyle='--')
     if hline:
         hline = np.array(hline) * bn.y_s + bn.y_t
-        ax.hlines(hline, pos[0], pos[0] + pos[2], color='w', linewidth=0.5)
+        ax.hlines(hline, pos[0], pos[0] + pos[2], color=hvline_color,
+                  linewidth=0.5)
     if x_label is not None:
         ax.text(pos[0] + pos[2] / 2., pos[1], x_label,
                 horizontalalignment='center', verticalalignment='top')
@@ -369,10 +492,10 @@ def _erfimage_imshow(ax, ch_idx, tmin, tmax, vmin, vmax, ylim=None, data=None,
                      epochs=None, sigma=None, order=None, scalings=None,
                      vline=None, x_label=None, y_label=None, colorbar=False,
                      cmap='RdBu_r'):
-    """Aux function to plot erfimage on sensor topography"""
+    """Plot erfimage on sensor topography."""
     from scipy import ndimage
     import matplotlib.pyplot as plt
-    this_data = data[:, ch_idx, :].copy()
+    this_data = data[:, ch_idx, :].copy() * scalings[ch_idx]
 
     if callable(order):
         order = order(epochs.times, this_data)
@@ -383,9 +506,9 @@ def _erfimage_imshow(ax, ch_idx, tmin, tmax, vmin, vmax, ylim=None, data=None,
     if sigma > 0.:
         this_data = ndimage.gaussian_filter1d(this_data, sigma=sigma, axis=0)
 
-    ax.imshow(this_data, extent=[tmin, tmax, 0, len(data)], aspect='auto',
-              origin='lower', vmin=vmin, vmax=vmax, picker=True, cmap=cmap,
-              interpolation='nearest')
+    img = ax.imshow(this_data, extent=[tmin, tmax, 0, len(data)],
+                    aspect='auto', origin='lower', vmin=vmin, vmax=vmax,
+                    picker=True, cmap=cmap, interpolation='nearest')
 
     ax = plt.gca()
     if x_label is not None:
@@ -393,21 +516,21 @@ def _erfimage_imshow(ax, ch_idx, tmin, tmax, vmin, vmax, ylim=None, data=None,
     if y_label is not None:
         ax.set_ylabel(y_label)
     if colorbar:
-        plt.colorbar()
+        plt.colorbar(mappable=img)
 
 
 def _erfimage_imshow_unified(bn, ch_idx, tmin, tmax, vmin, vmax, ylim=None,
                              data=None, epochs=None, sigma=None, order=None,
                              scalings=None, vline=None, x_label=None,
                              y_label=None, colorbar=False, cmap='RdBu_r'):
-    """Aux function to plot erfimage topography using a single axis"""
+    """Plot erfimage topography using a single axis."""
     from scipy import ndimage
     _compute_scalings(bn, (tmin, tmax), (0, len(epochs.events)))
     ax = bn.ax
     data_lines = bn.data_lines
     extent = (bn.x_t + bn.x_s * tmin, bn.x_t + bn.x_s * tmax, bn.y_t,
               bn.y_t + bn.y_s * len(epochs.events))
-    this_data = data[:, ch_idx, :].copy()
+    this_data = data[:, ch_idx, :].copy() * scalings[ch_idx]
 
     if callable(order):
         order = order(epochs.times, this_data)
@@ -428,7 +551,7 @@ def _plot_evoked_topo(evoked, layout=None, layout_scale=0.945, color=None,
                       border='none', ylim=None, scalings=None, title=None,
                       proj=False, vline=(0.,), hline=(0.,), fig_facecolor='k',
                       fig_background=None, axis_facecolor='k', font_color='w',
-                      merge_grads=False, show=True):
+                      merge_grads=False, legend=True, axes=None, show=True):
     """Plot 2D topography of evoked responses.
 
     Clicking on the plot of an individual sensor opens a new figure showing
@@ -482,6 +605,16 @@ def _plot_evoked_topo(evoked, layout=None, layout_scale=0.945, color=None,
     merge_grads : bool
         Whether to use RMS value of gradiometer pairs. Only works for Neuromag
         data. Defaults to False.
+    legend : bool | int | string | tuple
+        If True, create a legend based on evoked.comment. If False, disable the
+        legend. Otherwise, the legend is created and the parameter value is
+        passed as the location parameter to the matplotlib legend call. It can
+        be an integer (e.g. 0 corresponds to upper right corner of the plot),
+        a string (e.g. 'upper right'), or a tuple (x, y coordinates of the
+        lower left corner of the legend in the axes coordinate system).
+        See matplotlib documentation for more details.
+    axes : instance of matplotlib Axes | None
+        Axes to plot into. If None, axes will be created.
     show : bool
         Show figure if True.
 
@@ -490,6 +623,8 @@ def _plot_evoked_topo(evoked, layout=None, layout_scale=0.945, color=None,
     fig : Instance of matplotlib.figure.Figure
         Images of evoked responses at sensor locations
     """
+    import matplotlib.pyplot as plt
+
     if not type(evoked) in (tuple, list):
         evoked = [evoked]
 
@@ -564,7 +699,7 @@ def _plot_evoked_topo(evoked, layout=None, layout_scale=0.945, color=None,
 
         for e in evoked:
             for pick, ch_type in zip(picks, types_used):
-                e.data[pick] = e.data[pick] * scalings[ch_type]
+                e.data[pick] *= scalings[ch_type]
 
         if proj is True and all(e.proj is not True for e in evoked):
             evoked = [e.apply_proj() for e in evoked]
@@ -593,21 +728,34 @@ def _plot_evoked_topo(evoked, layout=None, layout_scale=0.945, color=None,
         raise TypeError('ylim must be None or a dict. Got %s.' % type(ylim))
 
     data = [e.data for e in evoked]
+    comments = [e.comment for e in evoked]
     show_func = partial(_plot_timeseries_unified, data=data, color=color,
-                        times=times, vline=vline, hline=hline)
+                        times=times, vline=vline, hline=hline,
+                        hvline_color=font_color)
     click_func = partial(_plot_timeseries, data=data, color=color, times=times,
-                         vline=vline, hline=hline)
+                         vline=vline, hline=hline, hvline_color=font_color,
+                         labels=comments)
 
     fig = _plot_topo(info=info, times=times, show_func=show_func,
-                     click_func=click_func, layout=layout,
-                     colorbar=False, ylim=ylim_, cmap=None,
-                     layout_scale=layout_scale, border=border,
-                     fig_facecolor=fig_facecolor, font_color=font_color,
-                     axis_facecolor=axis_facecolor, title=title,
-                     x_label='Time (s)', y_label=y_label, unified=True)
+                     click_func=click_func, layout=layout, colorbar=False,
+                     ylim=ylim_, cmap=None, layout_scale=layout_scale,
+                     border=border, fig_facecolor=fig_facecolor,
+                     font_color=font_color, axis_facecolor=axis_facecolor,
+                     title=title, x_label='Time (s)', y_label=y_label,
+                     unified=True, axes=axes)
 
     add_background_image(fig, fig_background)
 
+    if legend is not False:
+        legend_loc = 0 if legend is True else legend
+        labels = [e.comment if e.comment else 'Unknown' for e in evoked]
+        legend = plt.legend(labels, loc=legend_loc,
+                            prop={'size': 10})
+        legend.get_frame().set_facecolor(axis_facecolor)
+        txts = legend.get_texts()
+        for txt, col in zip(txts, color):
+            txt.set_color(col)
+
     if proj == 'interactive':
         for e in evoked:
             _check_delayed_ssp(e)
@@ -621,7 +769,7 @@ def _plot_evoked_topo(evoked, layout=None, layout_scale=0.945, color=None,
 
 
 def _plot_update_evoked_topo_proj(params, bools):
-    """Helper function to update topo sensor plots"""
+    """Update topo sensor plots."""
     evokeds = [e.copy() for e in params['evokeds']]
     fig = params['fig']
     projs = [proj for proj, b in zip(params['projs'], bools) if b]
@@ -643,7 +791,7 @@ def plot_topo_image_epochs(epochs, layout=None, sigma=0., vmin=None,
                            layout_scale=.95, title=None, scalings=None,
                            border='none', fig_facecolor='k',
                            fig_background=None, font_color='w', show=True):
-    """Plot Event Related Potential / Fields image on topographies
+    """Plot Event Related Potential / Fields image on topographies.
 
     Parameters
     ----------
@@ -701,17 +849,15 @@ def plot_topo_image_epochs(epochs, layout=None, sigma=0., vmin=None,
     for idx in range(epochs.info['nchan']):
         ch_type = channel_type(epochs.info, idx)
         scale_coeffs.append(scalings.get(ch_type, 1))
-    for epoch_data in data:
-        epoch_data *= np.asarray(scale_coeffs)[:, np.newaxis]
     vmin, vmax = _setup_vmin_vmax(data, vmin, vmax)
 
     if layout is None:
         layout = find_layout(epochs.info)
 
-    show_func = partial(_erfimage_imshow_unified, scalings=scalings,
+    show_func = partial(_erfimage_imshow_unified, scalings=scale_coeffs,
                         order=order, data=data, epochs=epochs, sigma=sigma,
                         cmap=cmap)
-    erf_imshow = partial(_erfimage_imshow, scalings=scalings, order=order,
+    erf_imshow = partial(_erfimage_imshow, scalings=scale_coeffs, order=order,
                          data=data, epochs=epochs, sigma=sigma, cmap=cmap)
 
     fig = _plot_topo(info=epochs.info, times=epochs.times,
diff --git a/mne/viz/topomap.py b/mne/viz/topomap.py
index ae5225a..becb3af 100644
--- a/mne/viz/topomap.py
+++ b/mne/viz/topomap.py
@@ -1,5 +1,4 @@
-"""Functions to plot M/EEG data e.g. topographies
-"""
+"""Functions to plot M/EEG data e.g. topographies."""
 from __future__ import print_function
 
 # Authors: Alexandre Gramfort <alexandre.gramfort at telecom-paristech.fr>
@@ -12,27 +11,30 @@ from __future__ import print_function
 import math
 import copy
 from functools import partial
+import itertools
+from numbers import Integral
+import warnings
 
 import numpy as np
-from scipy import linalg
 
 from ..baseline import rescale
 from ..io.constants import FIFF
 from ..io.pick import (pick_types, _picks_by_type, channel_type, pick_info,
-                       _pick_data_channels)
-from ..utils import _clean_names, _time_mask, verbose, logger, warn
+                       _pick_data_channels, pick_channels)
+from ..utils import _clean_names, _time_mask, verbose, logger, warn, _scale_dep
 from .utils import (tight_layout, _setup_vmin_vmax, _prepare_trellis,
                     _check_delayed_ssp, _draw_proj_checkbox, figure_nobar,
                     plt_show, _process_times, DraggableColorbar,
-                    _validate_if_list_of_axes)
+                    _validate_if_list_of_axes, _setup_cmap)
 from ..time_frequency import psd_multitaper
 from ..defaults import _handle_default
 from ..channels.layout import _find_topomap_coords
 from ..io.meas_info import Info
+from ..externals.six import string_types
 
 
 def _prepare_topo_plot(inst, ch_type, layout):
-    """"Aux Function"""
+    """Prepare topo plot."""
     info = copy.deepcopy(inst if isinstance(inst, Info) else inst.info)
 
     if layout is None and ch_type is not 'eeg':
@@ -90,7 +92,8 @@ def _prepare_topo_plot(inst, ch_type, layout):
 
 
 def _plot_update_evoked_topomap(params, bools):
-    """ Helper to update topomaps """
+    """Update topomaps."""
+    from ..channels.layout import _merge_grad_data
     projs = [proj for ii, proj in enumerate(params['projs'])
              if ii in np.where(bools)[0]]
 
@@ -100,21 +103,21 @@ def _plot_update_evoked_topomap(params, bools):
     new_evoked.add_proj(projs)
     new_evoked.apply_proj()
 
-    data = new_evoked.data[np.ix_(params['picks'],
-                                  params['time_idx'])] * params['scale']
+    data = new_evoked.data[:, params['time_idx']] * params['scale']
     if params['merge_grads']:
-        from ..channels.layout import _merge_grad_data
         data = _merge_grad_data(data)
-    image_mask = params['image_mask']
+    image_off = ~params['image_mask']
 
-    pos_x, pos_y = np.asarray(params['pos'])[:, :2].T
+    pos = np.asarray(params['pos'])[:, :2]
+    pos_x, pos_y = pos.T
+    interp = _GridData(pos)
 
     xi = np.linspace(pos_x.min(), pos_x.max(), params['res'])
     yi = np.linspace(pos_y.min(), pos_y.max(), params['res'])
     Xi, Yi = np.meshgrid(xi, yi)
     for ii, im in enumerate(params['images']):
-        Zi = _griddata(pos_x, pos_y, data[:, ii], Xi, Yi)
-        Zi[~image_mask] = np.nan
+        Zi = interp.set_values(data[:, ii])(Xi, Yi)
+        Zi[image_off] = np.nan
         im.set_data(Zi)
     for cont in params['contours']:
         cont.set_array(np.c_[Xi, Yi, Zi])
@@ -125,8 +128,8 @@ def _plot_update_evoked_topomap(params, bools):
 def plot_projs_topomap(projs, layout=None, cmap=None, sensors=True,
                        colorbar=False, res=64, size=1, show=True,
                        outlines='head', contours=6, image_interp='bilinear',
-                       axes=None):
-    """Plot topographic maps of SSP projections
+                       axes=None, info=None):
+    """Plot topographic maps of SSP projections.
 
     Parameters
     ----------
@@ -170,8 +173,12 @@ def plot_projs_topomap(projs, layout=None, cmap=None, sensors=True,
         masking options, either directly or as a function that returns patches
         (required for multi-axis plots). If None, nothing will be drawn.
         Defaults to 'head'.
-    contours : int | False | None
+    contours : int | array of float
         The number of contour lines to draw. If 0, no contours will be drawn.
+        When an integer, matplotlib ticker locator is used to find suitable
+        values for the contour thresholds (may sometimes be inaccurate, use
+        array for accuracy). If an array, the values represent the levels for
+        the contours. Defaults to 6.
     image_interp : str
         The image interpolation to be used. All matplotlib options are
         accepted.
@@ -179,6 +186,9 @@ def plot_projs_topomap(projs, layout=None, cmap=None, sensors=True,
         The axes to plot to. If list, the list must be a list of Axes of
         the same length as the number of projectors. If instance of Axes,
         there must be only one projector. Defaults to None.
+    info : instance of Info | None
+        The measurement information to use to determine the layout.
+        If not None, ``layout`` must be None.
 
     Returns
     -------
@@ -191,21 +201,33 @@ def plot_projs_topomap(projs, layout=None, cmap=None, sensors=True,
     """
     import matplotlib.pyplot as plt
     from mpl_toolkits.axes_grid1 import make_axes_locatable
-    if layout is None:
-        from ..channels import read_layout
-        layout = read_layout('Vectorview-all')
-
-    if not isinstance(layout, list):
-        layout = [layout]
+    from ..channels.layout import (_pair_grad_sensors_from_ch_names, Layout,
+                                   _merge_grad_data)
+    from ..channels import _get_ch_type
+    if info is not None:
+        if not isinstance(info, Info):
+            raise TypeError('info must be an instance of Info, got %s'
+                            % (type(info),))
+        if layout is not None:
+            raise ValueError('layout must be None if info is provided')
+    else:
+        if layout is None:
+            from ..channels import read_layout
+            layout = read_layout('Vectorview-all')
+        if not isinstance(layout, (list, tuple)):
+            layout = [layout]
+        if not isinstance(layout, (list, tuple)):
+            raise TypeError('layout must be an instance of Layout, list, '
+                            'or None, got %s' % (type(layout),))
+        if not all(isinstance(l, Layout) for l in layout):
+            raise TypeError('All entries in layout list must be of type '
+                            'Layout')
 
     n_projs = len(projs)
     nrows = math.floor(math.sqrt(n_projs))
     ncols = math.ceil(n_projs / nrows)
 
-    if cmap == 'interactive':
-        cmap = (None, True)
-    elif not isinstance(cmap, tuple):
-        cmap = (cmap, True)
+    cmap = _setup_cmap(cmap)
     if axes is None:
         plt.figure()
         axes = list()
@@ -217,54 +239,64 @@ def plot_projs_topomap(projs, layout=None, cmap=None, sensors=True,
     if len(axes) != len(projs):
         raise RuntimeError('There must be an axes for each picked projector.')
     for proj_idx, proj in enumerate(projs):
-        axes[proj_idx].set_title(proj['desc'][:10] + '...')
-        ch_names = _clean_names(proj['data']['col_names'])
+        title = proj['desc']
+        title = '\n'.join(title[ii:ii + 22] for ii in range(0, len(title), 22))
+        axes[proj_idx].set_title(title, fontsize=10)
+        ch_names = _clean_names(proj['data']['col_names'],
+                                remove_whitespace=True)
         data = proj['data']['data'].ravel()
 
-        idx = []
-        for l in layout:
-            is_vv = l.kind.startswith('Vectorview')
-            if is_vv:
-                from ..channels.layout import _pair_grad_sensors_from_ch_names
-                grad_pairs = _pair_grad_sensors_from_ch_names(ch_names)
-                if grad_pairs:
-                    ch_names = [ch_names[i] for i in grad_pairs]
-
-            idx = [l.names.index(c) for c in ch_names if c in l.names]
+        if info is not None:
+            info_names = _clean_names(info['ch_names'],
+                                      remove_whitespace=True)
+            use_info = pick_info(info, pick_channels(info_names, ch_names))
+            data_picks, pos, merge_grads, names, _ = _prepare_topo_plot(
+                use_info, _get_ch_type(use_info, None), None)
+            data = data[data_picks]
+            if merge_grads:
+                data = _merge_grad_data(data).ravel()
+        else:  # list of layouts
+            idx = []
+            for l in layout:
+                is_vv = l.kind.startswith('Vectorview')
+                if is_vv:
+                    grad_pairs = _pair_grad_sensors_from_ch_names(ch_names)
+                    if grad_pairs:
+                        ch_names = [ch_names[i] for i in grad_pairs]
+
+                l_names = _clean_names(l.names, remove_whitespace=True)
+                idx = [l_names.index(c) for c in ch_names if c in l_names]
+                if len(idx) == 0:
+                    continue
+                pos = l.pos[idx]
+                if is_vv and grad_pairs:
+                    shape = (len(idx) // 2, 2, -1)
+                    pos = pos.reshape(shape).mean(axis=1)
+                    data = _merge_grad_data(data[grad_pairs]).ravel()
+                break
             if len(idx) == 0:
-                continue
-
-            pos = l.pos[idx]
-            if is_vv and grad_pairs:
-                from ..channels.layout import _merge_grad_data
-                shape = (len(idx) // 2, 2, -1)
-                pos = pos.reshape(shape).mean(axis=1)
-                data = _merge_grad_data(data[grad_pairs]).ravel()
-
-            break
-
-        if len(idx):
-            im = plot_topomap(data, pos[:, :2], vmax=None, cmap=cmap[0],
-                              sensors=sensors, res=res, axes=axes[proj_idx],
-                              outlines=outlines, contours=contours,
-                              image_interp=image_interp, show=False)[0]
-            if colorbar:
-                divider = make_axes_locatable(axes[proj_idx])
-                cax = divider.append_axes("right", size="5%", pad=0.05)
-                cbar = plt.colorbar(im, cax=cax, cmap=cmap)
-                if cmap[1]:
-                    axes[proj_idx].CB = DraggableColorbar(cbar, im)
-        else:
-            raise RuntimeError('Cannot find a proper layout for projection %s'
-                               % proj['desc'])
+                raise RuntimeError('Cannot find a proper layout for '
+                                   'projection %s, consider explicitly '
+                                   'passing a Layout or Info as the layout '
+                                   'parameter.' % proj['desc'])
+
+        im = plot_topomap(data, pos[:, :2], vmax=None, cmap=cmap[0],
+                          sensors=sensors, res=res, axes=axes[proj_idx],
+                          outlines=outlines, contours=contours,
+                          image_interp=image_interp, show=False)[0]
+        if colorbar:
+            divider = make_axes_locatable(axes[proj_idx])
+            cax = divider.append_axes("right", size="5%", pad=0.05)
+            cbar = plt.colorbar(im, cax=cax, cmap=cmap)
+            if cmap[1]:
+                axes[proj_idx].CB = DraggableColorbar(cbar, im)
     tight_layout(fig=axes[0].get_figure())
     plt_show(show)
     return axes[0].get_figure()
 
 
 def _check_outlines(pos, outlines, head_pos=None):
-    """Check or create outlines for topoplot
-    """
+    """Check or create outlines for topoplot."""
     pos = np.array(pos, float)[:, :2]  # ensure we have a copy
     head_pos = dict() if head_pos is None else head_pos
     if not isinstance(head_pos, dict):
@@ -279,11 +311,11 @@ def _check_outlines(pos, outlines, head_pos=None):
             raise ValueError('head_pos["%s"] must have shape (2,), not '
                              '%s' % (key, head_pos[key].shape))
 
-    if outlines in ('head', 'skirt', None):
+    if isinstance(outlines, np.ndarray) or outlines in ('head', 'skirt', None):
         radius = 0.5
-        l = np.linspace(0, 2 * np.pi, 101)
-        head_x = np.cos(l) * radius
-        head_y = np.sin(l) * radius
+        ll = np.linspace(0, 2 * np.pi, 101)
+        head_x = np.cos(ll) * radius
+        head_y = np.sin(ll) * radius
         nose_x = np.array([0.18, 0, -0.18]) * radius
         nose_y = np.array([radius - .004, radius * 1.15, radius - .004])
         ear_x = np.array([.497, .510, .518, .5299, .5419, .54, .547,
@@ -304,7 +336,7 @@ def _check_outlines(pos, outlines, head_pos=None):
         else:
             outlines_dict = dict()
 
-        if outlines == 'skirt':
+        if isinstance(outlines, string_types) and outlines == 'skirt':
             if 'scale' not in head_pos:
                 # By default, fit electrodes inside the head circle
                 head_pos['scale'] = 1.0 / (pos.max(axis=0) - pos.min(axis=0))
@@ -324,24 +356,34 @@ def _check_outlines(pos, outlines, head_pos=None):
                 # this number was empirically determined (seems to work well)
                 head_pos['scale'] = 0.85 / (pos.max(axis=0) - pos.min(axis=0))
             pos *= head_pos['scale']
-            outlines_dict['autoshrink'] = True
             outlines_dict['mask_pos'] = head_x, head_y
-            outlines_dict['clip_radius'] = (0.5, 0.5)
+            if isinstance(outlines, np.ndarray):
+                outlines_dict['autoshrink'] = False
+                outlines_dict['clip_radius'] = outlines
+                x_scale = np.max(outlines_dict['head'][0]) / outlines[0]
+                y_scale = np.max(outlines_dict['head'][1]) / outlines[1]
+                for key in ['head', 'nose', 'ear_left', 'ear_right']:
+                    value = outlines_dict[key]
+                    value = (value[0] / x_scale, value[1] / y_scale)
+                    outlines_dict[key] = value
+            else:
+                outlines_dict['autoshrink'] = True
+                outlines_dict['clip_radius'] = (0.5, 0.5)
 
         outlines = outlines_dict
 
     elif isinstance(outlines, dict):
         if 'mask_pos' not in outlines:
-            raise ValueError('You must specify the coordinates of the image'
-                             'mask')
+            raise ValueError('You must specify the coordinates of the image '
+                             'mask.')
     else:
-        raise ValueError('Invalid value for `outlines')
+        raise ValueError('Invalid value for `outlines`.')
 
     return pos, outlines
 
 
 def _draw_outlines(ax, outlines):
-    """Helper for drawing the outlines for a topomap."""
+    """Draw the outlines for a topomap."""
     outlines_ = dict([(k, v) for k, v in outlines.items() if k not in
                       ['patch', 'autoshrink']])
     for key, (x_coord, y_coord) in outlines_.items():
@@ -351,44 +393,55 @@ def _draw_outlines(ax, outlines):
     return outlines_
 
 
-def _griddata(x, y, v, xi, yi):
-    """Aux function"""
-    xy = x.ravel() + y.ravel() * -1j
-    d = xy[None, :] * np.ones((len(xy), 1))
-    d = np.abs(d - d.T)
-    n = d.shape[0]
-    d.flat[::n + 1] = 1.
-
-    g = (d * d) * (np.log(d) - 1.)
-    g.flat[::n + 1] = 0.
-    weights = linalg.solve(g, v.ravel())
-
-    m, n = xi.shape
-    zi = np.zeros_like(xi)
-    xy = xy.T
-
-    g = np.empty(xy.shape)
-    for i in range(m):
-        for j in range(n):
-            d = np.abs(xi[i, j] + -1j * yi[i, j] - xy)
-            mask = np.where(d == 0)[0]
-            if len(mask):
-                d[mask] = 1.
-            np.log(d, out=g)
-            g -= 1.
-            g *= d * d
-            if len(mask):
-                g[mask] = 0.
-            zi[i, j] = g.dot(weights)
-    return zi
+class _GridData(object):
+    """Unstructured (x,y) data interpolator.
+
+    This class allows optimized interpolation by computing parameters
+    for a fixed set of true points, and allowing the values at those points
+    to be set independently.
+    """
+
+    def __init__(self, pos):
+        from scipy.spatial.qhull import Delaunay
+        # in principle this works in N dimensions, not just 2
+        assert pos.ndim == 2 and pos.shape[1] == 2
+        # Adding points outside the extremes helps the interpolators
+        extremes = np.array([pos.min(axis=0), pos.max(axis=0)])
+        diffs = extremes[1] - extremes[0]
+        extremes[0] -= diffs
+        extremes[1] += diffs
+        eidx = np.array(list(itertools.product(
+            *([[0] * (pos.shape[1] - 1) + [1]] * pos.shape[1]))))
+        pidx = np.tile(np.arange(pos.shape[1])[np.newaxis], (len(eidx), 1))
+        self.n_extra = pidx.shape[0]
+        outer_pts = extremes[eidx, pidx]
+        pos = np.concatenate((pos, outer_pts))
+        self.tri = Delaunay(pos)
+
+    def set_values(self, v):
+        """Set the values at interpolation points."""
+        # Rbf with thin-plate is what we used to use, but it's slower and
+        # looks about the same:
+        #
+        #     zi = Rbf(x, y, v, function='multiquadric', smooth=0)(xi, yi)
+        #
+        # Eventually we could also do set_values with this class if we want,
+        # see scipy/interpolate/rbf.py, especially the self.nodes one-liner.
+        from scipy.interpolate import CloughTocher2DInterpolator
+        v = np.concatenate((v, np.zeros(self.n_extra)))
+        self.interpolator = CloughTocher2DInterpolator(self.tri, v)
+        return self
+
+    def __call__(self, *args):
+        """Evaluate the interpolator."""
+        return self.interpolator(*args)
 
 
 def _plot_sensors(pos_x, pos_y, sensors, ax):
-    """Aux function"""
-    from matplotlib.patches import Circle
+    """Plot sensors."""
     if sensors is True:
-        for x, y in zip(pos_x, pos_y):
-            ax.add_artist(Circle(xy=(x, y), radius=0.003, color='k'))
+        ax.scatter(pos_x, pos_y, s=0.25, marker='o',
+                   edgecolor=['k'] * len(pos_x), facecolor='none')
     else:
         ax.plot(pos_x, pos_y, sensors)
 
@@ -398,7 +451,7 @@ def plot_topomap(data, pos, vmin=None, vmax=None, cmap=None, sensors=True,
                  mask_params=None, outlines='head', image_mask=None,
                  contours=6, image_interp='bilinear', show=True,
                  head_pos=None, onselect=None):
-    """Plot a topographic map as image
+    """Plot a topographic map as image.
 
     Parameters
     ----------
@@ -423,8 +476,8 @@ def plot_topomap(data, pos, vmin=None, vmax=None, cmap=None, sensors=True,
         otherwise defaults to 'RdBu_r'.
     sensors : bool | str
         Add markers for sensor locations to the plot. Accepts matplotlib plot
-        format string (e.g., 'r+' for red plusses). If True, a circle will be
-        used (via .add_artist). Defaults to True.
+        format string (e.g., 'r+' for red plusses). If True (default), circles
+        will be used.
     res : int
         The resolution of the topomap image (n pixels along each side).
     axes : instance of Axes | None
@@ -462,8 +515,11 @@ def plot_topomap(data, pos, vmin=None, vmax=None, cmap=None, sensors=True,
     image_mask : ndarray of bool, shape (res, res) | None
         The image mask to cover the interpolated surface. If None, it will be
         computed from the outline.
-    contours : int | False | None
+    contours : int | array of float
         The number of contour lines to draw. If 0, no contours will be drawn.
+        If an array, the values represent the levels for the contours. The
+        values are in uV for EEG, fT for magnetometers and fT/m for
+        gradiometers. Defaults to 6.
     image_interp : str
         The image interpolation to be used. All matplotlib options are
         accepted.
@@ -489,7 +545,12 @@ def plot_topomap(data, pos, vmin=None, vmax=None, cmap=None, sensors=True,
     import matplotlib.pyplot as plt
     from matplotlib.widgets import RectangleSelector
 
+    if contours is None or contours is False:
+        warn('Using %s as contours is deprecated and will not be allowed in '
+             '0.16. Use 0 instead.' % str(contours), DeprecationWarning)
+        contours = 0
     data = np.asarray(data)
+    logger.debug('Plotting topomap for data shape %s' % (data.shape,))
 
     if isinstance(pos, Info):  # infer pos from Info object
         picks = _pick_data_channels(pos)  # pick only data channels
@@ -572,7 +633,7 @@ def plot_topomap(data, pos, vmin=None, vmax=None, cmap=None, sensors=True,
     xi = np.linspace(xmin, xmax, res)
     yi = np.linspace(ymin, ymax, res)
     Xi, Yi = np.meshgrid(xi, yi)
-    Zi = _griddata(pos_x, pos_y, data, Xi, Yi)
+    Zi = _GridData(np.array((pos_x, pos_y)).T).set_values(data)(Xi, Yi)
 
     if outlines is None:
         _is_default_outlines = False
@@ -605,11 +666,16 @@ def plot_topomap(data, pos, vmin=None, vmax=None, cmap=None, sensors=True,
     # drawn. To avoid rescalings, we will always draw contours.
     # But if no contours are desired we only draw one and make it invisible .
     no_contours = False
-    if contours in (False, None):
+    if isinstance(contours, (np.ndarray, list)):
+        pass  # contours precomputed
+    elif contours == 0:
         contours, no_contours = 1, True
-    cont = ax.contour(Xi, Yi, Zi, contours, colors='k',
-                      linewidths=linewidth)
-    if no_contours is True:
+    if (Zi == Zi[0, 0]).all():
+        cont = None  # can't make contours for constant-valued functions
+    else:
+        cont = ax.contour(Xi, Yi, Zi, contours, colors='k',
+                          linewidths=linewidth)
+    if no_contours and cont is not None:
         for col in cont.collections:
             col.set_visible(False)
 
@@ -666,9 +732,7 @@ def plot_topomap(data, pos, vmin=None, vmax=None, cmap=None, sensors=True,
 
 
 def _make_image_mask(outlines, pos, res):
-    """Aux function
-    """
-
+    """Make an image mask."""
     mask_ = np.c_[outlines['mask_pos']]
     xmin, xmax = (np.min(np.r_[np.inf, mask_[:, 0]]),
                   np.max(np.r_[-np.inf, mask_[:, 0]]))
@@ -698,35 +762,31 @@ def _make_image_mask(outlines, pos, res):
 
 
 def _inside_contour(pos, contour):
-    """Aux function"""
+    """Check if points are inside a contour."""
     npos = len(pos)
     x, y = pos[:, :2].T
 
     check_mask = np.ones((npos), dtype=bool)
     check_mask[((x < np.min(x)) | (y < np.min(y)) |
                 (x > np.max(x)) | (y > np.max(y)))] = False
-
     critval = 0.1
-    sel = np.where(check_mask)[0]
-    for this_sel in sel:
-        contourx = contour[:, 0] - pos[this_sel, 0]
-        contoury = contour[:, 1] - pos[this_sel, 1]
-        angle = np.arctan2(contoury, contourx)
-        angle = np.unwrap(angle)
-        total = np.sum(np.diff(angle))
-        check_mask[this_sel] = np.abs(total) > critval
-
+    contourx = contour[:, 0] - pos[check_mask, 0][:, np.newaxis]
+    contoury = contour[:, 1] - pos[check_mask, 1][:, np.newaxis]
+    angle = np.arctan2(contoury, contourx)
+    angle = np.unwrap(angle)
+    check_mask[check_mask] = (np.abs(np.sum(np.diff(angle, axis=1), axis=1)) >
+                              critval)
     return check_mask
 
 
 def _plot_ica_topomap(ica, idx=0, ch_type=None, res=64, layout=None,
                       vmin=None, vmax=None, cmap='RdBu_r', colorbar=False,
                       title=None, show=True, outlines='head', contours=6,
-                      image_interp='bilinear', head_pos=None, axes=None):
-    """plot single ica map to axes"""
+                      image_interp='bilinear', head_pos=None, axes=None,
+                      sensors=True):
+    """Plot single ica map to axes."""
     import matplotlib as mpl
     from ..channels import _get_ch_type
-    from ..preprocessing.ica import _get_ica_map
 
     if ica.info is None:
         raise RuntimeError('The ICA\'s measurement info is missing. Please '
@@ -736,7 +796,7 @@ def _plot_ica_topomap(ica, idx=0, ch_type=None, res=64, layout=None,
                          'got %s instead.' % type(axes))
     ch_type = _get_ch_type(ica, ch_type)
 
-    data = _get_ica_map(ica, components=idx)
+    data = ica.get_components()[:, idx]
     data_picks, pos, merge_grads, names, _ = _prepare_topo_plot(
         ica, ch_type, layout)
     pos, outlines = _check_outlines(pos, outlines, head_pos)
@@ -745,21 +805,21 @@ def _plot_ica_topomap(ica, idx=0, ch_type=None, res=64, layout=None,
     else:
         image_mask = None
 
-    data = np.atleast_2d(data)
-    data = data[:, data_picks]
+    data = data[data_picks]
 
     if merge_grads:
         from ..channels.layout import _merge_grad_data
         data = _merge_grad_data(data)
-    axes.set_title('IC #%03d' % idx, fontsize=12)
+    axes.set_title(ica._ica_names[idx], fontsize=12)
     vmin_, vmax_ = _setup_vmin_vmax(data, vmin, vmax)
     im = plot_topomap(data.ravel(), pos, vmin=vmin_, vmax=vmax_,
                       res=res, axes=axes, cmap=cmap, outlines=outlines,
                       image_mask=image_mask, contours=contours,
-                      image_interp=image_interp, show=show)[0]
+                      sensors=sensors, image_interp=image_interp,
+                      show=show)[0]
     if colorbar:
         import matplotlib.pyplot as plt
-        from mpl_toolkits.axes_grid import make_axes_locatable
+        from mpl_toolkits.axes_grid1 import make_axes_locatable
         divider = make_axes_locatable(axes)
         cax = divider.append_axes("right", size="5%", pad=0.05)
         cbar = plt.colorbar(im, cax=cax, format='%3.2f', cmap=cmap)
@@ -818,8 +878,8 @@ def plot_ica_components(ica, picks=None, ch_type=None, res=64,
 
     sensors : bool | str
         Add markers for sensor locations to the plot. Accepts matplotlib
-        plot format string (e.g., 'r+' for red plusses). If True, a circle
-        will be used (via .add_artist). Defaults to True.
+        plot format string (e.g., 'r+' for red plusses). If True (default),
+        circles  will be used.
     colorbar : bool
         Plot a colorbar.
     title : str | None
@@ -837,8 +897,12 @@ def plot_ica_components(ica, picks=None, ch_type=None, res=64,
         masking options, either directly or as a function that returns patches
         (required for multi-axis plots). If None, nothing will be drawn.
         Defaults to 'head'.
-    contours : int | False | None
+    contours : int | array of float
         The number of contour lines to draw. If 0, no contours will be drawn.
+        When an integer, matplotlib ticker locator is used to find suitable
+        values for the contour thresholds (may sometimes be inaccurate, use
+        array for accuracy). If an array, the values represent the levels for
+        the contours. Defaults to 6.
     image_interp : str
         The image interpolation to be used. All matplotlib options are
         accepted.
@@ -858,10 +922,10 @@ def plot_ica_components(ica, picks=None, ch_type=None, res=64,
     fig : instance of matplotlib.pyplot.Figure or list
         The figure object(s).
     """
-    from ..io import _BaseRaw
-    from ..epochs import _BaseEpochs
+    from ..io import BaseRaw
+    from ..epochs import BaseEpochs
     import matplotlib.pyplot as plt
-    from mpl_toolkits.axes_grid import make_axes_locatable
+    from mpl_toolkits.axes_grid1 import make_axes_locatable
     from ..channels import _get_ch_type
 
     if picks is None:  # plot components by sets of 20
@@ -885,10 +949,7 @@ def plot_ica_components(ica, picks=None, ch_type=None, res=64,
         picks = [picks]
     ch_type = _get_ch_type(ica, ch_type)
 
-    if cmap == 'interactive':
-        cmap = ('RdBu_r', True)
-    elif not isinstance(cmap, tuple):
-        cmap = (cmap, False if len(picks) > 2 else True)
+    cmap = _setup_cmap(cmap, n_axes=len(picks))
     data = np.dot(ica.mixing_matrix_[:, picks].T,
                   ica.pca_components_[:ica.n_components_])
 
@@ -916,14 +977,16 @@ def plot_ica_components(ica, picks=None, ch_type=None, res=64,
     if merge_grads:
         from ..channels.layout import _merge_grad_data
     for ii, data_, ax in zip(picks, data, axes):
-        ax.set_title('IC #%03d' % ii, fontsize=12)
+        kwargs = dict(color='gray') if ii in ica.exclude else dict()
+        ax.set_title(ica._ica_names[ii], fontsize=12, **kwargs)
         data_ = _merge_grad_data(data_) if merge_grads else data_
         vmin_, vmax_ = _setup_vmin_vmax(data_, vmin, vmax)
         im = plot_topomap(data_.flatten(), pos, vmin=vmin_, vmax=vmax_,
                           res=res, axes=ax, cmap=cmap[0], outlines=outlines,
                           image_mask=image_mask, contours=contours,
-                          image_interp=image_interp, show=False)[0]
-        im.axes.set_label('IC #%03d' % ii)
+                          image_interp=image_interp, show=False,
+                          sensors=sensors)[0]
+        im.axes.set_label(ica._ica_names[ii])
         if colorbar:
             divider = make_axes_locatable(ax)
             cax = divider.append_axes("right", size="5%", pad=0.05)
@@ -937,12 +1000,12 @@ def plot_ica_components(ica, picks=None, ch_type=None, res=64,
     tight_layout(fig=fig)
     fig.subplots_adjust(top=0.95)
     fig.canvas.draw()
-    if isinstance(inst, (_BaseRaw, _BaseEpochs)):
+    if isinstance(inst, (BaseRaw, BaseEpochs)):
         def onclick(event, ica=ica, inst=inst):
             # check which component to plot
             label = event.inaxes.get_label()
-            if 'IC #' in label:
-                ic = int(label[4:])
+            if label.startswith('ICA'):
+                ic = int(label[-3:])
                 ica.plot_properties(inst, picks=ic, show=True)
         fig.canvas.mpl_connect('button_press_event', onclick)
 
@@ -955,49 +1018,55 @@ def plot_tfr_topomap(tfr, tmin=None, tmax=None, fmin=None, fmax=None,
                      vmin=None, vmax=None, cmap=None, sensors=True,
                      colorbar=True, unit=None, res=64, size=2,
                      cbar_fmt='%1.1e', show_names=False, title=None,
-                     axes=None, show=True, outlines='head', head_pos=None):
-    """Plot topographic maps of specific time-frequency intervals of TFR data
+                     axes=None, show=True, outlines='head', head_pos=None,
+                     contours=6):
+    """Plot topographic maps of specific time-frequency intervals of TFR data.
 
     Parameters
     ----------
-    tfr : AvereageTFR
-        The AvereageTFR object.
+    tfr : AverageTFR
+        The AverageTFR object.
     tmin : None | float
         The first time instant to display. If None the first time point
         available is used.
     tmax : None | float
-        The last time instant to display. If None the last time point
-        available is used.
+        The last time instant to display. If None the last time point available
+        is used.
     fmin : None | float
-        The first frequency to display. If None the first frequency
-        available is used.
+        The first frequency to display. If None the first frequency available
+        is used.
     fmax : None | float
-        The last frequency to display. If None the last frequency
-        available is used.
+        The last frequency to display. If None the last frequency available is
+        used.
     ch_type : 'mag' | 'grad' | 'planar1' | 'planar2' | 'eeg' | None
-        The channel type to plot. For 'grad', the gradiometers are
-        collected in pairs and the RMS for each pair is plotted.
-        If None, then channels are chosen in the order given above.
+        The channel type to plot. For 'grad', the gradiometers are collected in
+        pairs and the RMS for each pair is plotted. If None, then channels are
+        chosen in the order given above.
     baseline : tuple or list of length 2
-        The time interval to apply rescaling / baseline correction.
-        If None do not apply it. If baseline is (a, b)
-        the interval is between "a (s)" and "b (s)".
-        If a is None the beginning of the data is used
-        and if b is None then b is set to the end of the interval.
-        If baseline is equal to (None, None) all the time
-        interval is used.
-    mode : 'logratio' | 'ratio' | 'zscore' | 'mean' | 'percent'
-        Do baseline correction with ratio (power is divided by mean
-        power during baseline) or z-score (power is divided by standard
-        deviation of power during baseline after subtracting the mean,
-        power = [power - mean(power_baseline)] / std(power_baseline))
-        If None, baseline no correction will be performed.
+        The time interval to apply rescaling / baseline correction. If None do
+        not apply it. If baseline is (a, b) the interval is between "a (s)" and
+        "b (s)". If a is None the beginning of the data is used and if b is
+        None then b is set to the end of the interval. If baseline is equal to
+        (None, None) the whole time interval is used.
+    mode : 'mean' | 'ratio' | 'logratio' | 'percent' | 'zscore' | 'zlogratio' | None
+        Perform baseline correction by
+
+          - subtracting the mean baseline power ('mean')
+          - dividing by the mean baseline power ('ratio')
+          - dividing by the mean baseline power and taking the log ('logratio')
+          - subtracting the mean baseline power followed by dividing by the
+            mean baseline power ('percent')
+          - subtracting the mean baseline power and dividing by the standard
+            deviation of the baseline power ('zscore')
+          - dividing by the mean baseline power, taking the log, and dividing
+            by the standard deviation of the baseline power ('zlogratio')
+
+        If None no baseline correction is applied.
     layout : None | Layout
-        Layout instance specifying sensor positions (does not need to
-        be specified for Neuromag data). If possible, the correct layout
-        file is inferred from the data; if no appropriate layout file
-        was found, the layout is automatically generated from the sensor
-        locations.
+        Layout instance specifying sensor positions (does not need to be
+        specified for Neuromag data). If possible, the correct layout file is
+        inferred from the data; if no appropriate layout file was found, the
+        layout is automatically generated from the sensor locations.
     vmin : float | callable | None
         The value specifying the lower bound of the color range.
         If None, and vmax is None, -vmax is used. Else np.min(data) or in case
@@ -1018,9 +1087,8 @@ def plot_tfr_topomap(tfr, tmin=None, tmax=None, fmin=None, fmax=None,
         otherwise defaults to 'RdBu_r'. If 'interactive', translates to
         (None, True).
     sensors : bool | str
-        Add markers for sensor locations to the plot. Accepts matplotlib
-        plot format string (e.g., 'r+' for red plusses). If True, a circle will
-        be used (via .add_artist). Defaults to True.
+        Add markers for sensor locations to the plot. Accepts matplotlib plot
+        format string (e.g., 'r+'). If True (default), circles will be used.
     colorbar : bool
         Plot a colorbar.
     unit : str | None
@@ -1033,13 +1101,13 @@ def plot_tfr_topomap(tfr, tmin=None, tmax=None, fmin=None, fmax=None,
     cbar_fmt : str
         String format for colorbar values.
     show_names : bool | callable
-        If True, show channel names on top of the map. If a callable is
-        passed, channel names will be formatted using the callable; e.g., to
-        delete the prefix 'MEG ' from all channel names, pass the function
-        lambda x: x.replace('MEG ', ''). If `mask` is not None, only
+        If True, show channel names on top of the map. If a callable is passed,
+        channel names will be formatted using the callable; e.g., to delete the
+        prefix 'MEG ' from all channel names, pass the function
+        ``lambda x: x.replace('MEG ', '')``. If `mask` is not None, only
         significant sensors will be shown.
     title : str | None
-        Title. If None (default), no title is displayed.
+        Plot title. If None (default), no title is displayed.
     axes : instance of Axis | None
         The axes to plot to. If None the axes is defined automatically.
     show : bool
@@ -1056,16 +1124,23 @@ def plot_tfr_topomap(tfr, tmin=None, tmax=None, fmin=None, fmax=None,
         (required for multi-axis plots). If None, nothing will be drawn.
         Defaults to 'head'.
     head_pos : dict | None
-        If None (default), the sensors are positioned such that they span
-        the head circle. If dict, can have entries 'center' (tuple) and
-        'scale' (tuple) for what the center and scale of the head should be
-        relative to the electrode locations.
+        If None (default), the sensors are positioned such that they span the
+        head circle. If dict, can have entries 'center' (tuple) and 'scale'
+        (tuple) for what the center and scale of the head should be relative to
+        the electrode locations.
+    contours : int | array of float
+        The number of contour lines to draw. If 0, no contours will be drawn.
+        When an integer, matplotlib ticker locator is used to find suitable
+        values for the contour thresholds (may sometimes be inaccurate, use
+        array for accuracy). If an array, the values represent the levels for
+        the contours. If colorbar=True, the ticks in colorbar correspond to the
+        contour levels. Defaults to 6.
 
     Returns
     -------
     fig : matplotlib.figure.Figure
         The figure containing the topography.
-    """
+    """  # noqa: E501
     from ..channels import _get_ch_type
     ch_type = _get_ch_type(tfr, ch_type)
     import matplotlib.pyplot as plt
@@ -1104,13 +1179,10 @@ def plot_tfr_topomap(tfr, tmin=None, tmax=None, fmin=None, fmax=None,
 
     norm = False if np.min(data) < 0 else True
     vmin, vmax = _setup_vmin_vmax(data, vmin, vmax, norm)
-    if cmap is None or cmap == 'interactive':
-        cmap = ('Reds', True) if norm else ('RdBu_r', True)
-    elif not isinstance(cmap, tuple):
-        cmap = (cmap, True)
+    cmap = _setup_cmap(cmap, norm=norm)
 
     if axes is None:
-        fig = plt.figure()
+        fig = plt.figure(figsize=(size, size))
         ax = fig.gca()
     else:
         fig = axes.figure
@@ -1118,6 +1190,10 @@ def plot_tfr_topomap(tfr, tmin=None, tmax=None, fmin=None, fmax=None,
 
     _hide_frame(ax)
 
+    locator = None
+    if not isinstance(contours, (list, np.ndarray)):
+        locator, contours = _set_contour_locator(vmin, vmax, contours)
+
     if title is not None:
         ax.set_title(title)
     fig_wrapper = list()
@@ -1128,16 +1204,23 @@ def plot_tfr_topomap(tfr, tmin=None, tmax=None, fmin=None, fmax=None,
 
     im, _ = plot_topomap(data[:, 0], pos, vmin=vmin, vmax=vmax,
                          axes=ax, cmap=cmap[0], image_interp='bilinear',
-                         contours=False, names=names, show_names=show_names,
-                         show=False, onselect=selection_callback)
+                         contours=contours, names=names, show_names=show_names,
+                         show=False, onselect=selection_callback,
+                         sensors=sensors, res=res, head_pos=head_pos,
+                         outlines=outlines)
 
     if colorbar:
+        from matplotlib import ticker
         divider = make_axes_locatable(ax)
         cax = divider.append_axes("right", size="5%", pad=0.05)
         cbar = plt.colorbar(im, cax=cax, format=cbar_fmt, cmap=cmap[0])
-        cbar.set_ticks((vmin, vmax))
+        if locator is None:
+            locator = ticker.MaxNLocator(nbins=5)
+        cbar.locator = locator
+        cbar.update_ticks()
         cbar.ax.tick_params(labelsize=12)
-        cbar.ax.set_title('AU')
+        unit = _handle_default('units', unit)['misc']
+        cbar.ax.set_title(unit)
         if cmap[1]:
             ax.CB = DraggableColorbar(cbar, im)
 
@@ -1147,25 +1230,27 @@ def plot_tfr_topomap(tfr, tmin=None, tmax=None, fmin=None, fmax=None,
 
 def plot_evoked_topomap(evoked, times="auto", ch_type=None, layout=None,
                         vmin=None, vmax=None, cmap=None, sensors=True,
-                        colorbar=True, scale=None, scale_time=1e3, unit=None,
-                        res=64, size=1, cbar_fmt='%3.1f',
+                        colorbar=None, scalings=None, scaling_time=1e3,
+                        units=None, res=64, size=1, cbar_fmt='%3.1f',
                         time_format='%01d ms', proj=False, show=True,
                         show_names=False, title=None, mask=None,
                         mask_params=None, outlines='head', contours=6,
                         image_interp='bilinear', average=None, head_pos=None,
-                        axes=None):
-    """Plot topographic maps of specific time points of evoked data
+                        axes=None, scale=None, scale_time=None, unit=None):
+    """Plot topographic maps of specific time points of evoked data.
 
     Parameters
     ----------
     evoked : Evoked
         The Evoked object.
-    times : float | array of floats | "auto" | "peaks".
+    times : float | array of floats | "auto" | "peaks" | "interactive"
         The time point(s) to plot. If "auto", the number of ``axes`` determines
-        the amount of time point(s). If ``axes`` is also None, 10 topographies
-        will be shown with a regular time spacing between the first and last
-        time instant. If "peaks", finds time points automatically by checking
-        for local maxima in global field power.
+        the amount of time point(s). If ``axes`` is also None, at most 10
+        topographies will be shown with a regular time spacing between the
+        first and last time instant. If "peaks", finds time points
+        automatically by checking for local maxima in global field power. If
+        "interactive", the time can be set interactively at run-time by using a
+        slider.
     ch_type : 'mag' | 'grad' | 'planar1' | 'planar2' | 'eeg' | None
         The channel type to plot. For 'grad', the gradiometers are collected in
         pairs and the RMS for each pair is plotted.
@@ -1188,27 +1273,32 @@ def plot_evoked_topomap(evoked, times="auto", ch_type=None, layout=None,
         use and the second value is a boolean defining interactivity. In
         interactive mode the colors are adjustable by clicking and dragging the
         colorbar with left and right mouse button. Left mouse button moves the
-        scale up and down and right mouse button adjusts the range. Hitting
-        space bar resets the range. Up and down arrows can be used to change
-        the colormap. If None (default), 'Reds' is used for all positive data,
+        scale up and down and right mouse button adjusts the range (zoom).
+        The mouse scroll can also be used to adjust the range. Hitting space
+        bar resets the range. Up and down arrows can be used to change the
+        colormap. If None (default), 'Reds' is used for all positive data,
         otherwise defaults to 'RdBu_r'. If 'interactive', translates to
         (None, True).
 
         .. warning::  Interactive mode works smoothly only for a small amount
-            of topomaps.
+            of topomaps. Interactive mode is disabled by default for more than
+            2 topomaps.
 
     sensors : bool | str
         Add markers for sensor locations to the plot. Accepts matplotlib plot
-        format string (e.g., 'r+' for red plusses). If True, a circle will be
-        used (via .add_artist). Defaults to True.
-    colorbar : bool
-        Plot a colorbar.
-    scale : dict | float | None
-        Scale the data for plotting. If None, defaults to 1e6 for eeg, 1e13
-        for grad and 1e15 for mag.
-    scale_time : float | None
+        format string (e.g., 'r+' for red plusses). If True (default),
+        circles will be used.
+    colorbar : bool | None
+        Plot a colorbar in the rightmost column of the figure.
+        None (default) is the same as True, but emits a warning if custom
+        ``axes`` are provided to remind the user that the colorbar will
+        occupy the last :class:`matplotlib.axes.Axes` instance.
+    scalings : dict | float | None
+        The scalings of the channel types to be applied for plotting.
+        If None, defaults to ``dict(eeg=1e6, grad=1e13, mag=1e15)``.
+    scaling_time : float | None
         Scale the time labels. Defaults to 1e3 (ms).
-    unit : dict | str | None
+    units : dict | str | None
         The unit of the channel type used for colorbar label. If
         scale is None the unit is automatically determined.
     res : int
@@ -1254,8 +1344,14 @@ def plot_evoked_topomap(evoked, times="auto", ch_type=None, layout=None,
         masking options, either directly or as a function that returns patches
         (required for multi-axis plots). If None, nothing will be drawn.
         Defaults to 'head'.
-    contours : int | False | None
+    contours : int | array of float
         The number of contour lines to draw. If 0, no contours will be drawn.
+        When an integer, matplotlib ticker locator is used to find suitable
+        values for the contour thresholds (may sometimes be inaccurate, use
+        array for accuracy). If an array, the values represent the levels for
+        the contours. The values are in uV for EEG, fT for magnetometers and
+        fT/m for gradiometers. If colorbar=True, the ticks in colorbar
+        correspond to the contour levels. Defaults to 6.
     image_interp : str
         The image interpolation to be used. All matplotlib options are
         accepted.
@@ -1281,10 +1377,23 @@ def plot_evoked_topomap(evoked, times="auto", ch_type=None, layout=None,
        The figure.
     """
     from ..channels import _get_ch_type
+    from ..channels.layout import _merge_grad_data
     ch_type = _get_ch_type(evoked, ch_type)
     import matplotlib.pyplot as plt
-    from mpl_toolkits.axes_grid1 import make_axes_locatable  # noqa
-
+    from matplotlib import gridspec
+    from matplotlib.widgets import Slider
+    from mpl_toolkits.axes_grid1 import make_axes_locatable  # noqa: F401
+    scalings = _scale_dep(scalings, scale, 'scalings', 'scale')
+    scaling_time = _scale_dep(scaling_time, scale_time,
+                              'scaling_time', 'scale_time')
+    units = _scale_dep(units, unit, 'units', 'unit')
+    del scale, scale_time, unit
+
+    if colorbar is None:
+        colorbar = True
+        colorbar_warn = True
+    else:
+        colorbar_warn = False
     mask_params = _handle_default('mask_params', mask_params)
     mask_params['markersize'] *= size / 2.
     mask_params['markeredgewidth'] *= size / 2.
@@ -1301,6 +1410,8 @@ def plot_evoked_topomap(evoked, times="auto", ch_type=None, layout=None,
     evoked = evoked.copy().pick_channels(
         [evoked.ch_names[pick] for pick in picks])
 
+    with warnings.catch_warnings(record=True):  # elementwise comparison
+        interactive = times == 'interactive'
     if axes is not None:
         if isinstance(axes, plt.Axes):
             axes = [axes]
@@ -1316,15 +1427,25 @@ def plot_evoked_topomap(evoked, times="auto", ch_type=None, layout=None,
     nax = n_times + bool(colorbar)
     width = size * nax
     height = size + max(0, 0.1 * (4 - size)) + bool(title) * 0.5
+
+    cols = n_times + 1 if colorbar else n_times  # room for the colorbar
+    if interactive:
+        if axes is not None:
+            raise ValueError("User provided axes not allowed when "
+                             "times='interactive'.")
+        height_ratios = [5, 1]
+        rows = 2
+        g_kwargs = {'left': 0.2, 'right': 1., 'bottom': 0.05, 'top': 0.95}
+    else:
+        rows, height_ratios, g_kwargs = 1, None, {}
+
+    gs = gridspec.GridSpec(rows, cols, height_ratios=height_ratios, **g_kwargs)
     if axes is None:
-        plt.figure(figsize=(width, height))
+        figure_nobar(figsize=(width * 1.5, height * 1.5))
         axes = list()
         for ax_idx in range(len(times)):
-            if colorbar:  # Make room for the colorbar
-                axes.append(plt.subplot(1, n_times + 1, ax_idx + 1))
-            else:
-                axes.append(plt.subplot(1, n_times, ax_idx + 1))
-    elif colorbar:
+            axes.append(plt.subplot(gs[ax_idx]))
+    elif colorbar and colorbar_warn:
         warn('Colorbar is drawn to the rightmost column of the figure. Be '
              'sure to provide enough space for it or turn it off with '
              'colorbar=False.')
@@ -1336,8 +1457,8 @@ def plot_evoked_topomap(evoked, times="auto", ch_type=None, layout=None,
     else:
         key = ch_type
 
-    scale = _handle_default('scalings', scale)[key]
-    unit = _handle_default('units', unit)[key]
+    scaling = _handle_default('scalings', scalings)[key]
+    unit = _handle_default('units', units)[key]
 
     if not show_names:
         names = None
@@ -1371,16 +1492,18 @@ def plot_evoked_topomap(evoked, times="auto", ch_type=None, layout=None,
         raise ValueError('The average parameter must be None or a float.'
                          'Check your input.')
 
-    data *= scale
+    data *= scaling
     if merge_grads:
-        from ..channels.layout import _merge_grad_data
         data = _merge_grad_data(data)
 
     images, contours_ = [], []
 
     if mask is not None:
-        _picks = picks[::2 if ch_type not in ['mag', 'eeg'] else 1]
-        mask_ = mask[np.ix_(_picks, time_idx)]
+        if ch_type == 'grad':
+            mask_ = (mask[np.ix_(picks[::2], time_idx)] |
+                     mask[np.ix_(picks[1::2], time_idx)])
+        else:  # mag, eeg, planar1, planar2
+            mask_ = mask[np.ix_(picks, time_idx)]
 
     pos, outlines = _check_outlines(pos, outlines, head_pos)
     if outlines is not None:
@@ -1392,26 +1515,41 @@ def plot_evoked_topomap(evoked, times="auto", ch_type=None, layout=None,
              for i in range(len(times))]
     vmin = np.min(vlims)
     vmax = np.max(vlims)
-    if cmap == 'interactive':
-        cmap = (None, True)
-    elif not isinstance(cmap, tuple):
-        cmap = (cmap, False if len(times) > 2 else True)
+    cmap = _setup_cmap(cmap, n_axes=len(times), norm=vmin >= 0)
+
+    if not isinstance(contours, (list, np.ndarray)):
+        _, contours = _set_contour_locator(vmin, vmax, contours)
+
+    kwargs = dict(vmin=vmin, vmax=vmax, sensors=sensors, res=res, names=names,
+                  show_names=show_names, cmap=cmap[0], mask_params=mask_params,
+                  outlines=outlines, image_mask=image_mask, contours=contours,
+                  image_interp=image_interp, show=False)
     for idx, time in enumerate(times):
-        tp, cn = plot_topomap(data[:, idx], pos, vmin=vmin, vmax=vmax,
-                              sensors=sensors, res=res, names=names,
-                              show_names=show_names, cmap=cmap[0],
+        tp, cn = plot_topomap(data[:, idx], pos, axes=axes[idx],
                               mask=mask_[:, idx] if mask is not None else None,
-                              mask_params=mask_params, axes=axes[idx],
-                              outlines=outlines, image_mask=image_mask,
-                              contours=contours, image_interp=image_interp,
-                              show=False)
+                              **kwargs)
 
         images.append(tp)
         if cn is not None:
             contours_.append(cn)
         if time_format is not None:
-            axes[idx].set_title(time_format % (time * scale_time))
-
+            axes[idx].set_title(time_format % (time * scaling_time))
+
+    if interactive:
+        axes.append(plt.subplot(gs[2]))
+        slider = Slider(axes[-1], 'Time', evoked.times[0], evoked.times[-1],
+                        times[0], valfmt='%1.2fs')
+        slider.vline.remove()  # remove initial point indicator
+        func = _merge_grad_data if merge_grads else lambda x: x
+        changed_callback = partial(_slider_changed, ax=axes[0],
+                                   data=evoked.data, times=evoked.times,
+                                   pos=pos, scaling=scaling, func=func,
+                                   time_format=time_format,
+                                   scaling_time=scaling_time, kwargs=kwargs)
+        slider.on_changed(changed_callback)
+        ts = np.tile(evoked.times, len(evoked.data)).reshape(evoked.data.shape)
+        axes[-1].plot(ts, evoked.data, color='k')
+        axes[-1].slider = slider
     if title is not None:
         plt.suptitle(title, verticalalignment='top', size='x-large')
 
@@ -1420,17 +1558,12 @@ def plot_evoked_topomap(evoked, times="auto", ch_type=None, layout=None,
         n_fig_axes = max(nax, len(fig.get_axes()))
         cax = plt.subplot(1, n_fig_axes + 1, n_fig_axes + 1)
         # resize the colorbar (by default the color fills the whole axes)
-        cpos = cax.get_position()
-        if size <= 1:
-            cpos.x0 = 1 - (.7 + .1 / size) / n_fig_axes
-        cpos.x1 = cpos.x0 + .1 / n_fig_axes
-        cpos.y0 = .2
-        cpos.y1 = .7
-        cax.set_position(cpos)
+        _resize_cbar(cax, n_fig_axes, size)
         if unit is not None:
             cax.set_title(unit)
         cbar = fig.colorbar(images[-1], ax=cax, cax=cax, format=cbar_fmt)
-        cbar.set_ticks([cbar.vmin, 0, cbar.vmax])
+        cbar.set_ticks(cn.levels)
+        cbar.ax.tick_params(labelsize=7)
         if cmap[1]:
             for im in images:
                 im.axes.CB = DraggableColorbar(cbar, im)
@@ -1439,19 +1572,45 @@ def plot_evoked_topomap(evoked, times="auto", ch_type=None, layout=None,
         _check_delayed_ssp(evoked)
         params = dict(evoked=evoked, fig=fig, projs=evoked.info['projs'],
                       picks=picks, images=images, contours=contours_,
-                      time_idx=time_idx, scale=scale, merge_grads=merge_grads,
+                      time_idx=time_idx, merge_grads=merge_grads,
                       res=res, pos=pos, image_mask=image_mask,
-                      plot_update_proj_callback=_plot_update_evoked_topomap)
+                      plot_update_proj_callback=_plot_update_evoked_topomap,
+                      scale=scaling)
         _draw_proj_checkbox(None, params)
 
     plt_show(show)
     return fig
 
 
+def _resize_cbar(cax, n_fig_axes, size=1):
+    """Resize colorbar."""
+    cpos = cax.get_position()
+    if size <= 1:
+        cpos.x0 = 1 - (.7 + .1 / size) / n_fig_axes
+    cpos.x1 = cpos.x0 + .1 / n_fig_axes
+    cpos.y0 = .2
+    cpos.y1 = .7
+    cax.set_position(cpos)
+
+
+def _slider_changed(val, ax, data, times, pos, scaling, func, time_format,
+                    scaling_time, kwargs):
+    """Handle selection in interactive topomap."""
+    idx = np.argmin(np.abs(times - val))
+    data = func(data[:, idx]).ravel() * scaling
+    ax.clear()
+    im, _ = plot_topomap(data, pos, axes=ax, **kwargs)
+    if hasattr(ax, 'CB'):
+        ax.CB.mappable = im
+        _resize_cbar(ax.CB.cbar.ax, 2)
+    if time_format is not None:
+        ax.set_title(time_format % (val * scaling_time))
+
+
 def _plot_topomap_multi_cbar(data, pos, ax, title=None, unit=None, vmin=None,
                              vmax=None, cmap=None, outlines='head',
                              colorbar=False, cbar_fmt='%3.3f'):
-    """Aux Function"""
+    """Plot topomap multi cbar."""
     import matplotlib.pyplot as plt
     from mpl_toolkits.axes_grid1 import make_axes_locatable
 
@@ -1459,14 +1618,11 @@ def _plot_topomap_multi_cbar(data, pos, ax, title=None, unit=None, vmin=None,
     vmin = np.min(data) if vmin is None else vmin
     vmax = np.max(data) if vmax is None else vmax
 
-    if cmap == 'interactive':
-        cmap = (None, True)
-    elif not isinstance(cmap, tuple):
-        cmap = (cmap, True)
+    cmap = _setup_cmap(cmap)
     if title is not None:
         ax.set_title(title, fontsize=10)
     im, _ = plot_topomap(data, pos, vmin=vmin, vmax=vmax, axes=ax,
-                         cmap=cmap[0], image_interp='bilinear', contours=False,
+                         cmap=cmap[0], image_interp='bilinear', contours=0,
                          outlines=outlines, show=False)
 
     if colorbar is True:
@@ -1490,7 +1646,7 @@ def plot_epochs_psd_topomap(epochs, bands=None, vmin=None, vmax=None,
                             normalize=False, cbar_fmt='%0.3f',
                             outlines='head', axes=None, show=True,
                             verbose=None):
-    """Plot the topomap of the power spectral density across epochs
+    """Plot the topomap of the power spectral density across epochs.
 
     Parameters
     ----------
@@ -1582,7 +1738,8 @@ def plot_epochs_psd_topomap(epochs, bands=None, vmin=None, vmax=None,
     show : bool
         Show figure if True.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -1616,7 +1773,7 @@ def plot_psds_topomap(
         psds, freqs, pos, agg_fun=None, vmin=None, vmax=None, bands=None,
         cmap=None, dB=True, normalize=False, cbar_fmt='%0.3f', outlines='head',
         axes=None, show=True):
-    """Plot spatial maps of PSDs
+    """Plot spatial maps of PSDs.
 
     Parameters
     ----------
@@ -1685,7 +1842,6 @@ def plot_psds_topomap(
     fig : instance of matplotlib figure
         Figure distributing one image per channel across sensor topography.
     """
-
     import matplotlib.pyplot as plt
 
     if bands is None:
@@ -1729,13 +1885,16 @@ def plot_psds_topomap(
     return fig
 
 
-def plot_layout(layout, show=True):
+def plot_layout(layout, picks=None, show=True):
     """Plot the sensor positions.
 
     Parameters
     ----------
     layout : None | Layout
         Layout instance specifying sensor positions.
+    picks : array-like
+        Indices of the channels to show. If None (default), all the channels
+        are shown.
     show : bool
         Show figure if True. Defaults to True.
 
@@ -1749,41 +1908,47 @@ def plot_layout(layout, show=True):
     .. versionadded:: 0.12.0
     """
     import matplotlib.pyplot as plt
-    fig = plt.figure()
+    fig = plt.figure(figsize=(max(plt.rcParams['figure.figsize']),) * 2)
     ax = fig.add_subplot(111)
     fig.subplots_adjust(left=0, bottom=0, right=1, top=1, wspace=None,
                         hspace=None)
-    ax.set_xticks([])
-    ax.set_yticks([])
+    ax.set(xticks=[], yticks=[], aspect='equal')
     pos = [(p[0] + p[2] / 2., p[1] + p[3] / 2.) for p in layout.pos]
     pos, outlines = _check_outlines(pos, 'head')
     _draw_outlines(ax, outlines)
-    for ii, (this_pos, ch_id) in enumerate(zip(pos, layout.names)):
+    if picks is None:
+        names = layout.names
+    else:
+        pos = pos[picks]
+        names = np.array(layout.names)[picks]
+    for ii, (this_pos, ch_id) in enumerate(zip(pos, names)):
         ax.annotate(ch_id, xy=this_pos[:2], horizontalalignment='center',
                     verticalalignment='center', size='x-small')
+    tight_layout(fig=fig, pad=0, w_pad=0, h_pad=0)
     plt_show(show)
     return fig
 
 
 def _onselect(eclick, erelease, tfr, pos, ch_type, itmin, itmax, ifmin, ifmax,
               cmap, fig, layout=None):
-    """Callback called from topomap for drawing average tfr over channels."""
+    """Handle drawing average tfr over channels called from topomap."""
     import matplotlib.pyplot as plt
+    from matplotlib.collections import PathCollection
     pos, _ = _check_outlines(pos, outlines='head', head_pos=None)
     ax = eclick.inaxes
     xmin = min(eclick.xdata, erelease.xdata)
     xmax = max(eclick.xdata, erelease.xdata)
     ymin = min(eclick.ydata, erelease.ydata)
     ymax = max(eclick.ydata, erelease.ydata)
-    indices = [i for i in range(len(pos)) if pos[i][0] < xmax and
-               pos[i][0] > xmin and pos[i][1] < ymax and pos[i][1] > ymin]
-    for idx, circle in enumerate(ax.artists):
-        if idx in indices:
-            circle.set_color('r')
-        else:
-            circle.set_color('black')
-    plt.gcf().canvas.draw()
-    if not indices:
+    indices = ((pos[:, 0] < xmax) & (pos[:, 0] > xmin) &
+               (pos[:, 1] < ymax) & (pos[:, 1] > ymin))
+    colors = ['r' if ii else 'k' for ii in indices]
+    indices = np.where(indices)[0]
+    for collection in ax.collections:
+        if isinstance(collection, PathCollection):  # this is our "scatter"
+            collection.set_color(colors)
+    ax.figure.canvas.draw()
+    if len(indices) == 0:
         return
     data = tfr.data
     if ch_type == 'mag':
@@ -1837,7 +2002,7 @@ def _onselect(eclick, erelease, tfr, pos, ch_type, itmin, itmax, ifmin, ifmax,
 
 
 def _prepare_topomap(pos, ax):
-    """Helper for preparing the topomap."""
+    """Prepare the topomap."""
     pos_x = pos[:, 0]
     pos_y = pos[:, 1]
     _hide_frame(ax)
@@ -1848,7 +2013,7 @@ def _prepare_topomap(pos, ax):
 
 
 def _hide_frame(ax):
-    """Helper to hide axis frame for topomaps."""
+    """Hide axis frame for topomaps."""
     ax.get_yticks()
     ax.xaxis.set_ticks([])
     ax.yaxis.set_ticks([])
@@ -1879,8 +2044,6 @@ def _init_anim(ax, ax_line, ax_cbar, params, merge_grads):
     vmin, vmax = _setup_vmin_vmax(data, None, None, norm)
 
     pos, outlines = _check_outlines(params['pos'], 'head', None)
-    pos_x = pos[:, 0]
-    pos_y = pos[:, 1]
 
     _hide_frame(ax)
     xlim = np.inf, -np.inf,
@@ -1897,9 +2060,9 @@ def _init_anim(ax, ax_line, ax_cbar, params, merge_grads):
     Xi, Yi = np.meshgrid(xi, yi)
     params['Zis'] = list()
 
+    interp = _GridData(pos)
     for frame in params['frames']:
-        Zi = _griddata(pos_x, pos_y, data[:, frame], Xi, Yi)
-        params['Zis'].append(Zi)
+        params['Zis'].append(interp.set_values(data[:, frame])(Xi, Yi))
     Zi = params['Zis'][0]
     zi_min = np.min(params['Zis'])
     zi_max = np.max(params['Zis'])
@@ -1936,7 +2099,7 @@ def _init_anim(ax, ax_line, ax_cbar, params, merge_grads):
 
 
 def _animate(frame, ax, ax_line, params):
-    """Updates animated topomap."""
+    """Update animated topomap."""
     if params['pause']:
         frame = params['frame']
     time_idx = params['frames'][frame]
@@ -1987,12 +2150,12 @@ def _animate(frame, ax, ax_line, params):
 
 
 def _pause_anim(event, params):
-    """Function for pausing and continuing the animation on mouse click"""
+    """Pause or continue the animation on mouse click."""
     params['pause'] = not params['pause']
 
 
 def _key_press(event, params):
-    """Function for handling key presses for the animation."""
+    """Handle key presses for the animation."""
     if event.key == 'left':
         params['pause'] = True
         params['frame'] = max(params['frame'] - 1, 0)
@@ -2003,9 +2166,11 @@ def _key_press(event, params):
 
 def _topomap_animation(evoked, ch_type='mag', times=None, frame_rate=None,
                        butterfly=False, blit=True, show=True):
-    """Make animation of evoked data as topomap timeseries. Animation can be
-    paused/resumed with left mouse button. Left and right arrow keys can be
-    used to move backward or forward in time.
+    """Make animation of evoked data as topomap timeseries.
+
+    Animation can be paused/resumed with left mouse button.
+    Left and right arrow keys can be used to move backward or forward in
+    time.
 
     Parameters
     ----------
@@ -2060,9 +2225,8 @@ def _topomap_animation(evoked, ch_type='mag', times=None, frame_rate=None,
     frames = [np.abs(evoked.times - time).argmin() for time in times]
 
     blit = False if plt.get_backend() == 'MacOSX' else blit
-    picks, pos, merge_grads, _, ch_type = _prepare_topo_plot(evoked,
-                                                             ch_type=ch_type,
-                                                             layout=None)
+    picks, pos, merge_grads, _, ch_type = _prepare_topo_plot(
+        evoked, ch_type=ch_type, layout=None)
     data = evoked.data[picks, :]
     data *= _handle_default('scalings')[ch_type]
 
@@ -2075,7 +2239,7 @@ def _topomap_animation(evoked, ch_type='mag', times=None, frame_rate=None,
                                                         evoked.times[-1]))
     else:
         ax_line = None
-    if isinstance(frames, int):
+    if isinstance(frames, Integral):
         frames = np.linspace(0, len(evoked.times) - 1, frames).astype(int)
     ax_cbar = plt.axes([0.85, 0.1, 0.05, 0.8])
     ax_cbar.set_title(_handle_default('units')[ch_type], fontsize=10)
@@ -2103,3 +2267,15 @@ def _topomap_animation(evoked, ch_type='mag', times=None, frame_rate=None,
         params['line'].remove()
 
     return fig, anim
+
+
+def _set_contour_locator(vmin, vmax, contours):
+    """Set correct contour levels."""
+    locator = None
+    if isinstance(contours, Integral) and contours > 0:
+        from matplotlib import ticker
+        # nbins = ticks - 1, since 2 of the ticks are vmin and vmax, the
+        # correct number of bins is equal to contours + 1.
+        locator = ticker.MaxNLocator(nbins=contours + 1)
+        contours = locator.tick_values(vmin, vmax)
+    return locator, contours
diff --git a/mne/viz/utils.py b/mne/viz/utils.py
index 1e740dc..5465b6f 100644
--- a/mne/viz/utils.py
+++ b/mne/viz/utils.py
@@ -1,5 +1,4 @@
-"""Utility functions for plotting M/EEG data
-"""
+"""Utility functions for plotting M/EEG data."""
 from __future__ import print_function
 
 # Authors: Alexandre Gramfort <alexandre.gramfort at telecom-paristech.fr>
@@ -18,16 +17,20 @@ import tempfile
 import numpy as np
 from copy import deepcopy
 from distutils.version import LooseVersion
+from itertools import cycle
+from warnings import catch_warnings
 
 from ..channels.layout import _auto_topomap_coords
 from ..channels.channels import _contains_ch_type
 from ..defaults import _handle_default
 from ..io import show_fiff, Info
-from ..io.pick import channel_type, channel_indices_by_type, pick_channels
+from ..io.pick import (channel_type, channel_indices_by_type, pick_channels,
+                       _pick_data_channels)
 from ..utils import verbose, set_config, warn
 from ..externals.six import string_types
 from ..selection import (read_selection, _SELECTIONS, _EEG_SELECTIONS,
                          _divide_to_regions)
+from ..annotations import Annotations, _sync_onset
 
 
 COLORS = ['b', 'g', 'r', 'c', 'm', 'y', 'k', '#473C8B', '#458B74',
@@ -35,7 +38,7 @@ COLORS = ['b', 'g', 'r', 'c', 'm', 'y', 'k', '#473C8B', '#458B74',
 
 
 def _setup_vmin_vmax(data, vmin, vmax, norm=False):
-    """Aux function to handle vmin and vmax parameters"""
+    """Handle vmin and vmax parameters."""
     if vmax is None and vmin is None:
         vmax = np.abs(data).max()
         if norm:
@@ -57,18 +60,29 @@ def _setup_vmin_vmax(data, vmin, vmax, norm=False):
     return vmin, vmax
 
 
-def plt_show(show=True, **kwargs):
-    """Helper to show a figure while suppressing warnings"""
+def plt_show(show=True, fig=None, **kwargs):
+    """Show a figure while suppressing warnings.
+
+    Parameters
+    ----------
+    show : bool
+        Show the figure.
+    fig : instance of Figure | None
+        If non-None, use fig.show().
+    **kwargs : dict
+        Extra arguments for :func:`matplotlib.pyplot.show`.
+    """
     import matplotlib
     import matplotlib.pyplot as plt
     if show and matplotlib.get_backend() != 'agg':
-        plt.show(**kwargs)
+        (fig or plt).show(**kwargs)
 
 
 def tight_layout(pad=1.2, h_pad=None, w_pad=None, fig=None):
-    """ Adjust subplot parameters to give specified padding.
+    """Adjust subplot parameters to give specified padding.
 
-    Note. For plotting please use this function instead of plt.tight_layout
+    .. note:: For plotting please use this function instead of
+              ``plt.tight_layout``.
 
     Parameters
     ----------
@@ -89,18 +103,24 @@ def tight_layout(pad=1.2, h_pad=None, w_pad=None, fig=None):
 
     fig.canvas.draw()
     try:  # see https://github.com/matplotlib/matplotlib/issues/2654
-        fig.tight_layout(pad=pad, h_pad=h_pad, w_pad=w_pad)
+        with catch_warnings(record=True) as ws:
+            fig.tight_layout(pad=pad, h_pad=h_pad, w_pad=w_pad)
     except Exception:
         try:
-            fig.set_tight_layout(dict(pad=pad, h_pad=h_pad, w_pad=w_pad))
+            with catch_warnings(record=True) as ws:
+                fig.set_tight_layout(dict(pad=pad, h_pad=h_pad, w_pad=w_pad))
         except Exception:
             warn('Matplotlib function "tight_layout" is not supported.'
                  ' Skipping subplot adjustment.')
+            return
+    for w in ws:
+        w = str(w.message) if hasattr(w, 'message') else w.get_message()
+        if not w.startswith('This figure includes Axes'):
+            warn(w)
 
 
 def _check_delayed_ssp(container):
-    """ Aux function to be used for interactive SSP selection
-    """
+    """Handle interactive SSP selection."""
     if container.proj is True or\
        all(p['active'] for p in container.info['projs']):
         raise RuntimeError('Projs are already applied. Please initialize'
@@ -110,7 +130,7 @@ def _check_delayed_ssp(container):
 
 
 def _validate_if_list_of_axes(axes, obligatory_len=None):
-    """ Helper function that validates whether input is a list/array of axes"""
+    """Validate whether input is a list/array of axes."""
     import matplotlib as mpl
     if obligatory_len is not None and not isinstance(obligatory_len, int):
         raise ValueError('obligatory_len must be None or int, got %d',
@@ -136,7 +156,7 @@ def _validate_if_list_of_axes(axes, obligatory_len=None):
 
 
 def mne_analyze_colormap(limits=[5, 10, 15], format='mayavi'):
-    """Return a colormap similar to that used by mne_analyze
+    """Return a colormap similar to that used by mne_analyze.
 
     Parameters
     ----------
@@ -224,7 +244,7 @@ def mne_analyze_colormap(limits=[5, 10, 15], format='mayavi'):
 
 
 def _toggle_options(event, params):
-    """Toggle options (projectors) dialog"""
+    """Toggle options (projectors) dialog."""
     import matplotlib.pyplot as plt
     if len(params['projs']) > 0:
         if params['fig_proj'] is None:
@@ -237,7 +257,7 @@ def _toggle_options(event, params):
 
 
 def _toggle_proj(event, params):
-    """Operation to perform when proj boxes clicked"""
+    """Perform operations when proj boxes clicked."""
     # read options if possible
     if 'proj_checks' in params:
         bools = [x[0].get_visible() for x in params['proj_checks'].lines]
@@ -246,7 +266,8 @@ def _toggle_proj(event, params):
             if not b and p['active']:
                 bools[bi] = True
     else:
-        bools = [True] * len(params['projs'])
+        proj = params.get('apply_proj', True)
+        bools = [proj] * len(params['projs'])
 
     compute_proj = False
     if 'proj_bools' not in params:
@@ -260,13 +281,13 @@ def _toggle_proj(event, params):
 
 
 def _get_help_text(params):
-    """Aux function for customizing help dialogs text."""
+    """Customize help dialogs text."""
     text, text2 = list(), list()
 
-    text.append(u'\u2190 : \n')
-    text.append(u'\u2192 : \n')
-    text.append(u'\u2193 : \n')
-    text.append(u'\u2191 : \n')
+    text.append(u'\u2190 : \n')  # left arrow
+    text.append(u'\u2192 : \n')  # right arrow
+    text.append(u'\u2193 : \n')  # down arrow
+    text.append(u'\u2191 : \n')  # up arrow
     text.append(u'- : \n')
     text.append(u'+ or = : \n')
     text.append(u'Home : \n')
@@ -307,9 +328,13 @@ def _get_help_text(params):
             text.append(u'click channel name :\n')
             text2.insert(2, 'Navigate channels down\n')
             text2.insert(3, 'Navigate channels up\n')
+            text.insert(6, u'a : \n')
+            text2.insert(6, 'Toggle annotation mode\n')
+            text.insert(7, u'b : \n')
+            text2.insert(7, 'Toggle butterfly plot on/off\n')
             if 'fig_selection' not in params:
-                text2.insert(8, 'Reduce the number of channels per view\n')
-                text2.insert(9, 'Increase the number of channels per view\n')
+                text2.insert(10, 'Reduce the number of channels per view\n')
+                text2.insert(11, 'Increase the number of channels per view\n')
             text2.append('Mark bad channel\n')
             text2.append('Vertical line at a time instant\n')
             text2.append('Mark bad channel\n')
@@ -351,8 +376,6 @@ def _get_help_text(params):
 
 
 def _prepare_trellis(n_cells, max_col):
-    """Aux function
-    """
     import matplotlib.pyplot as plt
     if n_cells == 1:
         nrow = ncol = 1
@@ -372,26 +395,31 @@ def _prepare_trellis(n_cells, max_col):
 
 
 def _draw_proj_checkbox(event, params, draw_current_state=True):
-    """Toggle options (projectors) dialog"""
+    """Toggle options (projectors) dialog."""
     from matplotlib import widgets
     projs = params['projs']
     # turn on options dialog
 
     labels = [p['desc'] for p in projs]
     actives = ([p['active'] for p in projs] if draw_current_state else
-               [True] * len(params['projs']))
+               params.get('proj_bools', [params['apply_proj']] * len(projs)))
 
-    width = max([len(p['desc']) for p in projs]) / 6.0 + 0.5
-    height = len(projs) / 6.0 + 0.5
+    width = max([4., max([len(p['desc']) for p in projs]) / 6.0 + 0.5])
+    height = len(projs) / 6.0 + 1.5
     fig_proj = figure_nobar(figsize=(width, height))
     fig_proj.canvas.set_window_title('SSP projection vectors')
     params['fig_proj'] = fig_proj  # necessary for proper toggling
-    ax_temp = fig_proj.add_axes((0, 0, 1, 1), frameon=False)
+    ax_temp = fig_proj.add_axes((0, 0, 1, 0.8), frameon=False)
+    ax_temp.set_title('Projectors marked with "X" are active')
 
     proj_checks = widgets.CheckButtons(ax_temp, labels=labels, actives=actives)
+    # make edges around checkbox areas
+    [rect.set_edgecolor('0.5') for rect in proj_checks.rectangles]
+    [rect.set_linewidth(1.) for rect in proj_checks.rectangles]
+
     # change already-applied projectors to red
     for ii, p in enumerate(projs):
-        if p['active'] is True:
+        if p['active']:
             for x in proj_checks.lines[ii]:
                 x.set_color('r')
     # make minimal size
@@ -399,17 +427,18 @@ def _draw_proj_checkbox(event, params, draw_current_state=True):
 
     proj_checks.on_clicked(partial(_toggle_proj, params=params))
     params['proj_checks'] = proj_checks
+    fig_proj.canvas.mpl_connect('key_press_event', _key_press)
 
     # this should work for non-test cases
     try:
         fig_proj.canvas.draw()
-        fig_proj.show(warn=False)
+        plt_show(fig=fig_proj, warn=False)
     except Exception:
         pass
 
 
 def _layout_figure(params):
-    """Function for setting figure layout. Shared with raw and epoch plots"""
+    """Set figure layout. Shared with raw and epoch plots."""
     size = params['fig'].get_size_inches() * params['fig'].dpi
     scroll_width = 25
     hscroll_dist = 25
@@ -417,7 +446,7 @@ def _layout_figure(params):
     l_border = 100
     r_border = 10
     t_border = 35
-    b_border = 40
+    b_border = 45
 
     # only bother trying to reset layout if it's reasonable to do so
     if size[0] < 2 * scroll_width or size[1] < 2 * scroll_width + hscroll_dist:
@@ -465,7 +494,7 @@ def _layout_figure(params):
 @verbose
 def compare_fiff(fname_1, fname_2, fname_out=None, show=True, indent='    ',
                  read_limit=np.inf, max_str=30, verbose=None):
-    """Compare the contents of two fiff files using diff and show_fiff
+    """Compare the contents of two fiff files using diff and show_fiff.
 
     Parameters
     ----------
@@ -487,7 +516,8 @@ def compare_fiff(fname_1, fname_2, fname_out=None, show=True, indent='    ',
         Max number of characters of string representation to print for
         each tag's data.
     verbose : bool, str, int, or None
-        If not None, override default verbose level (see mne.verbose).
+        If not None, override default verbose level (see :func:`mne.verbose`
+        and :ref:`Logging documentation <tut_logging>` for more).
 
     Returns
     -------
@@ -513,7 +543,7 @@ def compare_fiff(fname_1, fname_2, fname_out=None, show=True, indent='    ',
 
 
 def figure_nobar(*args, **kwargs):
-    """Make matplotlib figure with no toolbar"""
+    """Make matplotlib figure with no toolbar."""
     from matplotlib import rcParams, pyplot as plt
     old_val = rcParams['toolbar']
     try:
@@ -523,23 +553,23 @@ def figure_nobar(*args, **kwargs):
         cbs = list(fig.canvas.callbacks.callbacks['key_press_event'].keys())
         for key in cbs:
             fig.canvas.callbacks.disconnect(key)
-    except Exception as ex:
-        raise ex
     finally:
         rcParams['toolbar'] = old_val
     return fig
 
 
 def _helper_raw_resize(event, params):
-    """Helper for resizing"""
+    """Resize."""
     size = ','.join([str(s) for s in params['fig'].get_size_inches()])
     set_config('MNE_BROWSE_RAW_SIZE', size, set_env=False)
     _layout_figure(params)
 
 
 def _plot_raw_onscroll(event, params, len_channels=None):
-    """Interpret scroll events"""
+    """Interpret scroll events."""
     if 'fig_selection' in params:
+        if params['butterfly']:
+            return
         _change_channel_group(event.step, params)
         return
     if len_channels is None:
@@ -555,7 +585,7 @@ def _plot_raw_onscroll(event, params, len_channels=None):
 
 
 def _channels_changed(params, len_channels):
-    """Helper function for dealing with the vertical shift of the viewport."""
+    """Deal with the vertical shift of the viewport."""
     if params['ch_start'] + params['n_channels'] > len_channels:
         params['ch_start'] = len_channels - params['n_channels']
     if params['ch_start'] < 0:
@@ -564,21 +594,25 @@ def _channels_changed(params, len_channels):
 
 
 def _plot_raw_time(value, params):
-    """Deal with changed time value"""
+    """Deal with changed time value."""
     info = params['info']
-    max_times = params['n_times'] / float(info['sfreq']) - params['duration']
+    max_times = params['n_times'] / float(info['sfreq']) + \
+        params['first_time'] - params['duration']
     if value > max_times:
-        value = params['n_times'] / info['sfreq'] - params['duration']
-    if value < 0:
-        value = 0
+        value = params['n_times'] / float(info['sfreq']) + \
+            params['first_time'] - params['duration']
+    if value < params['first_time']:
+        value = params['first_time']
     if params['t_start'] != value:
         params['t_start'] = value
         params['hsel_patch'].set_x(value)
 
 
 def _radio_clicked(label, params):
-    """Callback for radio buttons in selection dialog."""
+    """Handle radio buttons in selection dialog."""
     from .evoked import _rgb
+
+    # First the selection dialog.
     labels = [l._text for l in params['fig_selection'].radio.labels]
     idx = labels.index(label)
     params['fig_selection'].radio._active_idx = idx
@@ -592,13 +626,17 @@ def _radio_clicked(label, params):
     colors = np.zeros((len(types), 4))  # alpha = 0 by default
     locs3d = np.array([ch['loc'][:3] for ch in params['info']['chs']])
     x, y, z = locs3d.T
-    color_vals = _rgb(params['info'], x, y, z)
+    color_vals = _rgb(x, y, z)
     for color_idx, pick in enumerate(types):
         if pick in channels:  # set color and alpha = 1
             colors[color_idx] = np.append(color_vals[pick], 1.)
     ax_topo.collections[0]._facecolors = colors
     params['fig_selection'].canvas.draw()
 
+    if params['butterfly']:
+        return
+    # Then the plotting window.
+    params['ax_vscroll'].set_visible(True)
     nchan = sum([len(params['selections'][l]) for l in labels[:idx]])
     params['vsel_patch'].set_y(nchan)
     n_channels = len(channels)
@@ -612,8 +650,25 @@ def _radio_clicked(label, params):
     params['plot_fun']()
 
 
+def _get_active_radiobutton(radio):
+    """Find out active radio button."""
+    # XXX: In mpl 1.5 you can do: fig.radio.value_selected
+    colors = np.array([np.sum(circle.get_facecolor()) for circle
+                       in radio.circles])
+    return np.where(colors < 4.0)[0][0]  # return idx where color != white
+
+
+def _set_annotation_radio_button(idx, params):
+    """Set active button."""
+    radio = params['fig_annotation'].radio
+    for circle in radio.circles:
+        circle.set_facecolor('white')
+    radio.circles[idx].set_facecolor('#cccccc')
+    _annotation_radio_clicked('', radio, params['ax'].selector)
+
+
 def _set_radio_button(idx, params):
-    """Helper for setting radio button."""
+    """Set radio button."""
     # XXX: New version of matplotlib has this implemented for radio buttons,
     # This function is for compatibility with old versions of mpl.
     radio = params['fig_selection'].radio
@@ -629,14 +684,12 @@ def _change_channel_group(step, params):
     if step < 0:
         if idx < len(radio.labels) - 1:
             _set_radio_button(idx + 1, params)
-    else:
-        if idx > 0:
-            _set_radio_button(idx - 1, params)
-    return
+    elif idx > 0:
+        _set_radio_button(idx - 1, params)
 
 
 def _handle_change_selection(event, params):
-    """Helper for handling clicks on vertical scrollbar using selections."""
+    """Handle clicks on vertical scrollbar using selections."""
     radio = params['fig_selection'].radio
     ydata = event.ydata
     labels = [label._text for label in radio.labels]
@@ -650,20 +703,26 @@ def _handle_change_selection(event, params):
 
 
 def _plot_raw_onkey(event, params):
-    """Interpret key presses"""
+    """Interpret key presses."""
     import matplotlib.pyplot as plt
-    if event.key == 'escape':
+    if event.key == params['close_key']:
         plt.close(params['fig'])
+        if params['fig_annotation'] is not None:
+            plt.close(params['fig_annotation'])
     elif event.key == 'down':
         if 'fig_selection' in params.keys():
             _change_channel_group(-1, params)
             return
+        elif params['butterfly']:
+            return
         params['ch_start'] += params['n_channels']
         _channels_changed(params, len(params['inds']))
     elif event.key == 'up':
         if 'fig_selection' in params.keys():
             _change_channel_group(1, params)
             return
+        elif params['butterfly']:
+            return
         params['ch_start'] -= params['n_channels']
         _channels_changed(params, len(params['inds']))
     elif event.key == 'right':
@@ -716,13 +775,122 @@ def _plot_raw_onkey(event, params):
     elif event.key == 'f11':
         mng = plt.get_current_fig_manager()
         mng.full_screen_toggle()
+    elif event.key == 'a':
+        if 'ica' in params.keys():
+            return
+        if params['fig_annotation'] is None:
+            _setup_annotation_fig(params)
+        else:
+            params['fig_annotation'].canvas.close_event()
+    elif event.key == 'b':
+        _setup_butterfly(params)
+
+
+def _setup_annotation_fig(params):
+    """Initialize the annotation figure."""
+    import matplotlib as mpl
+    import matplotlib.pyplot as plt
+    from matplotlib.widgets import RadioButtons, SpanSelector, Button
+    if params['fig_annotation'] is not None:
+        params['fig_annotation'].canvas.close_event()
+    if params['raw'].annotations is None:
+        params['raw'].annotations = Annotations(list(), list(), list())
+    annotations = params['raw'].annotations
+    labels = list(set(annotations.description))
+    labels = np.union1d(labels, params['added_label'])
+    fig = figure_nobar(figsize=(4.5, 2.75 + len(labels) * 0.75))
+    fig.patch.set_facecolor('white')
+    ax = plt.subplot2grid((len(labels) + 2, 2), (0, 0), rowspan=len(labels),
+                          colspan=2, frameon=False)
+    ax.set_title('Labels')
+    ax.set_aspect('equal')
+    button_ax = plt.subplot2grid((len(labels) + 2, 2), (len(labels), 1),
+                                 rowspan=1, colspan=1)
+    label_ax = plt.subplot2grid((len(labels) + 2, 2), (len(labels), 0),
+                                rowspan=1, colspan=1)
+    plt.axis('off')
+    text_ax = plt.subplot2grid((len(labels) + 2, 2), (len(labels) + 1, 0),
+                               rowspan=1, colspan=2)
+    text_ax.text(0.5, 0.9, 'Left click & drag - Create/modify annotation\n'
+                           'Right click - Delete annotation\n'
+                           'Letter/number keys - Add character\n'
+                           'Backspace - Delete character\n'
+                           'Esc - Close window/exit annotation mode', va='top',
+                 ha='center')
+    plt.axis('off')
+
+    annotations_closed = partial(_annotations_closed, params=params)
+    fig.canvas.mpl_connect('close_event', annotations_closed)
+    fig.canvas.set_window_title('Annotations')
+    fig.radio = RadioButtons(ax, labels, activecolor='#cccccc')
+    radius = 0.15
+    circles = fig.radio.circles
+    for circle, label in zip(circles, fig.radio.labels):
+        circle.set_edgecolor(params['segment_colors'][label.get_text()])
+        circle.set_linewidth(4)
+        circle.set_radius(radius / (len(labels)))
+        label.set_x(circle.center[0] + (radius + 0.1) / len(labels))
+    col = 'r' if len(fig.radio.circles) < 1 else circles[0].get_edgecolor()
+    fig.canvas.mpl_connect('key_press_event', partial(
+        _change_annotation_description, params=params))
+    fig.button = Button(button_ax, 'Add label')
+    fig.label = label_ax.text(0.5, 0.5, 'BAD_', va='center', ha='center')
+    fig.button.on_clicked(partial(_onclick_new_label, params=params))
+    plt_show(fig=fig)
+    params['fig_annotation'] = fig
+
+    ax = params['ax']
+    cb_onselect = partial(_annotate_select, params=params)
+    selector = SpanSelector(ax, cb_onselect, 'horizontal', minspan=.1,
+                            rectprops=dict(alpha=0.5, facecolor=col))
+    if len(labels) == 0:
+        selector.active = False
+    params['ax'].selector = selector
+    if LooseVersion(mpl.__version__) < LooseVersion('1.5'):
+        # XXX: Hover event messes up callback ids in old mpl.
+        warn('Modifying existing annotations is not possible for '
+             'matplotlib versions < 1.4. Upgrade matplotlib.')
+        return
+    hover_callback = partial(_on_hover, params=params)
+    params['hover_callback'] = params['fig'].canvas.mpl_connect(
+        'motion_notify_event', hover_callback)
+
+    radio_clicked = partial(_annotation_radio_clicked, radio=fig.radio,
+                            selector=selector)
+    fig.radio.on_clicked(radio_clicked)
+
+
+def _onclick_new_label(event, params):
+    """Add new description on button press."""
+    text = params['fig_annotation'].label.get_text()[:-1]
+    params['added_label'].append(text)
+    _setup_annotation_colors(params)
+    _setup_annotation_fig(params)
+    idx = [label.get_text() for label in
+           params['fig_annotation'].radio.labels].index(text)
+    _set_annotation_radio_button(idx, params)
 
 
 def _mouse_click(event, params):
-    """Vertical select callback"""
-    if event.button != 1:
+    """Handle mouse clicks."""
+    if event.button not in (1, 3):
         return
-    if event.inaxes is None:
+    if event.button == 3:
+        if params['fig_annotation'] is None:
+            return
+        raw = params['raw']
+        if np.any([c.contains(event)[0] for c in params['ax'].collections]):
+            xdata = event.xdata - params['first_time']
+            onset = _sync_onset(raw, raw.annotations.onset)
+            ends = onset + raw.annotations.duration
+            ann_idx = np.where((xdata > onset) & (xdata < ends))[0]
+            raw.annotations.delete(ann_idx)  # only first one deleted
+        _remove_segment_line(params)
+        _plot_annotations(raw, params)
+        params['plot_fun']()
+        return
+
+    if event.inaxes is None:  # check if channel label is clicked
         if params['n_channels'] > 100:
             return
         ax = params['ax']
@@ -732,7 +900,7 @@ def _mouse_click(event, params):
             return
         params['label_click_fun'](pos)
     # vertical scrollbar changed
-    if event.inaxes == params['ax_vscroll']:
+    elif event.inaxes == params['ax_vscroll']:
         if 'fig_selection' in params.keys():
             _handle_change_selection(event, params)
         else:
@@ -751,7 +919,7 @@ def _mouse_click(event, params):
 
 
 def _handle_topomap_bads(ch_name, params):
-    """Helper for coloring channels in selection topomap when selecting bads"""
+    """Color channels in selection topomap when selecting bads."""
     for type in ('mag', 'grad', 'eeg', 'seeg', 'hbo', 'hbr'):
         if type in params['types']:
             types = np.where(np.array(params['types']) == type)[0]
@@ -769,7 +937,7 @@ def _handle_topomap_bads(ch_name, params):
 
 
 def _find_channel_idx(ch_name, params):
-    """Helper for finding all indices when using selections."""
+    """Find all indices when using selections."""
     indices = list()
     offset = 0
     labels = [l._text for l in params['fig_selection'].radio.labels]
@@ -784,10 +952,21 @@ def _find_channel_idx(ch_name, params):
     return indices
 
 
+def _draw_vert_line(xdata, params):
+    """Draw vertical line."""
+    params['ax_vertline'].set_data(xdata, np.array(params['ax'].get_ylim()))
+    params['ax_hscroll_vertline'].set_data(xdata, np.array([0., 1.]))
+    params['vertline_t'].set_text('%0.3f' % xdata[0])
+
+
 def _select_bads(event, params, bads):
-    """Helper for selecting bad channels onpick. Returns updated bads list."""
+    """Select bad channels onpick. Returns updated bads list."""
     # trade-off, avoid selecting more than one channel when drifts are present
     # however for clean data don't click on peaks but on flat segments
+    if params['butterfly']:
+        _draw_vert_line(np.array([event.xdata] * 2), params)
+        return bads
+
     def f(x, y):
         return y(np.mean(x), x.std() * 2)
     lines = event.inaxes.lines
@@ -818,16 +997,13 @@ def _select_bads(event, params, bads):
                         params['ax_vscroll'].patches[idx].set_color(color)
                     break
     else:
-        x = np.array([event.xdata] * 2)
-        params['ax_vertline'].set_data(x, np.array(params['ax'].get_ylim()))
-        params['ax_hscroll_vertline'].set_data(x, np.array([0., 1.]))
-        params['vertline_t'].set_text('%0.3f' % x[0])
+        _draw_vert_line(np.array([event.xdata] * 2), params)
 
     return bads
 
 
 def _onclick_help(event, params):
-    """Function for drawing help window"""
+    """Draw help window."""
     import matplotlib.pyplot as plt
     text, text2 = _get_help_text(params)
 
@@ -836,13 +1012,13 @@ def _onclick_help(event, params):
 
     fig_help = figure_nobar(figsize=(width, height), dpi=80)
     fig_help.canvas.set_window_title('Help')
+    params['fig_help'] = fig_help
     ax = plt.subplot2grid((8, 5), (0, 0), colspan=5)
     ax.set_title('Keyboard shortcuts')
     plt.axis('off')
     ax1 = plt.subplot2grid((8, 5), (1, 0), rowspan=7, colspan=2)
     ax1.set_yticklabels(list())
-    plt.text(0.99, 1, text, fontname='STIXGeneral', va='top', weight='bold',
-             ha='right')
+    plt.text(0.99, 1, text, fontname='STIXGeneral', va='top', ha='right')
     plt.axis('off')
 
     ax2 = plt.subplot2grid((8, 5), (1, 2), rowspan=7, colspan=3)
@@ -850,17 +1026,26 @@ def _onclick_help(event, params):
     plt.text(0, 1, text2, fontname='STIXGeneral', va='top')
     plt.axis('off')
 
+    fig_help.canvas.mpl_connect('key_press_event', _key_press)
+
     tight_layout(fig=fig_help)
     # this should work for non-test cases
     try:
         fig_help.canvas.draw()
-        fig_help.show(warn=False)
+        plt_show(fig=fig_help, warn=False)
     except Exception:
         pass
 
 
+def _key_press(event):
+    """Handle key press in dialog."""
+    import matplotlib.pyplot as plt
+    if event.key == 'escape':
+        plt.close(event.canvas.figure)
+
+
 def _setup_browser_offsets(params, n_channels):
-    """Aux function for computing viewport height and adjusting offsets."""
+    """Compute viewport height and adjust offsets."""
     ylim = [n_channels * 2 + 1, 0]
     offset = ylim[0] / n_channels
     params['offsets'] = np.arange(n_channels) * offset + (offset / 2.)
@@ -873,9 +1058,7 @@ def _setup_browser_offsets(params, n_channels):
 
 
 class ClickableImage(object):
-
-    """
-    Display an image so you can click on it and store x/y positions.
+    """Display an image so you can click on it and store x/y positions.
 
     Takes as input an image array (can be any array that works with imshow,
     but will work best with images.  Displays the image and lets you
@@ -898,7 +1081,7 @@ class ClickableImage(object):
 
     """
 
-    def __init__(self, imdata, **kwargs):
+    def __init__(self, imdata, **kwargs):  # noqa: D102
         """Display the image for clicking."""
         from matplotlib.pyplot import figure
         self.coords = []
@@ -907,15 +1090,15 @@ class ClickableImage(object):
         self.ax = self.fig.add_subplot(111)
         self.ymax = self.imdata.shape[0]
         self.xmax = self.imdata.shape[1]
-        self.im = self.ax.imshow(imdata, aspect='auto',
+        self.im = self.ax.imshow(imdata,
                                  extent=(0, self.xmax, 0, self.ymax),
                                  picker=True, **kwargs)
         self.ax.axis('off')
         self.fig.canvas.mpl_connect('pick_event', self.onclick)
-        plt_show()
+        plt_show(block=True)
 
     def onclick(self, event):
-        """Mouse click handler.
+        """Handle Mouse clicks.
 
         Parameters
         ----------
@@ -934,6 +1117,9 @@ class ClickableImage(object):
             Arguments are passed to imshow in displaying the bg image.
         """
         from matplotlib.pyplot import subplots
+        if len(self.coords) == 0:
+            raise ValueError('No coordinates found, make sure you click '
+                             'on the image that is first shown.')
         f, ax = subplots()
         ax.imshow(self.imdata, extent=(0, self.xmax, 0, self.ymax), **kwargs)
         xlim, ylim = [ax.get_xlim(), ax.get_ylim()]
@@ -963,7 +1149,7 @@ class ClickableImage(object):
 
 
 def _fake_click(fig, ax, point, xform='ax', button=1, kind='press'):
-    """Helper to fake a click at a relative point within axes."""
+    """Fake a click at a relative point within axes."""
     if xform == 'ax':
         x, y = ax.transAxes.transform_point(point)
     elif xform == 'data':
@@ -1030,7 +1216,8 @@ def add_background_image(fig, im, set_ratios=None):
 
 
 def _find_peaks(evoked, npeaks):
-    """Helper function for finding peaks from evoked data
+    """Find peaks from evoked data.
+
     Returns ``npeaks`` biggest peaks as a list of time points.
     """
     from scipy.signal import argrelmax
@@ -1048,38 +1235,41 @@ def _find_peaks(evoked, npeaks):
     return times
 
 
-def _process_times(inst, times, n_peaks=None, few=False):
-    """Helper to return a list of times for topomaps"""
-    if isinstance(times, string_types):
-        if times == "peaks":
+def _process_times(inst, use_times, n_peaks=None, few=False):
+    """Return a list of times for topomaps."""
+    if isinstance(use_times, string_types):
+        if use_times == 'interactive':
+            use_times, n_peaks = 'peaks', 1
+        if use_times == 'peaks':
             if n_peaks is None:
-                n_peaks = 3 if few else 7
-            times = _find_peaks(inst, n_peaks)
-        elif times == "auto":
+                n_peaks = min(3 if few else 7, len(inst.times))
+            use_times = _find_peaks(inst, n_peaks)
+        elif use_times == 'auto':
             if n_peaks is None:
-                n_peaks = 5 if few else 10
-            times = np.linspace(inst.times[0], inst.times[-1], n_peaks)
+                n_peaks = min(5 if few else 10, len(use_times))
+            use_times = np.linspace(inst.times[0], inst.times[-1], n_peaks)
         else:
             raise ValueError("Got an unrecognized method for `times`. Only "
-                             "'peaks' and 'auto' are supported (or directly "
-                             "passing numbers).")
-    elif np.isscalar(times):
-        times = [times]
+                             "'peaks', 'auto' and 'interactive' are supported "
+                             "(or directly passing numbers).")
+    elif np.isscalar(use_times):
+        use_times = [use_times]
 
-    times = np.array(times)
+    use_times = np.array(use_times, float)
 
-    if times.ndim != 1:
-        raise ValueError('times must be 1D, got %d dimensions' % times.ndim)
-    if len(times) > 20:
+    if use_times.ndim != 1:
+        raise ValueError('times must be 1D, got %d dimensions'
+                         % use_times.ndim)
+    if len(use_times) > 20:
         raise RuntimeError('Too many plots requested. Please pass fewer '
                            'than 20 time instants.')
 
-    return times
+    return use_times
 
 
 def plot_sensors(info, kind='topomap', ch_type=None, title=None,
-                 show_names=False, ch_groups=None, axes=None, block=False,
-                 show=True):
+                 show_names=False, ch_groups=None, to_sphere=True, axes=None,
+                 block=False, show=True):
     """Plot sensors positions.
 
     Parameters
@@ -1101,8 +1291,9 @@ def plot_sensors(info, kind='topomap', ch_type=None, title=None,
     title : str | None
         Title for the figure. If None (default), equals to
         ``'Sensor positions (%s)' % ch_type``.
-    show_names : bool
-        Whether to display all channel names. Defaults to False.
+    show_names : bool | array of str
+        Whether to display all channel names. If an array, only the channel
+        names in the array are shown. Defaults to False.
     ch_groups : 'position' | array of shape (ch_groups, picks) | None
         Channel groups for coloring the sensors. If None (default), default
         coloring scheme is used. If 'position', the sensors are divided
@@ -1111,6 +1302,13 @@ def plot_sensors(info, kind='topomap', ch_type=None, title=None,
 
         .. versionadded:: 0.13.0
 
+    to_sphere : bool
+        Whether to project the 3d locations to a sphere. When False, the
+        sensor array appears similar as to looking downwards straight above the
+        subject's head. Has no effect when kind='3d'. Defaults to True.
+
+        .. versionadded:: 0.14.0
+
     axes : instance of Axes | instance of Axes3D | None
         Axes to draw the sensors to. If ``kind='3d'``, axes must be an instance
         of Axes3D. If None (default), a new axes will be created.
@@ -1141,7 +1339,7 @@ def plot_sensors(info, kind='topomap', ch_type=None, title=None,
     -----
     This function plots the sensor locations from the info structure using
     matplotlib. For drawing the sensors using mayavi see
-    :func:`mne.viz.plot_trans`.
+    :func:`mne.viz.plot_alignment`.
 
     .. versionadded:: 0.12.0
 
@@ -1198,7 +1396,7 @@ def plot_sensors(info, kind='topomap', ch_type=None, title=None,
                 if len(color_picks) == 0:
                     continue
                 x, y, z = pos[color_picks].T
-                color = np.mean(_rgb(info, x, y, z), axis=0)
+                color = np.mean(_rgb(x, y, z), axis=0)
                 color_vals[idx, :3] = color  # mean of spatial color
         else:
             import matplotlib.pyplot as plt
@@ -1214,18 +1412,19 @@ def plot_sensors(info, kind='topomap', ch_type=None, title=None,
                     colors[pick_idx] = color_vals[ind]
                     break
     if kind in ('topomap', 'select'):
-        pos = _auto_topomap_coords(info, picks, True)
+        pos = _auto_topomap_coords(info, picks, True, to_sphere=to_sphere)
 
     title = 'Sensor positions (%s)' % ch_type if title is None else title
     fig = _plot_sensors(pos, colors, bads, ch_names, title, show_names, axes,
-                        show, kind == 'select', block=block)
+                        show, kind == 'select', block=block,
+                        to_sphere=to_sphere)
     if kind == 'select':
         return fig, fig.lasso.selection
     return fig
 
 
 def _onpick_sensor(event, fig, ax, pos, ch_names, show_names):
-    """Callback for picked channel in plot_sensors."""
+    """Pick a channel in plot_sensors."""
     if event.mouseevent.key == 'control' and fig.lasso is not None:
         for ind in event.ind:
             fig.lasso.select_one(ind)
@@ -1249,19 +1448,21 @@ def _onpick_sensor(event, fig, ax, pos, ch_names, show_names):
 
 
 def _close_event(event, fig):
-    fig.lasso.disconnect()
+    """Listen for sensor plotter close event."""
+    if fig.lasso is not None:
+        fig.lasso.disconnect()
 
 
 def _plot_sensors(pos, colors, bads, ch_names, title, show_names, ax, show,
-                  select, block):
-    """Helper function for plotting sensors."""
+                  select, block, to_sphere):
+    """Plot sensors."""
     import matplotlib.pyplot as plt
     from mpl_toolkits.mplot3d import Axes3D
     from .topomap import _check_outlines, _draw_outlines
     edgecolors = np.repeat('black', len(colors))
     edgecolors[bads] = 'red'
     if ax is None:
-        fig = plt.figure()
+        fig = plt.figure(figsize=(max(plt.rcParams['figure.figsize']),) * 2)
         if pos.shape[1] == 3:
             Axes3D(fig)
             ax = fig.gca(projection='3d')
@@ -1277,28 +1478,45 @@ def _plot_sensors(pos, colors, bads, ch_names, title, show_names, ax, show,
 
         ax.azim = 90
         ax.elev = 0
+        ax.xaxis.set_label_text('x')
+        ax.yaxis.set_label_text('y')
+        ax.zaxis.set_label_text('z')
     else:
         ax.text(0, 0, '', zorder=1)
-        ax.set_xticks([])
-        ax.set_yticks([])
+        # Equal aspect for 3D looks bad, so only use for 2D
+        ax.set(xticks=[], yticks=[], aspect='equal')
         fig.subplots_adjust(left=0, bottom=0, right=1, top=1, wspace=None,
                             hspace=None)
-        pos, outlines = _check_outlines(pos, 'head')
+        if to_sphere:
+            pos, outlines = _check_outlines(pos, 'head')
+        else:
+            pos, outlines = _check_outlines(pos, np.array([0.5, 0.5]),
+                                            {'center': (0, 0),
+                                             'scale': (4.5, 4.5)})
         _draw_outlines(ax, outlines)
 
-        pts = ax.scatter(pos[:, 0], pos[:, 1], picker=True, c=colors, s=75,
-                         edgecolor=edgecolors, linewidth=2)
+        pts = ax.scatter(pos[:, 0], pos[:, 1], picker=True, c=colors, s=25,
+                         edgecolor=edgecolors, linewidth=2, clip_on=False)
+
         if select:
             fig.lasso = SelectFromCollection(ax, pts, ch_names)
+        else:
+            fig.lasso = None
+
+        ax.axis("off")  # remove border around figure
 
     connect_picker = True
     if show_names:
-        for idx in range(len(pos)):
+        if isinstance(show_names, (list, np.ndarray)):  # only given channels
+            indices = [list(ch_names).index(name) for name in show_names]
+        else:  # all channels
+            indices = range(len(pos))
+        for idx in indices:
             this_pos = pos[idx]
             if pos.shape[1] == 3:
                 ax.text(this_pos[0], this_pos[1], this_pos[2], ch_names[idx])
             else:
-                ax.text(this_pos[0], this_pos[1], ch_names[idx])
+                ax.text(this_pos[0] + 0.015, this_pos[1], ch_names[idx])
         connect_picker = select
     if connect_picker:
         picker = partial(_onpick_sensor, fig=fig, ax=ax, pos=pos,
@@ -1332,9 +1550,9 @@ def _compute_scalings(scalings, inst):
     scalings : dict
         A scalings dictionary with updated values
     """
-    from ..io.base import _BaseRaw
-    from ..epochs import _BaseEpochs
-    if not isinstance(inst, (_BaseRaw, _BaseEpochs)):
+    from ..io.base import BaseRaw
+    from ..epochs import BaseEpochs
+    if not isinstance(inst, (BaseRaw, BaseEpochs)):
         raise ValueError('Must supply either Raw or Epochs')
     if scalings is None:
         # If scalings is None just return it and do nothing
@@ -1352,7 +1570,7 @@ def _compute_scalings(scalings, inst):
     scalings = deepcopy(scalings)
 
     if inst.preload is False:
-        if isinstance(inst, _BaseRaw):
+        if isinstance(inst, BaseRaw):
             # Load a window of data from the center up to 100mb in size
             n_times = 1e8 // (len(inst.ch_names) * 8)
             n_times = np.clip(n_times, 1, inst.n_times)
@@ -1361,7 +1579,7 @@ def _compute_scalings(scalings, inst):
             tmin = np.clip(time_middle - n_secs / 2., inst.times.min(), None)
             tmax = np.clip(time_middle + n_secs / 2., None, inst.times.max())
             data = inst._read_segment(tmin, tmax)
-        elif isinstance(inst, _BaseEpochs):
+        elif isinstance(inst, BaseEpochs):
             # Load a random subset of epochs up to 100mb in size
             n_epochs = 1e8 // (len(inst.ch_names) * len(inst.times) * 8)
             n_epochs = int(np.clip(n_epochs, 1, len(inst)))
@@ -1369,7 +1587,7 @@ def _compute_scalings(scalings, inst):
             inst = inst.copy()[ixs_epochs].load_data()
     else:
         data = inst._data
-    if isinstance(inst, _BaseEpochs):
+    if isinstance(inst, BaseEpochs):
         data = inst._data.reshape([len(inst.ch_names), -1])
     # Iterate through ch types and update scaling if ' auto'
     for key, value in scalings.items():
@@ -1384,11 +1602,24 @@ def _compute_scalings(scalings, inst):
     return scalings
 
 
+def _setup_cmap(cmap, n_axes=1, norm=False):
+    """Set color map interactivity."""
+    if cmap == 'interactive':
+        cmap = ('Reds' if norm else 'RdBu_r', True)
+    elif not isinstance(cmap, tuple):
+        if cmap is None:
+            cmap = 'Reds' if norm else 'RdBu_r'
+        cmap = (cmap, False if n_axes > 2 else True)
+    return cmap
+
+
 class DraggableColorbar(object):
-    """Class for enabling interactive colorbar.
+    """Enable interactive colorbar.
+
     See http://www.ster.kuleuven.be/~pieterd/python/html/plotting/interactive_colorbar.html
-    """  # noqa
-    def __init__(self, cbar, mappable):
+    """  # noqa: E501
+
+    def __init__(self, cbar, mappable):  # noqa: D102
         import matplotlib.pyplot as plt
         self.cbar = cbar
         self.mappable = mappable
@@ -1413,13 +1644,13 @@ class DraggableColorbar(object):
             'scroll_event', self.on_scroll)
 
     def on_press(self, event):
-        """Callback for button press."""
+        """Handle button press."""
         if event.inaxes != self.cbar.ax:
             return
         self.press = event.y
 
     def key_press(self, event):
-        """Callback for key press."""
+        """Handle key press."""
         if event.key == 'down':
             self.index += 1
         elif event.key == 'up':
@@ -1440,7 +1671,7 @@ class DraggableColorbar(object):
         self.cbar.patch.figure.canvas.draw()
 
     def on_motion(self, event):
-        """Callback for mouse movements."""
+        """Handle mouse movements."""
         if self.press is None:
             return
         if event.inaxes != self.cbar.ax:
@@ -1461,13 +1692,13 @@ class DraggableColorbar(object):
         self.cbar.patch.figure.canvas.draw()
 
     def on_release(self, event):
-        """Callback for release."""
+        """Handle release."""
         self.press = None
         self.mappable.set_norm(self.cbar.norm)
         self.cbar.patch.figure.canvas.draw()
 
     def on_scroll(self, event):
-        """Callback for scroll."""
+        """Handle scroll."""
         scale = 1.1 if event.step < 0 else 1. / 1.1
         self.cbar.norm.vmin *= scale
         self.cbar.norm.vmax *= scale
@@ -1477,31 +1708,31 @@ class DraggableColorbar(object):
 
 
 class SelectFromCollection(object):
-    """Select channels from a matplotlib collection using `LassoSelector`.
+    """Select channels from a matplotlib collection using ``LassoSelector``.
 
     Selected channels are saved in the ``selection`` attribute. This tool
     highlights selected points by fading other points out (i.e., reducing their
     alpha values).
 
-    Notes:
-    This tool selects collection objects based on their *origins*
-    (i.e., `offsets`). Emits mpl event 'lasso_event' when selection is ready.
-
     Parameters
     ----------
     ax : Instance of Axes
         Axes to interact with.
-
     collection : Instance of matplotlib collection
         Collection you want to select from.
-
     alpha_other : 0 <= float <= 1
         To highlight a selection, this tool sets all selected points to an
         alpha value of 1 and non-selected points to `alpha_other`.
         Defaults to 0.3.
+
+    Notes
+    -----
+    This tool selects collection objects based on their *origins*
+    (i.e., `offsets`). Emits mpl event 'lasso_event' when selection is ready.
     """
 
-    def __init__(self, ax, collection, ch_names, alpha_other=0.3):
+    def __init__(self, ax, collection, ch_names,
+                 alpha_other=0.3):  # noqa: D102
         import matplotlib as mpl
         if LooseVersion(mpl.__version__) < LooseVersion('1.2.1'):
             raise ImportError('Interactive selection not possible for '
@@ -1529,7 +1760,7 @@ class SelectFromCollection(object):
         self.selection = list()
 
     def on_select(self, verts):
-        """Callback for selecting a subset from the collection."""
+        """Select a subset from the collection."""
         from matplotlib.path import Path
         if len(verts) <= 3:  # Seems to be a good way to exclude single clicks.
             return
@@ -1551,7 +1782,7 @@ class SelectFromCollection(object):
         self.canvas.callbacks.process('lasso_event')
 
     def select_one(self, ind):
-        """Helper for selecting/deselecting one sensor."""
+        """Select or deselect one sensor."""
         ch_name = self.ch_names[ind]
         if ch_name in self.selection:
             sel_ind = self.selection.index(ch_name)
@@ -1566,8 +1797,438 @@ class SelectFromCollection(object):
         self.canvas.callbacks.process('lasso_event')
 
     def disconnect(self):
-        """Method for disconnecting the lasso selector."""
+        """Disconnect the lasso selector."""
         self.lasso.disconnect_events()
         self.fc[:, -1] = 1
         self.collection.set_facecolors(self.fc)
         self.canvas.draw_idle()
+
+
+def _annotate_select(vmin, vmax, params):
+    """Handle annotation span selector."""
+    raw = params['raw']
+    onset = _sync_onset(raw, vmin, True) - params['first_time']
+    duration = vmax - vmin
+    active_idx = _get_active_radiobutton(params['fig_annotation'].radio)
+    description = params['fig_annotation'].radio.labels[active_idx].get_text()
+    if raw.annotations is None:
+        annot = Annotations([onset], [duration], [description])
+        raw.annotations = annot
+    else:
+        _merge_annotations(onset, onset + duration, description,
+                           raw.annotations)
+
+    _plot_annotations(params['raw'], params)
+    params['plot_fun']()
+
+
+def _plot_annotations(raw, params):
+    """Set up annotations for plotting in raw browser."""
+    if raw.annotations is None:
+        return
+
+    while len(params['ax_hscroll'].collections) > 0:
+        params['ax_hscroll'].collections.pop()
+    segments = list()
+    # sort the segments by start time
+    ann_order = raw.annotations.onset.argsort(axis=0)
+    descriptions = raw.annotations.description[ann_order]
+
+    _setup_annotation_colors(params)
+    for idx, onset in enumerate(raw.annotations.onset[ann_order]):
+        annot_start = _sync_onset(raw, onset) + params['first_time']
+        annot_end = annot_start + raw.annotations.duration[ann_order][idx]
+        segments.append([annot_start, annot_end])
+        dscr = descriptions[idx]
+        params['ax_hscroll'].fill_betweenx(
+            (0., 1.), annot_start, annot_end, alpha=0.3,
+            color=params['segment_colors'][dscr])
+    params['segments'] = np.array(segments)
+    params['annot_description'] = descriptions
+
+
+def _setup_annotation_colors(params):
+    """Set up colors for annotations."""
+    raw = params['raw']
+    segment_colors = params.get('segment_colors', dict())
+    # sort the segments by start time
+    if raw.annotations is not None:
+        ann_order = raw.annotations.onset.argsort(axis=0)
+        descriptions = raw.annotations.description[ann_order]
+    else:
+        descriptions = list()
+    color_keys = np.union1d(descriptions, params['added_label'])
+    color_cycle = cycle(np.delete(COLORS, 2))  # no red
+    for _ in np.intersect1d(list(color_keys), list(segment_colors.keys())):
+        next(color_cycle)
+    for idx, key in enumerate(color_keys):
+        if key in segment_colors:
+            continue
+        elif key.lower().startswith('bad') or key.lower().startswith('edge'):
+            segment_colors[key] = 'red'
+        else:
+            segment_colors[key] = next(color_cycle)
+    params['segment_colors'] = segment_colors
+
+
+def _annotations_closed(event, params):
+    """Clean up on annotation dialog close."""
+    import matplotlib as mpl
+    import matplotlib.pyplot as plt
+    plt.close(params['fig_annotation'])
+    if params['ax'].selector is not None:
+        params['ax'].selector.disconnect_events()
+        params['ax'].selector = None
+    params['fig_annotation'] = None
+    if params['segment_line'] is not None:
+        params['segment_line'].remove()
+        params['segment_line'] = None
+    if LooseVersion(mpl.__version__) >= LooseVersion('1.5'):
+        params['fig'].canvas.mpl_disconnect(params['hover_callback'])
+    params['fig_annotation'] = None
+    params['fig'].canvas.draw()
+
+
+def _on_hover(event, params):
+    """Handle hover event."""
+    if (event.button is not None or
+            event.inaxes != params['ax'] or event.xdata is None):
+        return
+    for coll in params['ax'].collections:
+        if coll.contains(event)[0]:
+            path = coll.get_paths()[-1]
+            mn = min(path.vertices[:, 0])
+            mx = max(path.vertices[:, 0])
+            x = mn if abs(event.xdata - mn) < abs(event.xdata - mx) else mx
+            ylim = params['ax'].get_ylim()
+            if params['segment_line'] is None:
+                modify_callback = partial(_annotation_modify, params=params)
+                line = params['ax'].plot([x, x], ylim, color='r',
+                                         linewidth=3, picker=5.)[0]
+                dl = DraggableLine(line, modify_callback)
+                params['segment_line'] = dl
+            else:
+                params['segment_line'].set_x(x)
+            params['vertline_t'].set_text('%.3f' % x)
+            params['ax_vertline'].set_data(0,
+                                           np.array(params['ax'].get_ylim()))
+            params['ax'].selector.active = False
+            params['fig'].canvas.draw()
+            return
+    _remove_segment_line(params)
+
+
+def _remove_segment_line(params):
+    """Remove annotation line from the view."""
+    if params['segment_line'] is not None:
+        params['segment_line'].remove()
+        params['segment_line'] = None
+        params['ax'].selector.active = True
+        params['vertline_t'].set_text('')
+
+
+def _annotation_modify(old_x, new_x, params):
+    """Modify annotation."""
+    raw = params['raw']
+
+    segment = np.array(np.where(params['segments'] == old_x))
+    if segment.shape[1] == 0:
+        return
+    annotations = params['raw'].annotations
+    idx = [segment[0][0], segment[1][0]]
+    onset = _sync_onset(raw, params['segments'][idx[0]][0], True)
+    ann_idx = np.where(annotations.onset == onset - params['first_time'])[0]
+    if idx[1] == 0:  # start of annotation
+        onset = _sync_onset(raw, new_x, True) - params['first_time']
+        duration = annotations.duration[ann_idx] + old_x - new_x
+    else:  # end of annotation
+        onset = annotations.onset[ann_idx]
+        duration = _sync_onset(raw, new_x, True) - onset - params['first_time']
+
+    if duration < 0:
+        onset += duration
+        duration *= -1.
+
+    _merge_annotations(onset, onset + duration,
+                       annotations.description[ann_idx], annotations, ann_idx)
+    _plot_annotations(params['raw'], params)
+    _remove_segment_line(params)
+
+    params['plot_fun']()
+
+
+def _merge_annotations(start, stop, description, annotations, current=()):
+    """Handle drawn annotations."""
+    ends = annotations.onset + annotations.duration
+    idx = np.intersect1d(np.where(ends >= start)[0],
+                         np.where(annotations.onset <= stop)[0])
+    idx = np.intersect1d(idx,
+                         np.where(annotations.description == description)[0])
+    new_idx = np.setdiff1d(idx, current)  # don't include modified annotation
+    end = max(np.append((annotations.onset[new_idx] +
+                         annotations.duration[new_idx]), stop))
+    onset = min(np.append(annotations.onset[new_idx], start))
+    duration = end - onset
+    annotations.delete(idx)
+    annotations.append(onset, duration, description)
+
+
+def _change_annotation_description(event, params):
+    """Handle keys in annotation dialog."""
+    import matplotlib.pyplot as plt
+    fig = event.canvas.figure
+    text = fig.label.get_text()
+    if event.key == 'backspace':
+        if len(text) == 1:
+            return
+        text = text[:-2]
+    elif event.key == 'escape':
+        plt.close(fig)
+        return
+    elif event.key == 'enter':
+        _onclick_new_label(event, params)
+    elif len(event.key) > 1 or event.key == ';':  # ignore modifier keys
+        return
+    else:
+        text = text[:-1] + event.key
+    fig.label.set_text(text + '_')
+    fig.canvas.draw()
+
+
+def _annotation_radio_clicked(label, radio, selector):
+    """Handle annotation radio buttons."""
+    idx = _get_active_radiobutton(radio)
+    color = radio.circles[idx].get_edgecolor()
+    selector.rect.set_color(color)
+    selector.rectprops.update(dict(facecolor=color))
+
+
+def _setup_butterfly(params):
+    """Set butterfly view of raw plotter."""
+    from .raw import _setup_browser_selection
+    if 'ica' in params:
+        return
+    butterfly = not params['butterfly']
+    ax = params['ax']
+    params['butterfly'] = butterfly
+    if butterfly:
+        types = np.array(params['types'])[params['orig_inds']]
+        if params['group_by'] in ['type', 'original']:
+            inds = params['inds']
+            eeg = 'seeg' if 'seeg' in types else 'eeg'
+            labels = [t for t in ['grad', 'mag', eeg, 'eog', 'ecg']
+                      if t in types] + ['misc']
+            ticks = np.arange(5, 5 * (len(labels) + 1), 5)
+            offs = {l: t for (l, t) in zip(labels, ticks)}
+
+            params['offsets'] = np.zeros(len(params['types']))
+            for ind in inds:
+                params['offsets'][ind] = offs.get(params['types'][ind],
+                                                  5 * (len(labels)))
+            ax.set_yticks(ticks)
+            params['ax'].set_ylim(5 * (len(labels) + 1), 0)
+            ax.set_yticklabels(labels)
+        else:
+            if 'selections' not in params:
+                params['selections'] = _setup_browser_selection(
+                    params['raw'], 'position', selector=False)
+            sels = params['selections']
+            selections = _SELECTIONS[1:]  # Vertex not used
+            if ('Misc' in sels and len(sels['Misc']) > 0):
+                selections += ['Misc']
+            if params['group_by'] == 'selection' and 'eeg' in types:
+                for sel in _EEG_SELECTIONS:
+                    if sel in sels:
+                        selections += [sel]
+            picks = list()
+            for selection in selections:
+                picks.append(sels.get(selection, list()))
+            labels = ax.yaxis.get_ticklabels()
+            for label in labels:
+                label.set_visible(True)
+            ylim = (5. * len(picks), 0.)
+            ax.set_ylim(ylim)
+            offset = ylim[0] / (len(picks) + 1)
+            ticks = np.arange(0, ylim[0], offset)
+            ticks = [ticks[x] if x < len(ticks) else 0 for x in range(20)]
+            ax.set_yticks(ticks)
+            offsets = np.zeros(len(params['types']))
+
+            for group_idx, group in enumerate(picks):
+                for idx, pick in enumerate(group):
+                    offsets[pick] = offset * (group_idx + 1)
+            params['inds'] = params['orig_inds'].copy()
+            params['offsets'] = offsets
+            ax.set_yticklabels([''] + selections, color='black', rotation=45,
+                               va='top')
+    else:
+        params['inds'] = params['orig_inds'].copy()
+        if 'fig_selection' not in params:
+            for idx in np.arange(params['n_channels'], len(params['lines'])):
+                params['lines'][idx].set_xdata([])
+                params['lines'][idx].set_ydata([])
+        _setup_browser_offsets(params, max([params['n_channels'], 1]))
+        if 'fig_selection' in params:
+            radio = params['fig_selection'].radio
+            active_idx = _get_active_radiobutton(radio)
+            _radio_clicked(radio.labels[active_idx]._text, params)
+
+    params['ax_vscroll'].set_visible(not butterfly)
+    params['plot_fun']()
+
+
+class DraggableLine:
+    """Custom matplotlib line for moving around by drag and drop.
+
+    Parameters
+    ----------
+    line : instance of matplotlib Line2D
+        Line to add interactivity to.
+    callback : function
+        Callback to call when line is released.
+    """
+
+    def __init__(self, line, callback):  # noqa: D102
+        self.line = line
+        self.press = None
+        self.x0 = line.get_xdata()[0]
+        self.callback = callback
+        self.cidpress = self.line.figure.canvas.mpl_connect(
+            'button_press_event', self.on_press)
+        self.cidrelease = self.line.figure.canvas.mpl_connect(
+            'button_release_event', self.on_release)
+        self.cidmotion = self.line.figure.canvas.mpl_connect(
+            'motion_notify_event', self.on_motion)
+
+    def set_x(self, x):
+        """Repoisition the line."""
+        self.line.set_xdata([x, x])
+        self.x0 = x
+
+    def on_press(self, event):
+        """Store button press if on top of the line."""
+        if event.inaxes != self.line.axes or not self.line.contains(event)[0]:
+            return
+        x0 = self.line.get_xdata()
+        y0 = self.line.get_ydata()
+        self.press = x0, y0, event.xdata, event.ydata
+
+    def on_motion(self, event):
+        """Move the line on drag."""
+        if self.press is None:
+            return
+        if event.inaxes != self.line.axes:
+            return
+        x0, y0, xpress, ypress = self.press
+        dx = event.xdata - xpress
+        self.line.set_xdata(x0 + dx)
+        self.line.figure.canvas.draw()
+
+    def on_release(self, event):
+        """Handle release."""
+        if event.inaxes != self.line.axes or self.press is None:
+            return
+        self.press = None
+        self.line.figure.canvas.draw()
+        self.callback(self.x0, event.xdata)
+        self.x0 = event.xdata
+
+    def remove(self):
+        """Remove the line."""
+        self.line.figure.canvas.mpl_disconnect(self.cidpress)
+        self.line.figure.canvas.mpl_disconnect(self.cidrelease)
+        self.line.figure.canvas.mpl_disconnect(self.cidmotion)
+        self.line.figure.axes[0].lines.remove(self.line)
+
+
+def _set_ax_facecolor(ax, face_color):
+    """Fix call for old MPL."""
+    try:
+        ax.set_facecolor(face_color)
+    except AttributeError:
+        ax.set_axis_bgcolor(face_color)
+
+
+def _setup_ax_spines(axes, vlines, tmin, tmax, invert_y=False,
+                     ymax_bound=None, unit=None, truncate_xaxis=True):
+    ymin, ymax = axes.get_ylim()
+    y_range = -np.subtract(ymin, ymax)
+
+    # style the spines/axes
+    axes.spines["top"].set_position('zero')
+    if truncate_xaxis is True:
+        axes.spines["top"].set_smart_bounds(True)
+    else:
+        axes.spines['top'].set_bounds(tmin, tmax)
+
+    axes.tick_params(direction='out')
+    axes.tick_params(right="off")
+
+    current_ymin = axes.get_ylim()[0]
+
+    # set x label
+    axes.set_xlabel('Time (s)')
+    axes.xaxis.get_label().set_verticalalignment('center')
+
+    # set y label and ylabel position
+    if unit is not None:
+        axes.set_ylabel(unit + "\n", rotation=90)
+        ylabel_height = (-(current_ymin / y_range)
+                         if 0 > current_ymin  # ... if we have negative values
+                         else (axes.get_yticks()[-1] / 2 / y_range))
+        axes.yaxis.set_label_coords(-0.05, 1 - ylabel_height
+                                    if invert_y else ylabel_height)
+
+    xticks = sorted(list(set([x for x in axes.get_xticks()] + vlines)))
+    axes.set_xticks(xticks)
+    axes.set_xticklabels(xticks)
+    x_extrema = [t for t in xticks if tmax >= t >= tmin]
+    if truncate_xaxis is True:
+        axes.spines['bottom'].set_bounds(x_extrema[0], x_extrema[-1])
+    else:
+        axes.spines['bottom'].set_bounds(tmin, tmax)
+    if ymin >= 0:
+        axes.spines["top"].set_color('none')
+    axes.spines["left"].set_zorder(0)
+
+    # finishing touches
+    if invert_y:
+        axes.invert_yaxis()
+    axes.spines['right'].set_color('none')
+    axes.set_xlim(tmin, tmax)
+    if truncate_xaxis is False:
+        axes.axis("tight")
+        axes.set_autoscale_on(False)
+
+
+def _handle_decim(info, decim, lowpass):
+    """Handle decim parameter for plotters."""
+    from ..evoked import _check_decim
+    from ..utils import _ensure_int
+    if isinstance(decim, string_types) and decim == 'auto':
+        lp = info['sfreq'] if info['lowpass'] is None else info['lowpass']
+        lp = min(lp, info['sfreq'] if lowpass is None else lowpass)
+        info['lowpass'] = lp
+        decim = max(int(info['sfreq'] / (lp * 3) + 1e-6), 1)
+    decim = _ensure_int(decim, 'decim', must_be='an int or "auto"')
+    if decim <= 0:
+        raise ValueError('decim must be "auto" or a positive integer, got %s'
+                         % (decim,))
+    decim = _check_decim(info, decim, 0)[0]
+    data_picks = _pick_data_channels(info, exclude=())
+    return decim, data_picks
+
+
+def _grad_pair_pick_and_name(info, picks):
+    """Deal with grads. (Helper for a few viz functions)."""
+    from ..channels.layout import _pair_grad_sensors
+    picked_chans = list()
+    pairpicks = _pair_grad_sensors(info, topomap_coords=False)
+    for ii in np.arange(0, len(pairpicks), 2):
+        first, second = pairpicks[ii], pairpicks[ii + 1]
+        if first in picks or second in picks:
+            picked_chans.append(first)
+            picked_chans.append(second)
+    picks = list(sorted(set(picked_chans)))
+    ch_names = [info["ch_names"][pick] for pick in picks]
+    return picks, ch_names
diff --git a/setup.cfg b/setup.cfg
index 39e105d..31cafbf 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -14,20 +14,23 @@ release = egg_info -RDb ''
 [bdist_rpm]
 doc-files = doc
 
-[nosetests]
-# with-coverage = 1
-# cover-html = 1
-# cover-html-dir = coverage
-cover-package = mne
-ignore-files = (?:^\.|^_,|^conf\.py|_tempita\.py|^_looper\.py$)
-
-detailed-errors = 1
-with-doctest = 1
-doctest-tests = 1
-doctest-extension = rst
-doctest-fixtures = _fixture
-#doctest-options = +ELLIPSIS,+NORMALIZE_WHITESPACE
+[tool:pytest]
+addopts = --showlocals --durations=20 --doctest-modules -rs --cov-report= --doctest-ignore-import-errors
+filterwarnings =
+    ignore::ImportWarning
+    ignore:numpy.dtype size changed
+    ignore:module pycuda not found
+    ignore:.*HasTraits.trait_.*:DeprecationWarning
+    ignore:joblib not installed:RuntimeWarning
 
 [flake8]
 exclude = __init__.py,*externals*,constants.py,fixes.py
 ignore = E241,E305
+
+[pydocstyle]
+convention = pep257
+match_dir = ^(?!\.|externals|doc|tutorials|examples|logo|tests).*$
+match = (?!test_|fixes).*\.py
+add-ignore = D100,D107,D413
+add-select = D214,D215,D404,D405,D406,D407,D408,D409,D410,D411
+ignore-decorators = ^(copy_.*_doc_to_|on_trait_change|cached_property|deprecated|property|.*setter).*
diff --git a/setup.py b/setup.py
index b542aa6..3d1496e 100755
--- a/setup.py
+++ b/setup.py
@@ -1,13 +1,13 @@
 #! /usr/bin/env python
+#
 
-# Copyright (C) 2011-2014 Alexandre Gramfort
+# Copyright (C) 2011-2017 Alexandre Gramfort
 # <alexandre.gramfort at telecom-paristech.fr>
 
 import os
 from os import path as op
 
-import setuptools  # noqa; we are using a setuptools namespace
-from numpy.distutils.core import setup
+from setuptools import setup
 
 # get the version (don't import mne here, so dependencies are not needed)
 version = None
@@ -32,6 +32,15 @@ DOWNLOAD_URL = 'http://github.com/mne-tools/mne-python'
 VERSION = version
 
 
+def package_tree(pkgroot):
+    """Get the submodule list."""
+    # Adapted from VisPy
+    path = os.path.dirname(__file__)
+    subdirs = [os.path.relpath(i[0], path).replace(os.path.sep, '.')
+               for i in os.walk(os.path.join(path, pkgroot))
+               if '__init__.py' in i[2]]
+    return sorted(subdirs)
+
 if __name__ == "__main__":
     if os.path.exists('MANIFEST'):
         os.remove('MANIFEST')
@@ -58,52 +67,7 @@ if __name__ == "__main__":
                        'Operating System :: Unix',
                        'Operating System :: MacOS'],
           platforms='any',
-          packages=['mne', 'mne.tests',
-                    'mne.beamformer', 'mne.beamformer.tests',
-                    'mne.commands', 'mne.commands.tests',
-                    'mne.connectivity', 'mne.connectivity.tests',
-                    'mne.data',
-                    'mne.datasets',
-                    'mne.datasets.eegbci',
-                    'mne.datasets._fake',
-                    'mne.datasets.megsim',
-                    'mne.datasets.misc',
-                    'mne.datasets.sample',
-                    'mne.datasets.somato',
-                    'mne.datasets.spm_face',
-                    'mne.datasets.brainstorm',
-                    'mne.datasets.testing',
-                    'mne.datasets.tests',
-                    'mne.datasets.multimodal',
-                    'mne.externals',
-                    'mne.externals.h5io',
-                    'mne.externals.tempita',
-                    'mne.io', 'mne.io.tests',
-                    'mne.io.array', 'mne.io.array.tests',
-                    'mne.io.brainvision', 'mne.io.brainvision.tests',
-                    'mne.io.bti', 'mne.io.bti.tests',
-                    'mne.io.cnt', 'mne.io.cnt.tests',
-                    'mne.io.ctf', 'mne.io.ctf.tests',
-                    'mne.io.edf', 'mne.io.edf.tests',
-                    'mne.io.egi', 'mne.io.egi.tests',
-                    'mne.io.fiff', 'mne.io.fiff.tests',
-                    'mne.io.kit', 'mne.io.kit.tests',
-                    'mne.io.nicolet', 'mne.io.nicolet.tests',
-                    'mne.io.eeglab', 'mne.io.eeglab',
-                    'mne.forward', 'mne.forward.tests',
-                    'mne.viz', 'mne.viz.tests',
-                    'mne.gui', 'mne.gui.tests',
-                    'mne.minimum_norm', 'mne.minimum_norm.tests',
-                    'mne.inverse_sparse', 'mne.inverse_sparse.tests',
-                    'mne.preprocessing', 'mne.preprocessing.tests',
-                    'mne.simulation', 'mne.simulation.tests',
-                    'mne.tests',
-                    'mne.stats', 'mne.stats.tests',
-                    'mne.time_frequency', 'mne.time_frequency.tests',
-                    'mne.realtime', 'mne.realtime.tests',
-                    'mne.decoding', 'mne.decoding.tests',
-                    'mne.commands',
-                    'mne.channels', 'mne.channels.tests'],
+          packages=package_tree('mne'),
           package_data={'mne': [op.join('data', '*.sel'),
                                 op.join('data', 'icos.fif.gz'),
                                 op.join('data', 'coil_def*.dat'),
@@ -111,6 +75,7 @@ if __name__ == "__main__":
                                 op.join('data', 'FreeSurferColorLUT.txt'),
                                 op.join('data', 'image', '*gif'),
                                 op.join('data', 'image', '*lout'),
+                                op.join('data', 'fsaverage', '*.fif'),
                                 op.join('channels', 'data', 'layouts', '*.lout'),
                                 op.join('channels', 'data', 'layouts', '*.lay'),
                                 op.join('channels', 'data', 'montages', '*.sfp'),
@@ -119,5 +84,7 @@ if __name__ == "__main__":
                                 op.join('channels', 'data', 'neighbors', '*.mat'),
                                 op.join('gui', 'help', '*.json'),
                                 op.join('html', '*.js'),
-                                op.join('html', '*.css')]},
+                                op.join('html', '*.css'),
+                                op.join('io', 'artemis123', 'resources', '*.csv')
+                                ]},
           scripts=['bin/mne'])
diff --git a/tutorials/plot_artifacts_correction_filtering.py b/tutorials/plot_artifacts_correction_filtering.py
index f886737..d1f79fe 100644
--- a/tutorials/plot_artifacts_correction_filtering.py
+++ b/tutorials/plot_artifacts_correction_filtering.py
@@ -4,7 +4,7 @@
 Filtering and resampling data
 =============================
 
-Certain artifacts are restricted to certain frequencies and can therefore
+Some artifacts are restricted to certain frequencies and can therefore
 be fixed by filtering. An artifact that typically affects only some
 frequencies is due to the power line.
 
@@ -31,7 +31,7 @@ tmin, tmax = 0, 20  # use the first 20s of data
 
 # Setup for reading the raw data (save memory by cropping the raw data
 # before loading it)
-raw = mne.io.read_raw_fif(raw_fname, add_eeg_ref=False)
+raw = mne.io.read_raw_fif(raw_fname)
 raw.crop(tmin, tmax).load_data()
 raw.info['bads'] = ['MEG 2443', 'EEG 053']  # bads + 2 more
 
@@ -44,7 +44,7 @@ picks = mne.pick_types(raw.info, meg='mag', eeg=False, eog=False,
                        stim=False, exclude='bads', selection=selection)
 
 # Let's first check out all channel types
-raw.plot_psd(area_mode='range', tmax=10.0, picks=picks)
+raw.plot_psd(area_mode='range', tmax=10.0, picks=picks, average=False)
 
 ###############################################################################
 # Removing power-line noise with notch filtering
@@ -55,7 +55,7 @@ raw.plot_psd(area_mode='range', tmax=10.0, picks=picks)
 
 raw.notch_filter(np.arange(60, 241, 60), picks=picks, filter_length='auto',
                  phase='zero')
-raw.plot_psd(area_mode='range', tmax=10.0, picks=picks)
+raw.plot_psd(area_mode='range', tmax=10.0, picks=picks, average=False)
 
 ###############################################################################
 # Removing power-line noise with low-pass filtering
@@ -65,9 +65,8 @@ raw.plot_psd(area_mode='range', tmax=10.0, picks=picks)
 # noise you can simply low pass filter the data.
 
 # low pass filtering below 50 Hz
-raw.filter(None, 50., h_trans_bandwidth='auto', filter_length='auto',
-           phase='zero')
-raw.plot_psd(area_mode='range', tmax=10.0, picks=picks)
+raw.filter(None, 50., fir_design='firwin')
+raw.plot_psd(area_mode='range', tmax=10.0, picks=picks, average=False)
 
 ###############################################################################
 # High-pass filtering to remove slow drifts
@@ -75,13 +74,16 @@ raw.plot_psd(area_mode='range', tmax=10.0, picks=picks)
 #
 # To remove slow drifts, you can high pass.
 #
-# .. warning:: There can be issues using high-passes greater than 0.1 Hz
-#              (see examples in :ref:`tut_filtering_hp_problems`),
-#              so apply high-pass filters with caution.
+# .. warning:: In several applications such as event-related potential (ERP)
+#              and event-related field (ERF) analysis, high-pass filters with
+#              cutoff frequencies greater than 0.1 Hz are usually considered
+#              problematic since they significantly change the shape of the
+#              resulting averaged waveform (see examples in
+#              :ref:`tut_filtering_hp_problems`). In such applications, apply
+#              high-pass filters with caution.
 
-raw.filter(1., None, l_trans_bandwidth='auto', filter_length='auto',
-           phase='zero')
-raw.plot_psd(area_mode='range', tmax=10.0, picks=picks)
+raw.filter(1., None, fir_design='firwin')
+raw.plot_psd(area_mode='range', tmax=10.0, picks=picks, average=False)
 
 
 ###############################################################################
@@ -89,8 +91,7 @@ raw.plot_psd(area_mode='range', tmax=10.0, picks=picks)
 # a so-called *band-pass* filter by running the following:
 
 # band-pass filtering in the range 1 Hz - 50 Hz
-raw.filter(1, 50., l_trans_bandwidth='auto', h_trans_bandwidth='auto',
-           filter_length='auto', phase='zero')
+raw.filter(1, 50., fir_design='firwin')
 
 ###############################################################################
 # Downsampling and decimation
diff --git a/tutorials/plot_artifacts_correction_ica.py b/tutorials/plot_artifacts_correction_ica.py
index 2d900e0..a56c3bd 100644
--- a/tutorials/plot_artifacts_correction_ica.py
+++ b/tutorials/plot_artifacts_correction_ica.py
@@ -13,8 +13,11 @@ These components have to be correctly identified and removed.
 
 If EOG or ECG recordings are available, they can be used in ICA to
 automatically select the corresponding artifact components from the
-decomposition. To do so, you have to first build an Epoch object around
-blink or heartbeat event.
+decomposition. To do so, you have to first build an :class:`mne.Epochs` object
+around blink or heartbeat events.
+
+ICA is implemented in MNE using the :class:`mne.preprocessing.ICA` class,
+which we will review here.
 """
 
 import numpy as np
@@ -29,8 +32,9 @@ from mne.preprocessing import create_eog_epochs, create_ecg_epochs
 data_path = sample.data_path()
 raw_fname = data_path + '/MEG/sample/sample_audvis_filt-0-40_raw.fif'
 
-raw = mne.io.read_raw_fif(raw_fname, preload=True, add_eeg_ref=False)
-raw.filter(1, 40, n_jobs=2)  # 1Hz high pass is often helpful for fitting ICA
+raw = mne.io.read_raw_fif(raw_fname, preload=True)
+# 1Hz high pass is often helpful for fitting ICA
+raw.filter(1., 40., n_jobs=2, fir_design='firwin')
 
 picks_meg = mne.pick_types(raw.info, meg=True, eeg=False, eog=False,
                            stim=False, exclude='bads')
@@ -38,6 +42,19 @@ picks_meg = mne.pick_types(raw.info, meg=True, eeg=False, eog=False,
 ###############################################################################
 # Before applying artifact correction please learn about your actual artifacts
 # by reading :ref:`tut_artifacts_detect`.
+#
+# .. warning:: ICA is sensitive to low-frequency drifts and therefore
+#              requires the data to be high-pass filtered prior to fitting.
+#              Typically, a cutoff frequency of 1 Hz is recommended. Note that
+#              FIR filters prior to MNE 0.15 used the ``'firwin2'`` design
+#              method, which generally produces rather shallow filters that
+#              might not work for ICA processing. Therefore, it is recommended
+#              to use IIR filters for MNE up to 0.14. In MNE 0.15, FIR filters
+#              can be designed with the ``'firwin'`` method, which generally
+#              produces much steeper filters. This method will be the default
+#              FIR design method in MNE 0.16. In MNE 0.15, you need to
+#              explicitly set ``fir_design='firwin'`` to use this method. This
+#              is the recommended filter method for ICA preprocessing.
 
 ###############################################################################
 # Fit ICA
@@ -68,7 +85,6 @@ print(ica)
 
 ###############################################################################
 # Plot ICA components
-
 ica.plot_components()  # can you spot some potential bad guys?
 
 
@@ -109,8 +125,6 @@ ica.plot_properties(raw, picks=[1, 2], psd_args={'fmax': 35.})
 eog_average = create_eog_epochs(raw, reject=dict(mag=5e-12, grad=4000e-13),
                                 picks=picks_meg).average()
 
-# We simplify things by setting the maximum number of components to reject
-n_max_eog = 1  # here we bet on finding the vertical EOG components
 eog_epochs = create_eog_epochs(raw, reject=reject)  # get single EOG trials
 eog_inds, scores = ica.find_bads_eog(eog_epochs)  # find via correlation
 
@@ -132,7 +146,7 @@ ica.plot_properties(eog_epochs, picks=eog_inds, psd_args={'fmax': 35.},
 # That component is showing a prototypical average vertical EOG time course.
 #
 # Pay attention to the labels, a customized read-out of the
-# :attr:`ica.labels_ <mne.preprocessing.ICA.labels_>`
+# ``mne.preprocessing.ICA.labels_``:
 print(ica.labels_)
 
 ###############################################################################
@@ -141,7 +155,7 @@ print(ica.labels_)
 # components.
 #
 # Now let's see how we would modify our signals if we removed this component
-# from the data
+# from the data.
 ica.plot_overlay(eog_average, exclude=eog_inds, show=False)
 # red -> before, black -> after. Yes! We remove quite a lot!
 
@@ -158,12 +172,21 @@ ica.exclude.extend(eog_inds)
 # ica = read_ica('my-ica.fif')
 
 ###############################################################################
+# Note that nothing is yet removed from the raw data. To remove the effects of
+# the rejected components,
+# :meth:`the apply method <mne.preprocessing.ICA.apply>` must be called.
+# Here we apply it on the copy of the first ten seconds, so that the rest of
+# this tutorial still works as intended.
+raw_copy = raw.copy().crop(0, 10)
+ica.apply(raw_copy)
+raw_copy.plot()  # check the result
+
+###############################################################################
 # Exercise: find and remove ECG artifacts using ICA!
 ecg_epochs = create_ecg_epochs(raw, tmin=-.5, tmax=.5)
 ecg_inds, scores = ica.find_bads_ecg(ecg_epochs, method='ctps')
 ica.plot_properties(ecg_epochs, picks=ecg_inds, psd_args={'fmax': 35.})
 
-
 ###############################################################################
 # What if we don't have an EOG channel?
 # -------------------------------------
@@ -199,8 +222,8 @@ from mne.preprocessing.ica import corrmap  # noqa
 # data sets instead.
 
 # We'll start by simulating a group of subjects or runs from a subject
-start, stop = [0, len(raw.times) - 1]
-intervals = np.linspace(start, stop, 4, dtype=int)
+start, stop = [0, raw.times[-1]]
+intervals = np.linspace(start, stop, 4, dtype=np.float)
 icas_from_other_data = list()
 raw.pick_types(meg=True, eeg=False)  # take only MEG channels
 for ii, start in enumerate(intervals):
@@ -236,20 +259,31 @@ reference_ica.plot_sources(eog_average, exclude=eog_inds)
 # Indeed it looks like an EOG, also in the average time course.
 #
 # We construct a list where our reference run is the first element. Then we
-# can detect similar components from the other runs using
-# :func:`mne.preprocessing.corrmap`. So our template must be a tuple like
+# can detect similar components from the other runs (the other ICA objects)
+# using :func:`mne.preprocessing.corrmap`. So our template must be a tuple like
 # (reference_run_index, component_index):
 icas = [reference_ica] + icas_from_other_data
 template = (0, eog_inds[0])
 
 ###############################################################################
-# Now we can do the corrmap.
+# Now we can run the CORRMAP algorithm.
 fig_template, fig_detected = corrmap(icas, template=template, label="blinks",
                                      show=True, threshold=.8, ch_type='mag')
 
 ###############################################################################
 # Nice, we have found similar ICs from the other (simulated) runs!
-# This is even nicer if we have 20 or 100 ICA solutions in a list.
+# In this way, you can detect a type of artifact semi-automatically for example
+# for all subjects in a study.
+# The detected template can also be retrieved as an array and stored; this
+# array can be used as an alternative template to
+# :func:`mne.preprocessing.corrmap`.
+eog_component = reference_ica.get_components()[:, eog_inds[0]]
+
+###############################################################################
+# If you calculate a new ICA solution, you can provide this array instead of
+# specifying the template in reference to the list of ICA objects you want
+# to run CORRMAP on. (Of course, the retrieved component map arrays can
+# also be used for other purposes than artifact correction.)
 #
 # You can also use SSP to correct for artifacts. It is a bit simpler and
 # faster but also less precise than ICA and requires that you know the event
diff --git a/tutorials/plot_artifacts_correction_maxwell_filtering.py b/tutorials/plot_artifacts_correction_maxwell_filtering.py
index eff9419..6360a02 100644
--- a/tutorials/plot_artifacts_correction_maxwell_filtering.py
+++ b/tutorials/plot_artifacts_correction_maxwell_filtering.py
@@ -24,7 +24,7 @@ fine_cal_fname = data_path + '/SSS/sss_cal_mgh.dat'
 
 ###############################################################################
 # Preprocess with Maxwell filtering
-raw = mne.io.read_raw_fif(raw_fname, add_eeg_ref=False)
+raw = mne.io.read_raw_fif(raw_fname)
 raw.info['bads'] = ['MEG 2443', 'EEG 053', 'MEG 1032', 'MEG 2313']  # set bads
 # Here we don't use tSSS (set st_duration) because MGH data is very clean
 raw_sss = maxwell_filter(raw, cross_talk=ctc_fname, calibration=fine_cal_fname)
@@ -38,8 +38,7 @@ picks = mne.pick_types(raw.info, meg=True, eeg=False, stim=False, eog=True,
                        include=[], exclude='bads')
 for r, kind in zip((raw, raw_sss), ('Raw data', 'Maxwell filtered data')):
     epochs = mne.Epochs(r, events, event_id, tmin, tmax, picks=picks,
-                        baseline=(None, 0), reject=dict(eog=150e-6),
-                        preload=False)
+                        baseline=(None, 0), reject=dict(eog=150e-6))
     evoked = epochs.average()
     evoked.plot(window_title=kind, ylim=dict(grad=(-200, 250),
                                              mag=(-600, 700)))
diff --git a/tutorials/plot_artifacts_correction_rejection.py b/tutorials/plot_artifacts_correction_rejection.py
index affdfef..8a4dcfe 100644
--- a/tutorials/plot_artifacts_correction_rejection.py
+++ b/tutorials/plot_artifacts_correction_rejection.py
@@ -12,8 +12,8 @@ from mne.datasets import sample
 
 data_path = sample.data_path()
 raw_fname = data_path + '/MEG/sample/sample_audvis_filt-0-40_raw.fif'
-raw = mne.io.read_raw_fif(raw_fname, add_eeg_ref=False)
-raw.set_eeg_reference()
+raw = mne.io.read_raw_fif(raw_fname)
+raw.set_eeg_reference('average', projection=True)
 
 ###############################################################################
 # .. _marking_bad_channels:
@@ -130,9 +130,13 @@ onset = eog_events[:, 0] / raw.info['sfreq'] - 0.25
 duration = np.repeat(0.5, n_blinks)
 raw.annotations = mne.Annotations(onset, duration, ['bad blink'] * n_blinks,
                                   orig_time=raw.info['meas_date'])
+print(raw.annotations)  # to get information about what annotations we have
 raw.plot(events=eog_events)  # To see the annotated segments.
 
 ###############################################################################
+# It is also possible to draw bad segments interactively using
+# :meth:`raw.plot <mne.io.Raw.plot>` (see :ref:`tut_viz_raw`).
+#
 # As the data is epoched, all the epochs overlapping with segments whose
 # description starts with 'bad' are rejected by default. To turn rejection off,
 # use keyword argument ``reject_by_annotation=False`` when constructing
@@ -178,7 +182,7 @@ picks_meg = mne.pick_types(raw.info, meg=True, eeg=False, eog=True,
                            stim=False, exclude='bads')
 epochs = mne.Epochs(raw, events, event_id, tmin, tmax, proj=True,
                     picks=picks_meg, baseline=baseline, reject=reject,
-                    reject_by_annotation=True, add_eeg_ref=False)
+                    reject_by_annotation=True)
 
 ###############################################################################
 # We then drop/reject the bad epochs
diff --git a/tutorials/plot_artifacts_correction_ssp.py b/tutorials/plot_artifacts_correction_ssp.py
index bff1cc5..b42c089 100644
--- a/tutorials/plot_artifacts_correction_ssp.py
+++ b/tutorials/plot_artifacts_correction_ssp.py
@@ -16,27 +16,29 @@ from mne.preprocessing import compute_proj_ecg, compute_proj_eog
 data_path = sample.data_path()
 raw_fname = data_path + '/MEG/sample/sample_audvis_filt-0-40_raw.fif'
 
-raw = mne.io.read_raw_fif(raw_fname, preload=True, add_eeg_ref=False)
-raw.set_eeg_reference()
-raw.pick_types(meg=True, ecg=True, eog=True, stim=True)
+raw = mne.io.read_raw_fif(raw_fname, preload=True)
 
 ##############################################################################
 # Compute SSP projections
 # -----------------------
+#
+# First let's do ECG.
 
-projs, events = compute_proj_ecg(raw, n_grad=1, n_mag=1, average=True)
+projs, events = compute_proj_ecg(raw, n_grad=1, n_mag=1, n_eeg=0, average=True)
 print(projs)
 
 ecg_projs = projs[-2:]
 mne.viz.plot_projs_topomap(ecg_projs)
 
-# Now for EOG
+###############################################################################
+# Now let's do EOG. Here we compute an EEG projector, and need to pass
+# the measurement info so the topomap coordinates can be created.
 
-projs, events = compute_proj_eog(raw, n_grad=1, n_mag=1, average=True)
+projs, events = compute_proj_eog(raw, n_grad=1, n_mag=1, n_eeg=1, average=True)
 print(projs)
 
-eog_projs = projs[-2:]
-mne.viz.plot_projs_topomap(eog_projs)
+eog_projs = projs[-3:]
+mne.viz.plot_projs_topomap(eog_projs, info=raw.info)
 
 ##############################################################################
 # Apply SSP projections
@@ -82,7 +84,7 @@ evoked = mne.Epochs(raw, events, event_id, tmin=-0.2, tmax=0.5,
 # set time instants in seconds (from 50 to 150ms in a step of 10ms)
 times = np.arange(0.05, 0.15, 0.01)
 
-evoked.plot_topomap(times, proj='interactive')
+fig = evoked.plot_topomap(times, proj='interactive')
 
 ##############################################################################
 # now you should see checkboxes. Remove a few SSP and see how the auditory
diff --git a/tutorials/plot_artifacts_detection.py b/tutorials/plot_artifacts_detection.py
index e38a945..eec68e8 100644
--- a/tutorials/plot_artifacts_detection.py
+++ b/tutorials/plot_artifacts_detection.py
@@ -63,6 +63,7 @@ This tutorial discusses a couple of major artifacts that most analyses
 have to deal with and demonstrates how to detect them.
 
 """
+import numpy as np
 
 import mne
 from mne.datasets import sample
@@ -86,7 +87,7 @@ raw = mne.io.read_raw_fif(raw_fname, preload=True)
 # we see high amplitude undulations in low frequencies, spanning across tens of
 # seconds
 
-raw.plot_psd(fmax=250)
+raw.plot_psd(tmax=np.inf, fmax=250)
 
 ###############################################################################
 # On MEG sensors we see narrow frequency peaks at 60, 120, 180, 240 Hz,
diff --git a/tutorials/plot_background_filtering.py b/tutorials/plot_background_filtering.py
index 0db34d7..c9b15f4 100644
--- a/tutorials/plot_background_filtering.py
+++ b/tutorials/plot_background_filtering.py
@@ -96,7 +96,7 @@ In MNE-Python we default to using FIR filtering. As noted in Widmann *et al.*
 
     Despite IIR filters often being considered as computationally more
     efficient, they are recommended only when high throughput and sharp
-    cutoffs are required (Ifeachor and Jervis, 2002[2]_, p. 321),
+    cutoffs are required (Ifeachor and Jervis, 2002 [2]_, p. 321),
     ...FIR filters are easier to control, are always stable, have a
     well-defined passband, can be corrected to zero-phase without
     additional computations, and can be converted to minimum-phase.
@@ -139,14 +139,13 @@ from scipy import signal, fftpack
 import matplotlib.pyplot as plt
 
 from mne.time_frequency.tfr import morlet
+from mne.viz import plot_filter, plot_ideal_filter
 
 import mne
 
 sfreq = 1000.
 f_p = 40.
-ylim = [-60, 10]  # for dB plots
-xlim = [2, sfreq / 2.]
-blue = '#1f77b4'
+flim = (1., sfreq / 2.)  # limits for plotting
 
 ###############################################################################
 # Take for example an ideal low-pass filter, which would give a value of 1 in
@@ -158,45 +157,9 @@ nyq = sfreq / 2.  # the Nyquist frequency is half our sample rate
 freq = [0, f_p, f_p, nyq]
 gain = [1, 1, 0, 0]
 
-
-def box_off(ax):
-    ax.grid(zorder=0)
-    for key in ('top', 'right'):
-        ax.spines[key].set_visible(False)
-
-
-def plot_ideal(freq, gain, ax):
-    freq = np.maximum(freq, xlim[0])
-    xs, ys = list(), list()
-    my_freq, my_gain = list(), list()
-    for ii in range(len(freq)):
-        xs.append(freq[ii])
-        ys.append(ylim[0])
-        if ii < len(freq) - 1 and gain[ii] != gain[ii + 1]:
-            xs += [freq[ii], freq[ii + 1]]
-            ys += [ylim[1]] * 2
-            my_freq += np.linspace(freq[ii], freq[ii + 1], 20,
-                                   endpoint=False).tolist()
-            my_gain += np.linspace(gain[ii], gain[ii + 1], 20,
-                                   endpoint=False).tolist()
-        else:
-            my_freq.append(freq[ii])
-            my_gain.append(gain[ii])
-    my_gain = 10 * np.log10(np.maximum(my_gain, 10 ** (ylim[0] / 10.)))
-    ax.fill_between(xs, ylim[0], ys, color='r', alpha=0.1)
-    ax.semilogx(my_freq, my_gain, 'r--', alpha=0.5, linewidth=4, zorder=3)
-    xticks = [1, 2, 4, 10, 20, 40, 100, 200, 400]
-    ax.set(xlim=xlim, ylim=ylim, xticks=xticks, xlabel='Frequency (Hz)',
-           ylabel='Amplitude (dB)')
-    ax.set(xticklabels=xticks)
-    box_off(ax)
-
-half_height = np.array(plt.rcParams['figure.figsize']) * [1, 0.5]
-ax = plt.subplots(1, figsize=half_height)[1]
-plot_ideal(freq, gain, ax)
-ax.set(title='Ideal %s Hz lowpass' % f_p)
-mne.viz.tight_layout()
-plt.show()
+third_height = np.array(plt.rcParams['figure.figsize']) * [1, 1. / 3.]
+ax = plt.subplots(1, figsize=third_height)[1]
+plot_ideal_filter(freq, gain, ax, title='Ideal %s Hz lowpass' % f_p, flim=flim)
 
 ###############################################################################
 # This filter hypothetically achieves zero ripple in the frequency domain,
@@ -214,46 +177,19 @@ plt.show()
 n = int(round(0.1 * sfreq)) + 1
 t = np.arange(-n // 2, n // 2) / sfreq  # center our sinc
 h = np.sinc(2 * f_p * t) / (4 * np.pi)
-
-
-def plot_filter(h, title, freq, gain, show=True):
-    if h.ndim == 2:  # second-order sections
-        sos = h
-        n = mne.filter.estimate_ringing_samples(sos)
-        h = np.zeros(n)
-        h[0] = 1
-        h = signal.sosfilt(sos, h)
-        H = np.ones(512, np.complex128)
-        for section in sos:
-            f, this_H = signal.freqz(section[:3], section[3:])
-            H *= this_H
-    else:
-        f, H = signal.freqz(h)
-    fig, axs = plt.subplots(2)
-    t = np.arange(len(h)) / sfreq
-    axs[0].plot(t, h, color=blue)
-    axs[0].set(xlim=t[[0, -1]], xlabel='Time (sec)',
-               ylabel='Amplitude h(n)', title=title)
-    box_off(axs[0])
-    f *= sfreq / (2 * np.pi)
-    axs[1].semilogx(f, 10 * np.log10((H * H.conj()).real), color=blue,
-                    linewidth=2, zorder=4)
-    plot_ideal(freq, gain, axs[1])
-    mne.viz.tight_layout()
-    if show:
-        plt.show()
-
-plot_filter(h, 'Sinc (0.1 sec)', freq, gain)
+plot_filter(h, sfreq, freq, gain, 'Sinc (0.1 sec)', flim=flim)
 
 ###############################################################################
 # This is not so good! Making the filter 10 times longer (1 sec) gets us a
 # bit better stop-band suppression, but still has a lot of ringing in
-# the time domain. Note the x-axis is an order of magnitude longer here:
+# the time domain. Note the x-axis is an order of magnitude longer here,
+# and the filter has a correspondingly much longer group delay (again equal
+# to half the filter length, or 0.5 seconds):
 
 n = int(round(1. * sfreq)) + 1
 t = np.arange(-n // 2, n // 2) / sfreq
 h = np.sinc(2 * f_p * t) / (4 * np.pi)
-plot_filter(h, 'Sinc (1.0 sec)', freq, gain)
+plot_filter(h, sfreq, freq, gain, 'Sinc (1.0 sec)', flim=flim)
 
 ###############################################################################
 # Let's make the stop-band tighter still with a longer filter (10 sec),
@@ -262,7 +198,7 @@ plot_filter(h, 'Sinc (1.0 sec)', freq, gain)
 n = int(round(10. * sfreq)) + 1
 t = np.arange(-n // 2, n // 2) / sfreq
 h = np.sinc(2 * f_p * t) / (4 * np.pi)
-plot_filter(h, 'Sinc (10.0 sec)', freq, gain)
+plot_filter(h, sfreq, freq, gain, 'Sinc (10.0 sec)', flim=flim)
 
 ###############################################################################
 # Now we have very sharp frequency suppression, but our filter rings for the
@@ -273,7 +209,8 @@ plot_filter(h, 'Sinc (10.0 sec)', freq, gain)
 # based on desired response characteristics. These include:
 #
 #     1. The Remez_ algorithm (:func:`scipy.signal.remez`, `MATLAB firpm`_)
-#     2. Windowed FIR design (:func:`scipy.signal.firwin2`, `MATLAB fir2`_)
+#     2. Windowed FIR design (:func:`scipy.signal.firwin2`, `MATLAB fir2`_
+#        and :func:`scipy.signal.firwin`)
 #     3. Least squares designs (:func:`scipy.signal.firls`, `MATLAB firls`_)
 #     4. Frequency-domain design (construct filter in Fourier
 #        domain and use an :func:`IFFT <scipy.fftpack.ifft>` to invert it)
@@ -299,11 +236,9 @@ f_s = f_p + trans_bandwidth  # = 50 Hz
 
 freq = [0., f_p, f_s, nyq]
 gain = [1., 1., 0., 0.]
-ax = plt.subplots(1, figsize=half_height)[1]
-plot_ideal(freq, gain, ax)
-ax.set(title='%s Hz lowpass with a %s Hz transition' % (f_p, trans_bandwidth))
-mne.viz.tight_layout()
-plt.show()
+ax = plt.subplots(1, figsize=third_height)[1]
+title = '%s Hz lowpass with a %s Hz transition' % (f_p, trans_bandwidth)
+plot_ideal_filter(freq, gain, ax, title=title, flim=flim)
 
 ###############################################################################
 # Accepting a shallower roll-off of the filter in the frequency domain makes
@@ -312,7 +247,8 @@ plt.show()
 # domain signal. Here again for the 1 sec filter:
 
 h = signal.firwin2(n, freq, gain, nyq=nyq)
-plot_filter(h, 'Windowed 10-Hz transition (1.0 sec)', freq, gain)
+plot_filter(h, sfreq, freq, gain, 'Windowed 10-Hz transition (1.0 sec)',
+            flim=flim)
 
 ###############################################################################
 # Since our lowpass is around 40 Hz with a 10 Hz transition, we can actually
@@ -321,7 +257,8 @@ plot_filter(h, 'Windowed 10-Hz transition (1.0 sec)', freq, gain)
 
 n = int(round(sfreq * 0.5)) + 1
 h = signal.firwin2(n, freq, gain, nyq=nyq)
-plot_filter(h, 'Windowed 10-Hz transition (0.5 sec)', freq, gain)
+plot_filter(h, sfreq, freq, gain, 'Windowed 10-Hz transition (0.5 sec)',
+            flim=flim)
 
 ###############################################################################
 # But then if we shorten the filter too much (2 cycles of 10 Hz = 0.2 sec),
@@ -329,7 +266,8 @@ plot_filter(h, 'Windowed 10-Hz transition (0.5 sec)', freq, gain)
 
 n = int(round(sfreq * 0.2)) + 1
 h = signal.firwin2(n, freq, gain, nyq=nyq)
-plot_filter(h, 'Windowed 10-Hz transition (0.2 sec)', freq, gain)
+plot_filter(h, sfreq, freq, gain, 'Windowed 10-Hz transition (0.2 sec)',
+            flim=flim)
 
 ###############################################################################
 # If we want a filter that is only 0.1 seconds long, we should probably use
@@ -339,7 +277,35 @@ trans_bandwidth = 25
 f_s = f_p + trans_bandwidth
 freq = [0, f_p, f_s, nyq]
 h = signal.firwin2(n, freq, gain, nyq=nyq)
-plot_filter(h, 'Windowed 50-Hz transition (0.2 sec)', freq, gain)
+plot_filter(h, sfreq, freq, gain, 'Windowed 50-Hz transition (0.2 sec)',
+            flim=flim)
+
+###############################################################################
+# So far we have only discussed *acausal* filtering, which means that each
+# sample at each time point :math:`t` is filtered using samples that come
+# after (:math:`t + \Delta t`) *and* before (:math:`t - \Delta t`) :math:`t`.
+# In this sense, each sample is influenced by samples that come both before
+# and after it. This is useful in many cases, espcially because it does not
+# delay the timing of events.
+#
+# However, sometimes it can be beneficial to use *causal* filtering,
+# whereby each sample :math:`t` is filtered only using time points that came
+# after it.
+#
+# Note that the delay is variable (whereas for linear/zero-phase filters it
+# is constant) but small in the pass-band. Unlike zero-phase filters, which
+# require time-shifting backward the output of a linear-phase filtering stage
+# (and thus becoming acausal), minimum-phase filters do not require any
+# compensation to achieve small delays in the passband. Note that as an
+# artifact of the minimum phase filter construction step, the filter does
+# not end up being as steep as the linear/zero-phase version.
+#
+# We can construct a minimum-phase filter from our existing linear-phase
+# filter with the ``minimum_phase`` function (that will be in SciPy 0.19's
+# :mod:`scipy.signal`), and note that the falloff is not as steep:
+
+h_min = mne.fixes.minimum_phase(h)
+plot_filter(h_min, sfreq, freq, gain, 'Minimum-phase', flim=flim)
 
 ###############################################################################
 # .. _tut_effect_on_signals:
@@ -362,7 +328,7 @@ tlim = [center - 0.2, center + 0.2]
 tticks = [tlim[0], center, tlim[1]]
 flim = [20, 70]
 
-x = np.zeros(int(sfreq * dur))
+x = np.zeros(int(sfreq * dur) + 1)
 blip = morlet(sfreq, [morlet_freq], n_cycles=7)[0].imag / 20.
 n_onset = int(center * sfreq) - len(blip) // 2
 x[n_onset:n_onset + len(blip)] += blip
@@ -373,8 +339,27 @@ x += rng.randn(len(x)) / 1000.
 x += np.sin(2. * np.pi * 60. * np.arange(len(x)) / sfreq) / 2000.
 
 ###############################################################################
-# Filter it with a shallow cutoff, linear-phase FIR and compensate for
-# the delay:
+# Filter it with a shallow cutoff, linear-phase FIR (which allows us to
+# compensate for the constant filter delay):
+
+transition_band = 0.25 * f_p
+f_s = f_p + transition_band
+filter_dur = 6.6 / transition_band / 2.  # sec
+n = int(sfreq * filter_dur)
+freq = [0., f_p, f_s, sfreq / 2.]
+gain = [1., 1., 0., 0.]
+# This would be equivalent:
+h = mne.filter.create_filter(x, sfreq, l_freq=None, h_freq=f_p,
+                             fir_design='firwin')
+x_v16 = np.convolve(h, x)[len(h) // 2:]
+
+plot_filter(h, sfreq, freq, gain, 'MNE-Python 0.16 default', flim=flim)
+
+###############################################################################
+# Filter it with a different design mode ``fir_design="firwin2"``, and also
+# compensate for the constant filter delay. This method does not produce
+# quite as sharp a transition compared to ``fir_design="firwin"``, despite
+# being twice as long:
 
 transition_band = 0.25 * f_p
 f_s = f_p + transition_band
@@ -382,10 +367,13 @@ filter_dur = 6.6 / transition_band  # sec
 n = int(sfreq * filter_dur)
 freq = [0., f_p, f_s, sfreq / 2.]
 gain = [1., 1., 0., 0.]
-h = signal.firwin2(n, freq, gain, nyq=sfreq / 2.)
-x_shallow = np.convolve(h, x)[len(h) // 2:]
+# This would be equivalent:
+# h = signal.firwin2(n, freq, gain, nyq=sfreq / 2.)
+h = mne.filter.create_filter(x, sfreq, l_freq=None, h_freq=f_p,
+                             fir_design='firwin2')
+x_v14 = np.convolve(h, x)[len(h) // 2:]
 
-plot_filter(h, 'MNE-Python 0.14 default', freq, gain)
+plot_filter(h, sfreq, freq, gain, 'MNE-Python 0.14 default', flim=flim)
 
 ###############################################################################
 # This is actually set to become the default type of filter used in MNE-Python
@@ -400,15 +388,19 @@ filter_dur = 10.  # sec
 n = int(sfreq * filter_dur)
 freq = [0., f_p, f_s, sfreq / 2.]
 gain = [1., 1., 0., 0.]
-h = signal.firwin2(n, freq, gain, nyq=sfreq / 2.)
-x_steep = np.convolve(np.convolve(h, x)[::-1], h)[::-1][len(h) - 1:-len(h) - 1]
+# This would be equivalent
+# h = signal.firwin2(n, freq, gain, nyq=sfreq / 2.)
+h = mne.filter.create_filter(x, sfreq, l_freq=None, h_freq=f_p,
+                             h_trans_bandwidth=transition_band,
+                             filter_length='%ss' % filter_dur,
+                             fir_design='firwin2')
+x_v13 = np.convolve(np.convolve(h, x)[::-1], h)[::-1][len(h) - 1:-len(h) - 1]
 
-plot_filter(h, 'MNE-Python 0.13 default', freq, gain)
+plot_filter(h, sfreq, freq, gain, 'MNE-Python 0.13 default', flim=flim)
 
 ###############################################################################
-# Finally, Let's also filter it with the
-# MNE-C default, which is a long-duration steep-slope FIR filter designed
-# using frequency-domain techniques:
+# Let's also filter it with the MNE-C default, which is a long-duration
+# steep-slope FIR filter designed using frequency-domain techniques:
 
 h = mne.filter.design_mne_c_filter(sfreq, l_freq=None, h_freq=f_p + 2.5)
 x_mne_c = np.convolve(h, x)[len(h) // 2:]
@@ -417,7 +409,22 @@ transition_band = 5  # Hz (default in MNE-C)
 f_s = f_p + transition_band
 freq = [0., f_p, f_s, sfreq / 2.]
 gain = [1., 1., 0., 0.]
-plot_filter(h, 'MNE-C default', freq, gain)
+plot_filter(h, sfreq, freq, gain, 'MNE-C default', flim=flim)
+
+###############################################################################
+# And now an example of a minimum-phase filter:
+
+h = mne.filter.create_filter(x, sfreq, l_freq=None, h_freq=f_p,
+                             phase='minimum', fir_design='firwin')
+x_min = np.convolve(h, x)
+transition_band = 0.25 * f_p
+f_s = f_p + transition_band
+filter_dur = 6.6 / transition_band  # sec
+n = int(sfreq * filter_dur)
+freq = [0., f_p, f_s, sfreq / 2.]
+gain = [1., 1., 0., 0.]
+plot_filter(h, sfreq, freq, gain, 'Minimum-phase filter', flim=flim)
+
 
 ###############################################################################
 # Both the MNE-Python 0.13 and MNE-C filhters have excellent frequency
@@ -428,38 +435,37 @@ plot_filter(h, 'MNE-C default', freq, gain)
 # and the time-domain ringing is thus more pronounced for the steep-slope,
 # long-duration filter than the shorter, shallower-slope filter:
 
-axs = plt.subplots(1, 2)[1]
+axes = plt.subplots(1, 2)[1]
 
 
 def plot_signal(x, offset):
     t = np.arange(len(x)) / sfreq
-    axs[0].plot(t, x + offset)
-    axs[0].set(xlabel='Time (sec)', xlim=t[[0, -1]])
-    box_off(axs[0])
+    axes[0].plot(t, x + offset)
+    axes[0].set(xlabel='Time (sec)', xlim=t[[0, -1]])
     X = fftpack.fft(x)
     freqs = fftpack.fftfreq(len(x), 1. / sfreq)
     mask = freqs >= 0
     X = X[mask]
     freqs = freqs[mask]
-    axs[1].plot(freqs, 20 * np.log10(np.abs(X)))
-    axs[1].set(xlim=xlim)
+    axes[1].plot(freqs, 20 * np.log10(np.abs(X)))
+    axes[1].set(xlim=flim)
 
-yticks = np.arange(5) / -30.
-yticklabels = ['Original', 'Noisy', 'FIR-shallow (0.14)', 'FIR-steep (0.13)',
-               'FIR-steep (MNE-C)']
+yticks = np.arange(7) / -30.
+yticklabels = ['Original', 'Noisy', 'FIR-firwin (0.16)', 'FIR-firwin2 (0.14)',
+               'FIR-steep (0.13)', 'FIR-steep (MNE-C)', 'Minimum-phase']
 plot_signal(x_orig, offset=yticks[0])
 plot_signal(x, offset=yticks[1])
-plot_signal(x_shallow, offset=yticks[2])
-plot_signal(x_steep, offset=yticks[3])
-plot_signal(x_mne_c, offset=yticks[4])
-axs[0].set(xlim=tlim, title='FIR, Lowpass=%d Hz' % f_p, xticks=tticks,
-           ylim=[-0.150, 0.025], yticks=yticks, yticklabels=yticklabels,)
-for text in axs[0].get_yticklabels():
+plot_signal(x_v16, offset=yticks[2])
+plot_signal(x_v14, offset=yticks[3])
+plot_signal(x_v13, offset=yticks[4])
+plot_signal(x_mne_c, offset=yticks[5])
+plot_signal(x_min, offset=yticks[6])
+axes[0].set(xlim=tlim, title='FIR, Lowpass=%d Hz' % f_p, xticks=tticks,
+            ylim=[-0.200, 0.025], yticks=yticks, yticklabels=yticklabels,)
+for text in axes[0].get_yticklabels():
     text.set(rotation=45, size=8)
-axs[1].set(xlim=flim, ylim=ylim, xlabel='Frequency (Hz)',
-           ylabel='Magnitude (dB)')
-box_off(axs[0])
-box_off(axs[1])
+axes[1].set(xlim=flim, ylim=(-60, 10), xlabel='Frequency (Hz)',
+            ylabel='Magnitude (dB)')
 mne.viz.tight_layout()
 plt.show()
 
@@ -482,9 +488,20 @@ plt.show()
 # to have a *maximally flat pass-band*. Let's look at a few orders of filter,
 # i.e., a few different number of coefficients used and therefore steepness
 # of the filter:
+#
+# .. note:: Notice that the group delay (which is related to the phase) of
+#           the IIR filters below are not constant. In the FIR case, we can
+#           design so-called linear-phase filters that have a constant group
+#           delay, and thus compensate for the delay (making the filter
+#           acausal) if necessary. This cannot be done with IIR filters, as
+#           they have a non-linear phase (non-constant group delay). As the
+#           filter order increases, the phase distortion near and in the
+#           transition band worsens. However, if acausal (forward-backward)
+#           filtering can be used, e.g. with :func:`scipy.signal.filtfilt`,
+#           these phase issues can theoretically be mitigated.
 
 sos = signal.iirfilter(2, f_p / nyq, btype='low', ftype='butter', output='sos')
-plot_filter(sos, 'Butterworth order=2', freq, gain)
+plot_filter(dict(sos=sos), sfreq, freq, gain, 'Butterworth order=2', flim=flim)
 
 # Eventually this will just be from scipy signal.sosfiltfilt, but 0.18 is
 # not widely adopted yet (as of June 2016), so we use our wrapper...
@@ -494,17 +511,6 @@ x_shallow = sosfiltfilt(sos, x)
 ###############################################################################
 # The falloff of this filter is not very steep.
 #
-# .. warning:: For brevity, we do not show the phase of these filters here.
-#              In the FIR case, we can design linear-phase filters, and
-#              compensate for the delay (making the filter acausal) if
-#              necessary. This cannot be done
-#              with IIR filters, as they have a non-linear phase.
-#              As the filter order increases, the
-#              phase distortion near and in the transition band worsens.
-#              However, if acausal (forward-backward) filtering can be used,
-#              e.g. with :func:`scipy.signal.filtfilt`, these phase issues
-#              can be mitigated.
-#
 # .. note:: Here we have made use of second-order sections (SOS)
 #           by using :func:`scipy.signal.sosfilt` and, under the
 #           hood, :func:`scipy.signal.zpk2sos` when passing the
@@ -522,7 +528,7 @@ x_shallow = sosfiltfilt(sos, x)
 # with a longer impulse response:
 
 sos = signal.iirfilter(8, f_p / nyq, btype='low', ftype='butter', output='sos')
-plot_filter(sos, 'Butterworth order=8', freq, gain)
+plot_filter(dict(sos=sos), sfreq, freq, gain, 'Butterworth order=8', flim=flim)
 x_steep = sosfiltfilt(sos, x)
 
 ###############################################################################
@@ -533,7 +539,8 @@ x_steep = sosfiltfilt(sos, x)
 
 sos = signal.iirfilter(8, f_p / nyq, btype='low', ftype='cheby1', output='sos',
                        rp=1)  # dB of acceptable pass-band ripple
-plot_filter(sos, 'Chebychev-1 order=8, ripple=1 dB', freq, gain)
+plot_filter(dict(sos=sos), sfreq, freq, gain,
+            'Chebychev-1 order=8, ripple=1 dB', flim=flim)
 
 ###############################################################################
 # And if we can live with even more ripple, we can get it slightly steeper,
@@ -542,7 +549,8 @@ plot_filter(sos, 'Chebychev-1 order=8, ripple=1 dB', freq, gain)
 
 sos = signal.iirfilter(8, f_p / nyq, btype='low', ftype='cheby1', output='sos',
                        rp=6)
-plot_filter(sos, 'Chebychev-1 order=8, ripple=6 dB', freq, gain)
+plot_filter(dict(sos=sos), sfreq, freq, gain,
+            'Chebychev-1 order=8, ripple=6 dB', flim=flim)
 
 ###############################################################################
 # Applying IIR filters
@@ -551,21 +559,20 @@ plot_filter(sos, 'Chebychev-1 order=8, ripple=6 dB', freq, gain)
 # Now let's look at how our shallow and steep Butterworth IIR filters
 # perform on our Morlet signal from before:
 
-axs = plt.subplots(1, 2)[1]
+axes = plt.subplots(1, 2)[1]
 yticks = np.arange(4) / -30.
 yticklabels = ['Original', 'Noisy', 'Butterworth-2', 'Butterworth-8']
 plot_signal(x_orig, offset=yticks[0])
 plot_signal(x, offset=yticks[1])
 plot_signal(x_shallow, offset=yticks[2])
 plot_signal(x_steep, offset=yticks[3])
-axs[0].set(xlim=tlim, title='IIR, Lowpass=%d Hz' % f_p, xticks=tticks,
-           ylim=[-0.125, 0.025], yticks=yticks, yticklabels=yticklabels,)
-for text in axs[0].get_yticklabels():
+axes[0].set(xlim=tlim, title='IIR, Lowpass=%d Hz' % f_p, xticks=tticks,
+            ylim=[-0.125, 0.025], yticks=yticks, yticklabels=yticklabels,)
+for text in axes[0].get_yticklabels():
     text.set(rotation=45, size=8)
-axs[1].set(xlim=flim, ylim=ylim, xlabel='Frequency (Hz)',
-           ylabel='Magnitude (dB)')
-box_off(axs[0])
-box_off(axs[1])
+axes[1].set(xlim=flim, ylim=(-60, 10), xlabel='Frequency (Hz)',
+            ylabel='Magnitude (dB)')
+mne.viz.adjust_axes(axes)
 mne.viz.tight_layout()
 plt.show()
 
@@ -658,14 +665,14 @@ ylim = [-2, 6]
 xlabel = 'Time (sec)'
 ylabel = 'Amplitude ($\mu$V)'
 tticks = [0, 0.5, 1.3, t[-1]]
-axs = plt.subplots(2, 2)[1].ravel()
-for ax, x_f, title in zip(axs, [x_lp_2, x_lp_30, x_hp_2, x_hp_p1],
+axes = plt.subplots(2, 2)[1].ravel()
+for ax, x_f, title in zip(axes, [x_lp_2, x_lp_30, x_hp_2, x_hp_p1],
                           ['LP$_2$', 'LP$_{30}$', 'HP$_2$', 'LP$_{0.1}$']):
     ax.plot(t, x, color='0.5')
     ax.plot(t, x_f, color='k', linestyle='--')
     ax.set(ylim=ylim, xlim=xlim, xticks=tticks,
            title=title, xlabel=xlabel, ylabel=ylabel)
-    box_off(ax)
+mne.viz.adjust_axes(axes)
 mne.viz.tight_layout()
 plt.show()
 
@@ -703,9 +710,9 @@ plt.show()
 
 
 def baseline_plot(x):
-    all_axs = plt.subplots(3, 2)[1]
-    for ri, (axs, freq) in enumerate(zip(all_axs, [0.1, 0.3, 0.5])):
-        for ci, ax in enumerate(axs):
+    all_axes = plt.subplots(3, 2)[1]
+    for ri, (axes, freq) in enumerate(zip(all_axes, [0.1, 0.3, 0.5])):
+        for ci, ax in enumerate(axes):
             if ci == 0:
                 iir_hp = signal.iirfilter(4, freq / sfreq, btype='highpass',
                                           output='sos')
@@ -717,10 +724,10 @@ def baseline_plot(x):
             if ri == 0:
                 ax.set(title=('No ' if ci == 0 else '') +
                        'Baseline Correction')
-            box_off(ax)
             ax.set(xticks=tticks, ylim=ylim, xlim=xlim, xlabel=xlabel)
             ax.set_ylabel('%0.1f Hz' % freq, rotation=0,
                           horizontalalignment='right')
+        mne.viz.adjust_axes(axes)
     mne.viz.tight_layout()
     plt.suptitle(title)
     plt.show()
@@ -775,7 +782,7 @@ baseline_plot(x)
 # and thus :func:`mne.io.Raw.filter` is used. This function under the hood
 # (among other things) calls :func:`mne.filter.filter_data` to actually
 # filter the data, which by default applies a zero-phase FIR filter designed
-# using :func:`scipy.signal.firwin2`. In Widmann *et al.* 2015 [7]_, they
+# using :func:`scipy.signal.firwin`. In Widmann *et al.* 2015 [7]_, they
 # suggest a specific set of parameters to use for high-pass filtering,
 # including:
 #
@@ -821,27 +828,28 @@ baseline_plot(x)
 # To choose the filter length automatically with ``filter_length='auto'``,
 # the reciprocal of the shortest transition bandwidth is used to ensure
 # decent attenuation at the stop frequency. Specifically, the reciprocal
-# (in samples) is multiplied by 6.2, 6.6, or 11.0 for the Hann, Hamming,
+# (in samples) is multiplied by 3.1, 3.3, or 5.0 for the Hann, Hamming,
 # or Blackman windows, respectively as selected by the ``fir_window``
-# argument.
-#
-# .. note:: These multiplicative factors are double what is given in
-#           Ifeachor and Jervis [2]_ (p. 357). The window functions have a
-#           smearing effect on the frequency response; I&J thus take the
-#           approach of setting the stop frequency as
-#           :math:`f_s = f_p + f_{trans} / 2.`, but our stated definitions of
-#           :math:`f_s` and :math:`f_{trans}` do not
-#           allow us to do this in a nice way. Instead, we increase our filter
-#           length to achieve acceptable (20+ dB) attenuation by
-#           :math:`f_s = f_p + f_{trans}`, and excellent (50+ dB)
-#           attenuation by :math:`f_s + f_{trans}` (and usually earlier).
+# argument for ``fir_design='firwin'``, and double these for
+# ``fir_design='firwin2'`` mode.
+#
+# .. note:: For ``fir_design='firwin2'``, the multiplicative factors are
+#           doubled compared to what is given in Ifeachor and Jervis [2]_
+#           (p. 357), as :func:`scipy.signal.firwin2` has a smearing effect
+#           on the frequency response, which we compensate for by
+#           increasing the filter length. This is why
+#           ``fir_desgin='firwin'`` is preferred to ``fir_design='firwin2'``.
 #
 # In 0.14, we default to using a Hamming window in filter design, as it
 # provides up to 53 dB of stop-band attenuation with small pass-band ripple.
 #
 # .. note:: In band-pass applications, often a low-pass filter can operate
 #           effectively with fewer samples than the high-pass filter, so
-#           it is advisable to apply the high-pass and low-pass separately.
+#           it is advisable to apply the high-pass and low-pass separately
+#           when using ``fir_design='firwin2'``. For design mode
+#           ``fir_design='firwin'``, there is no need to separate the
+#           operations, as the lowpass and highpass elements are constructed
+#           separately to meet the transition band requirements.
 #
 # For more information on how to use the
 # MNE-Python filtering functions with real data, consult the preprocessing
diff --git a/tutorials/plot_brainstorm_auditory.py b/tutorials/plot_brainstorm_auditory.py
index ecc7196..02678fe 100644
--- a/tutorials/plot_brainstorm_auditory.py
+++ b/tutorials/plot_brainstorm_auditory.py
@@ -10,6 +10,7 @@ tutorial dataset. For comparison, see [1]_ and:
     http://neuroimage.usc.edu/brainstorm/Tutorials/Auditory
 
 Experiment:
+
     - One subject, 2 acquisition runs 6 minutes each.
     - Each run contains 200 regular beeps and 40 easy deviant beeps.
     - Random ISI: between 0.7s and 1.7s seconds, uniformly distributed.
@@ -41,7 +42,6 @@ from mne import combine_evoked
 from mne.minimum_norm import apply_inverse
 from mne.datasets.brainstorm import bst_auditory
 from mne.io import read_raw_ctf
-from mne.filter import notch_filter, low_pass_filter
 
 print(__doc__)
 
@@ -125,7 +125,7 @@ saccades_events = df[df['label'] == 'saccade'].values[:, :3].astype(int)
 # Conversion from samples to times:
 onsets = annotations_df['onset'].values / raw.info['sfreq']
 durations = annotations_df['duration'].values / raw.info['sfreq']
-descriptions = map(str, annotations_df['label'].values)
+descriptions = annotations_df['label'].values
 
 annotations = mne.Annotations(onsets, durations, descriptions)
 raw.annotations = annotations
@@ -168,16 +168,16 @@ raw.plot(block=True)
 # usually would do.
 if not use_precomputed:
     meg_picks = mne.pick_types(raw.info, meg=True, eeg=False)
-    raw.plot_psd(picks=meg_picks)
+    raw.plot_psd(tmax=np.inf, picks=meg_picks)
     notches = np.arange(60, 181, 60)
-    raw.notch_filter(notches)
-    raw.plot_psd(picks=meg_picks)
+    raw.notch_filter(notches, phase='zero-double', fir_design='firwin2')
+    raw.plot_psd(tmax=np.inf, picks=meg_picks)
 
 ###############################################################################
 # We also lowpass filter the data at 100 Hz to remove the hf components.
 if not use_precomputed:
     raw.filter(None, 100., h_trans_bandwidth=0.5, filter_length='10s',
-               phase='zero-double')
+               phase='zero-double', fir_design='firwin2')
 
 ###############################################################################
 # Epoching and averaging.
@@ -248,23 +248,13 @@ del epochs_standard, epochs_deviant
 
 ###############################################################################
 # Typical preprocessing step is the removal of power line artifact (50 Hz or
-# 60 Hz). Here we notch filter the data at 60, 120 and 180 to remove the
-# original 60 Hz artifact and the harmonics. Normally this would be done to
-# raw data (with :func:`mne.io.Raw.filter`), but to reduce memory consumption
-# of this tutorial, we do it at evoked stage.
-if use_precomputed:
-    sfreq = evoked_std.info['sfreq']
-    nchan = evoked_std.info['nchan']
-    notches = [60, 120, 180]
-    for ch_idx in range(nchan):
-        evoked_std.data[ch_idx] = notch_filter(evoked_std.data[ch_idx], sfreq,
-                                               notches, verbose='ERROR')
-        evoked_dev.data[ch_idx] = notch_filter(evoked_dev.data[ch_idx], sfreq,
-                                               notches, verbose='ERROR')
-        evoked_std.data[ch_idx] = low_pass_filter(evoked_std.data[ch_idx],
-                                                  sfreq, 100, verbose='ERROR')
-        evoked_dev.data[ch_idx] = low_pass_filter(evoked_dev.data[ch_idx],
-                                                  sfreq, 100, verbose='ERROR')
+# 60 Hz). Here we lowpass filter the data at 40 Hz, which will remove all
+# line artifacts (and high frequency information). Normally this would be done
+# to raw data (with :func:`mne.io.Raw.filter`), but to reduce memory
+# consumption of this tutorial, we do it at evoked stage. (At the raw stage,
+# you could alternatively notch filter with :func:`mne.io.Raw.notch_filter`.)
+for evoked in (evoked_std, evoked_dev):
+    evoked.filter(l_freq=None, h_freq=40., fir_design='firwin')
 
 ###############################################################################
 # Here we plot the ERF of standard and deviant conditions. In both conditions
@@ -277,6 +267,7 @@ if use_precomputed:
 evoked_std.plot(window_title='Standard', gfp=True)
 evoked_dev.plot(window_title='Deviant', gfp=True)
 
+
 ###############################################################################
 # Show activations as topography figures.
 times = np.arange(0.05, 0.301, 0.025)
diff --git a/tutorials/plot_brainstorm_phantom_ctf.py b/tutorials/plot_brainstorm_phantom_ctf.py
index 70daf66..4f29b43 100644
--- a/tutorials/plot_brainstorm_phantom_ctf.py
+++ b/tutorials/plot_brainstorm_phantom_ctf.py
@@ -86,6 +86,15 @@ epochs = mne.Epochs(raw, events, event_id=1, tmin=tmin, tmax=tmax,
 evoked = epochs.average()
 evoked.plot()
 evoked.crop(0., 0.)
+
+###############################################################################
+# Let's use a sphere head geometry model and let's see the coordinate
+# alignement and the sphere location.
+sphere = mne.make_sphere_model(r0=(0., 0., 0.), head_radius=None)
+
+mne.viz.plot_alignment(raw.info, subject='sample',
+                       meg='helmet', bem=sphere, dig=True,
+                       surfaces=['brain'])
 del raw, epochs
 
 ###############################################################################
@@ -97,8 +106,8 @@ raw_erm = mne.preprocessing.maxwell_filter(raw_erm, coord_frame='meg',
                                            **mf_kwargs)
 cov = mne.compute_raw_covariance(raw_erm)
 del raw_erm
-sphere = mne.make_sphere_model(r0=(0., 0., 0.), head_radius=None)
-dip = fit_dipole(evoked, cov, sphere)[0]
+
+dip, residual = fit_dipole(evoked, cov, sphere)
 
 ###############################################################################
 # Compare the actual position with the estimated one.
diff --git a/tutorials/plot_brainstorm_phantom_elekta.py b/tutorials/plot_brainstorm_phantom_elekta.py
index 75d3713..663fe7f 100644
--- a/tutorials/plot_brainstorm_phantom_elekta.py
+++ b/tutorials/plot_brainstorm_phantom_elekta.py
@@ -23,12 +23,14 @@ References
 
 import os.path as op
 import numpy as np
+import matplotlib.pyplot as plt
 
 import mne
 from mne import find_events, fit_dipole
 from mne.datasets.brainstorm import bst_phantom_elekta
 from mne.io import read_raw_fif
 
+from mayavi import mlab
 print(__doc__)
 
 ###############################################################################
@@ -38,7 +40,7 @@ print(__doc__)
 data_path = bst_phantom_elekta.data_path()
 
 raw_fname = op.join(data_path, 'kojak_all_200nAm_pp_no_chpi_no_ms_raw.fif')
-raw = read_raw_fif(raw_fname, add_eeg_ref=False)
+raw = read_raw_fif(raw_fname)
 
 ###############################################################################
 # Data channel array consisted of 204 MEG planor gradiometers,
@@ -54,7 +56,7 @@ raw.info['bads'] = ['MEG2421']
 # noise (five peaks around 300 Hz). Here we plot only out to 60 seconds
 # to save memory:
 
-raw.plot_psd(tmax=60.)
+raw.plot_psd(tmax=60., average=False)
 
 ###############################################################################
 # Let's use Maxwell filtering to clean the data a bit.
@@ -67,8 +69,7 @@ raw = mne.preprocessing.maxwell_filter(raw, origin=(0., 0., 0.))
 ###############################################################################
 # We know our phantom produces sinusoidal bursts below 25 Hz, so let's filter.
 
-raw.filter(None, 40., h_trans_bandwidth='auto', filter_length='auto',
-           phase='zero')
+raw.filter(None, 40., fir_design='firwin')
 raw.plot(events=events)
 
 ###############################################################################
@@ -79,19 +80,31 @@ raw.plot(events=events)
 tmin, tmax = -0.1, 0.1
 event_id = list(range(1, 33))
 epochs = mne.Epochs(raw, events, event_id, tmin, tmax, baseline=(None, -0.01),
-                    decim=5, preload=True, add_eeg_ref=False)
+                    decim=3, preload=True)
 epochs['1'].average().plot()
 
 ###############################################################################
-# Let's do some dipole fits. The phantom is properly modeled by a single-shell
-# sphere with origin (0., 0., 0.). We compute covariance, then do the fits.
-
-t_peak = 60e-3  # ~60 MS at largest peak
+# Let's use a sphere head geometry model and let's see the coordinate
+# alignement and the sphere location. The phantom is properly modeled by
+# a single-shell sphere with origin (0., 0., 0.).
 sphere = mne.make_sphere_model(r0=(0., 0., 0.), head_radius=None)
+
+mne.viz.plot_alignment(raw.info, subject='sample',
+                       meg='helmet', bem=sphere, dig=True,
+                       surfaces=['brain'])
+
+###############################################################################
+# Let's do some dipole fits. We first compute the noise covariance,
+# then do the fits for each event_id taking the time instant that maximizes
+# the global field power.
+
 cov = mne.compute_covariance(epochs, tmax=0)
 data = []
-for ii in range(1, 33):
-    evoked = epochs[str(ii)].average().crop(t_peak, t_peak)
+for ii in event_id:
+    evoked = epochs[str(ii)].average()
+    idx_peak = np.argmax(evoked.copy().pick_types(meg='grad').data.std(axis=0))
+    t_peak = evoked.times[idx_peak]
+    evoked.crop(t_peak, t_peak)
     data.append(evoked.data[:, 0])
 evoked = mne.EvokedArray(np.array(data).T, evoked.info, tmin=0.)
 del epochs, raw
@@ -100,7 +113,47 @@ dip = fit_dipole(evoked, cov, sphere, n_jobs=1)[0]
 ###############################################################################
 # Now we can compare to the actual locations, taking the difference in mm:
 
-actual_pos = mne.dipole.get_phantom_dipoles(kind='122')[0]
+actual_pos, actual_ori = mne.dipole.get_phantom_dipoles()
+actual_amp = 100.  # nAm
+
+fig, (ax1, ax2, ax3) = plt.subplots(nrows=3, ncols=1, figsize=(6, 7))
+
 diffs = 1000 * np.sqrt(np.sum((dip.pos - actual_pos) ** 2, axis=-1))
-print('Differences (mm):\n%s' % diffs[:, np.newaxis])
-print('μ = %s' % (np.mean(diffs),))
+print('mean(position error) = %s' % (np.mean(diffs),))
+ax1.bar(event_id, diffs)
+ax1.set_xlabel('Dipole index')
+ax1.set_ylabel('Loc. error (mm)')
+
+angles = np.arccos(np.abs(np.sum(dip.ori * actual_ori, axis=1)))
+print('mean(angle error) = %s' % (np.mean(angles),))
+ax2.bar(event_id, angles)
+ax2.set_xlabel('Dipole index')
+ax2.set_ylabel('Angle error (rad)')
+
+amps = actual_amp - dip.amplitude / 1e-9
+print('mean(abs amplitude error) = %s' % (np.mean(np.abs(amps)),))
+ax3.bar(event_id, amps)
+ax3.set_xlabel('Dipole index')
+ax3.set_ylabel('Amplitude error (nAm)')
+
+fig.tight_layout()
+plt.show()
+
+
+###############################################################################
+# Let's plot the positions and the orientations of the actual and the estimated
+# dipoles
+def plot_pos_ori(pos, ori, color=(0., 0., 0.)):
+    mlab.points3d(pos[:, 0], pos[:, 1], pos[:, 2], scale_factor=0.005,
+                  color=color)
+    mlab.quiver3d(pos[:, 0], pos[:, 1], pos[:, 2],
+                  ori[:, 0], ori[:, 1], ori[:, 2],
+                  scale_factor=0.03,
+                  color=color)
+
+mne.viz.plot_alignment(evoked.info, bem=sphere, surfaces=[])
+
+# Plot the position and the orientation of the actual dipole
+plot_pos_ori(actual_pos, actual_ori, color=(1., 0., 0.))
+# Plot the position and the orientation of the estimated dipole
+plot_pos_ori(dip.pos, dip.ori, color=(0., 0., 1.))
diff --git a/tutorials/plot_compute_covariance.py b/tutorials/plot_compute_covariance.py
index a928568..893eb90 100644
--- a/tutorials/plot_compute_covariance.py
+++ b/tutorials/plot_compute_covariance.py
@@ -3,7 +3,14 @@
 
 Computing covariance matrix
 ===========================
+
+Many methods in MNE, including e.g. source estimation and some classification
+algorithms, require covariance estimations from the recordings.
+In this tutorial we cover the basics of sensor covariance computations and
+construct a noise covariance matrix that can be used when computing the
+minimum-norm inverse solution. For more information, see :ref:`BABDEEEB`.
 """
+
 import os.path as op
 
 import mne
@@ -17,12 +24,13 @@ from mne.datasets import sample
 data_path = sample.data_path()
 raw_empty_room_fname = op.join(
     data_path, 'MEG', 'sample', 'ernoise_raw.fif')
-raw_empty_room = mne.io.read_raw_fif(raw_empty_room_fname, add_eeg_ref=False)
+raw_empty_room = mne.io.read_raw_fif(raw_empty_room_fname)
 raw_fname = op.join(data_path, 'MEG', 'sample', 'sample_audvis_raw.fif')
-raw = mne.io.read_raw_fif(raw_fname, add_eeg_ref=False)
-raw.set_eeg_reference()
+raw = mne.io.read_raw_fif(raw_fname)
+raw.set_eeg_reference('average', projection=True)
 raw.info['bads'] += ['EEG 053']  # bads + 1 more
 
+
 ###############################################################################
 # The definition of noise depends on the paradigm. In MEG it is quite common
 # to use empty room measurements for the estimation of sensor noise. However if
@@ -33,12 +41,27 @@ raw.info['bads'] += ['EEG 053']  # bads + 1 more
 # useful if you use resting state as a noise baseline. Here we use the whole
 # empty room recording to compute the noise covariance (tmax=None is the same
 # as the end of the recording, see :func:`mne.compute_raw_covariance`).
-noise_cov = mne.compute_raw_covariance(raw_empty_room, tmin=0, tmax=None)
+#
+# Keep in mind that you want to match your empty room dataset to your
+# actual MEG data, processing-wise. Ensure that filters
+# are all the same and if you use ICA, apply it to your empty-room and subject
+# data equivalently. In this case we did not filter the data and
+# we don't use ICA. However, we do have bad channels and projections in
+# the MEG data, and, hence, we want to make sure they get stored in the
+# covariance object.
+
+raw_empty_room.info['bads'] = [
+    bb for bb in raw.info['bads'] if 'EEG' not in bb]
+raw_empty_room.add_proj(
+    [pp.copy() for pp in raw.info['projs'] if 'EEG' not in pp['desc']])
+
+noise_cov = mne.compute_raw_covariance(
+    raw_empty_room, tmin=0, tmax=None)
 
 ###############################################################################
-# Now that you the covariance matrix in a python object you can save it to a
-# file with :func:`mne.write_cov`. Later you can read it back to a python
-# object using :func:`mne.read_cov`.
+# Now that you have the covariance matrix in an MNE-Python object you can
+# save it to a file with :func:`mne.write_cov`. Later you can read it back
+# using :func:`mne.read_cov`.
 #
 # You can also use the pre-stimulus baseline to estimate the noise covariance.
 # First we have to construct the epochs. When computing the covariance, you
@@ -46,13 +69,13 @@ noise_cov = mne.compute_raw_covariance(raw_empty_room, tmin=0, tmax=None)
 # covariance matrix will be inaccurate. In MNE this is done by default, but
 # just to be sure, we define it here manually.
 events = mne.find_events(raw)
-epochs = mne.Epochs(raw, events, event_id=1, tmin=-0.2, tmax=0.0,
-                    baseline=(-0.2, 0.0))
+epochs = mne.Epochs(raw, events, event_id=1, tmin=-0.2, tmax=0.5,
+                    baseline=(-0.2, 0.0), decim=3)  # we'll decimate for speed
 
 ###############################################################################
-# Note that this method also attenuates the resting state activity in your
-# source estimates.
-noise_cov_baseline = mne.compute_covariance(epochs)
+# Note that this method also attenuates any activity in your
+# source estimates that resemble the baseline, if you like it or not.
+noise_cov_baseline = mne.compute_covariance(epochs, tmax=0)
 
 ###############################################################################
 # Plot the covariance matrices
@@ -61,7 +84,7 @@ noise_cov_baseline = mne.compute_covariance(epochs)
 # Try setting proj to False to see the effect. Notice that the projectors in
 # epochs are already applied, so ``proj`` parameter has no effect.
 noise_cov.plot(raw_empty_room.info, proj=True)
-noise_cov_baseline.plot(epochs.info)
+noise_cov_baseline.plot(epochs.info, proj=True)
 
 ###############################################################################
 # How should I regularize the covariance matrix?
@@ -78,7 +101,8 @@ noise_cov_baseline.plot(epochs.info)
 # described in [1]_. For this the 'auto' option can be used. With this
 # option cross-validation will be used to learn the optimal regularization:
 
-cov = mne.compute_covariance(epochs, tmax=0., method='auto')
+noise_cov_reg = mne.compute_covariance(epochs, tmax=0.,
+                                       method='auto')
 
 ###############################################################################
 # This procedure evaluates the noise covariance quantitatively by how well it
@@ -95,7 +119,8 @@ cov = mne.compute_covariance(epochs, tmax=0., method='auto')
 # of freedom, e.g. ``ddof=3`` with 2 active SSP vectors):
 
 evoked = epochs.average()
-evoked.plot_white(cov)
+evoked.plot_white(noise_cov_reg)
+
 
 ###############################################################################
 # This plot displays both, the whitened evoked signals for each channels and
@@ -118,15 +143,33 @@ evoked.plot_white(cov)
 # For expert use cases or debugging the alternative estimators can also be
 # compared:
 
-covs = mne.compute_covariance(epochs, tmax=0., method=('empirical', 'shrunk'),
-                              return_estimators=True)
+noise_covs = mne.compute_covariance(
+    epochs, tmax=0., method=('empirical', 'shrunk'), return_estimators=True)
 evoked = epochs.average()
-evoked.plot_white(covs)
+evoked.plot_white(noise_covs)
+
 
 ##############################################################################
 # This will plot the whitened evoked for the optimal estimator and display the
 # GFPs for all estimators as separate lines in the related panel.
 
+
+##############################################################################
+# Finally, let's have a look at the difference between empty room and
+# event related covariance.
+
+evoked_meg = evoked.pick_types(meg=True, eeg=False)
+noise_cov_meg = mne.pick_channels_cov(noise_cov_baseline, evoked_meg.ch_names)
+noise_cov['method'] = 'empty_room'
+noise_cov_meg['method'] = 'baseline'
+
+evoked_meg.plot_white([noise_cov_meg, noise_cov])
+
+
+##############################################################################
+# Based on the negative log-likelihood, the baseline covariance
+# seems more appropriate.
+
 ###############################################################################
 # References
 # ----------
diff --git a/tutorials/plot_configuration.py b/tutorials/plot_configuration.py
new file mode 100644
index 0000000..879865c
--- /dev/null
+++ b/tutorials/plot_configuration.py
@@ -0,0 +1,87 @@
+# -*- coding: utf-8 -*-
+"""
+======================
+Configuring MNE python
+======================
+
+This tutorial gives a short introduction to MNE configurations.
+"""
+import os.path as op
+
+import mne
+from mne.datasets.sample import data_path
+
+fname = op.join(data_path(), 'MEG', 'sample', 'sample_audvis_raw.fif')
+raw = mne.io.read_raw_fif(fname).crop(0, 10)
+original_level = mne.get_config('MNE_LOGGING_LEVEL', 'INFO')
+
+###############################################################################
+# MNE-python stores configurations to a folder called `.mne` in the user's
+# home directory, or to AppData directory on Windows. The path to the config
+# file can be found out by calling :func:`mne.get_config_path`.
+print(mne.get_config_path())
+
+###############################################################################
+# These configurations include information like sample data paths and plotter
+# window sizes. Files inside this folder should never be modified manually.
+# Let's see what the configurations contain.
+print(mne.get_config())
+
+###############################################################################
+# We see fields like "MNE_DATASETS_SAMPLE_PATH". As the name suggests, this is
+# the path the sample data is downloaded to. All the fields in the
+# configuration file can be modified by calling :func:`mne.set_config`.
+
+###############################################################################
+#
+# .. _tut_logging:
+#
+# Logging
+# =======
+# Configurations also include the default logging level for the functions. This
+# field is called "MNE_LOGGING_LEVEL".
+mne.set_config('MNE_LOGGING_LEVEL', 'INFO')
+print(mne.get_config(key='MNE_LOGGING_LEVEL'))
+
+###############################################################################
+# The default value is now set to INFO. This level will now be used by default
+# every time we call a function in MNE. We can set the global logging level for
+# only this session by calling :func:`mne.set_log_level` function.
+mne.set_log_level('WARNING')
+print(mne.get_config(key='MNE_LOGGING_LEVEL'))
+
+###############################################################################
+# Notice how the value in the config file was not changed. Logging level of
+# WARNING only applies for this session. Let's see what logging level of
+# WARNING prints for :func:`mne.compute_raw_covariance`.
+cov = mne.compute_raw_covariance(raw)
+
+###############################################################################
+# Nothing. This means that no warnings were emitted during the computation. If
+# you look at the documentation of :func:`mne.compute_raw_covariance`, you
+# notice the ``verbose`` keyword. Setting this parameter does not touch the
+# configurations, but sets the logging level for just this one function call.
+# Let's see what happens with logging level of INFO.
+cov = mne.compute_raw_covariance(raw, verbose=True)
+
+###############################################################################
+# As you see there is some info about what the function is doing. The logging
+# level can be set to 'DEBUG', 'INFO', 'WARNING', 'ERROR' or 'CRITICAL'. It can
+# also be set to an integer or a boolean value. The correspondance to string
+# values can be seen in the table below. ``verbose=None`` uses the default
+# value from the configuration file.
+#
+# +----------+---------+---------+
+# | String   | Integer | Boolean |
+# +==========+=========+=========+
+# | DEBUG    | 10      |         |
+# +----------+---------+---------+
+# | INFO     | 20      | True    |
+# +----------+---------+---------+
+# | WARNING  | 30      | False   |
+# +----------+---------+---------+
+# | ERROR    | 40      |         |
+# +----------+---------+---------+
+# | CRITICAL | 50      |         |
+# +----------+---------+---------+
+mne.set_config('MNE_LOGGING_LEVEL', original_level)
diff --git a/tutorials/plot_creating_data_structures.py b/tutorials/plot_creating_data_structures.py
index 72f099c..17dc7f8 100644
--- a/tutorials/plot_creating_data_structures.py
+++ b/tutorials/plot_creating_data_structures.py
@@ -1,11 +1,12 @@
 """
 .. _tut_creating_data_structures:
 
-Creating MNE-Python's data structures from scratch
-==================================================
-"""
+Creating MNE's data structures from scratch
+===========================================
 
-from __future__ import print_function
+MNE provides mechanisms for creating various core objects directly from
+NumPy arrays.
+"""
 
 import mne
 import numpy as np
@@ -32,7 +33,7 @@ import numpy as np
 # Create some dummy metadata
 n_channels = 32
 sampling_rate = 200
-info = mne.create_info(32, sampling_rate)
+info = mne.create_info(n_channels, sampling_rate)
 print(info)
 
 ###############################################################################
@@ -78,7 +79,16 @@ print(info)
 #
 # To create a :class:`mne.io.Raw` object from scratch, you can use the
 # :class:`mne.io.RawArray` class, which implements raw data that is backed by a
-# numpy array.  Its constructor simply takes the data matrix and
+# numpy array. The correct units for the data are:
+#
+# - V: eeg, eog, seeg, emg, ecg, bio, ecog
+# - T: mag
+# - T/m: grad
+# - M: hbo, hbr
+# - Am: dipole
+# - AU: misc
+#
+# The :class:`mne.io.RawArray` constructor simply takes the data matrix and
 # :class:`mne.Info` object:
 
 # Generate some random data
@@ -102,7 +112,7 @@ print(custom_raw)
 # To create an :class:`mne.Epochs` object from scratch, you can use the
 # :class:`mne.EpochsArray` class, which uses a numpy array directly without
 # wrapping a raw object. The array must be of `shape(n_epochs, n_chans,
-# n_times)`
+# n_times)`. The proper units of measure are listed above.
 
 # Generate some random data: 10 epochs, 5 channels, 2 seconds per epoch
 sfreq = 100
@@ -117,23 +127,23 @@ info = mne.create_info(
 
 ###############################################################################
 # It is necessary to supply an "events" array in order to create an Epochs
-# object. This is of `shape(n_events, 3)` where the first column is the index
-# of the event, the second column is the length of the event, and the third
-# column is the event type.
+# object. This is of `shape(n_events, 3)` where the first column is the sample
+# number (time) of the event, the second column indicates the value from which
+# the transition is made from (only used when the new value is bigger than the
+# old one), and the third column is the new event value.
 
-# Create an event matrix: 10 events with a duration of 1 sample, alternating
-# event codes
+# Create an event matrix: 10 events with alternating event codes
 events = np.array([
-    [0, 1, 1],
-    [1, 1, 2],
-    [2, 1, 1],
-    [3, 1, 2],
-    [4, 1, 1],
-    [5, 1, 2],
-    [6, 1, 1],
-    [7, 1, 2],
-    [8, 1, 1],
-    [9, 1, 2],
+    [0, 0, 1],
+    [1, 0, 2],
+    [2, 0, 1],
+    [3, 0, 2],
+    [4, 0, 1],
+    [5, 0, 2],
+    [6, 0, 1],
+    [7, 0, 2],
+    [8, 0, 1],
+    [9, 0, 2],
 ])
 
 ###############################################################################
@@ -164,6 +174,7 @@ _ = custom_epochs['smiling'].average().plot()
 # If you already have data that is collapsed across trials, you may also
 # directly create an evoked array.  Its constructor accepts an array of
 # `shape(n_chans, n_times)` in addition to some bookkeeping parameters.
+# The proper units of measure for the data are listed above.
 
 # The averaged data
 data_evoked = data.mean(0)
diff --git a/tutorials/plot_dipole_fit.py b/tutorials/plot_dipole_fit.py
index 811a1ab..a5ef8b2 100644
--- a/tutorials/plot_dipole_fit.py
+++ b/tutorials/plot_dipole_fit.py
@@ -45,18 +45,21 @@ evoked.crop(0.07, 0.08)
 # Fit a dipole
 dip = mne.fit_dipole(evoked, fname_cov, fname_bem, fname_trans)[0]
 
-# Plot the result in 3D brain
-dip.plot_locations(fname_trans, 'sample', subjects_dir)
+# Plot the result in 3D brain with the MRI image.
+dip.plot_locations(fname_trans, 'sample', subjects_dir, mode='orthoview')
 
 ###############################################################################
 # Calculate and visualise magnetic field predicted by dipole with maximum GOF
 # and compare to the measured data, highlighting the ipsilateral (right) source
 fwd, stc = make_forward_dipole(dip, fname_bem, evoked.info, fname_trans)
-pred_evoked = simulate_evoked(fwd, stc, evoked.info, None, snr=np.inf)
+pred_evoked = simulate_evoked(fwd, stc, evoked.info, cov=None, nave=np.inf)
 
-# find time point with highes GOF to plot
+# find time point with highest GOF to plot
 best_idx = np.argmax(dip.gof)
 best_time = dip.times[best_idx]
+print('Highest GOF %0.1f%% at t=%0.1f ms with confidence volume %0.1f cm^3'
+      % (dip.gof[best_idx], best_time * 1000,
+         dip.conf['vol'][best_idx] * 100 ** 3))
 # rememeber to create a subplot for the colorbar
 fig, axes = plt.subplots(nrows=1, ncols=4, figsize=[10., 3.4])
 vmin, vmax = -400, 400  # make sure each plot has same colour range
diff --git a/tutorials/plot_dipole_orientations.py b/tutorials/plot_dipole_orientations.py
new file mode 100644
index 0000000..9de9a61
--- /dev/null
+++ b/tutorials/plot_dipole_orientations.py
@@ -0,0 +1,206 @@
+# -*- coding: utf-8 -*-
+"""
+.. _tut_dipole_orentiations:
+
+The role of dipole orientations in distributed source localization
+==================================================================
+
+When performing source localization in a distributed manner (MNE/dSPM/sLORETA),
+the source space is defined as a grid of dipoles that spans a large portion of
+the cortex. These dipoles have both a position and an orientation. In this
+tutorial, we will look at the various options available to restrict the
+orientation of the dipoles and the impact on the resulting source estimate.
+"""
+
+###############################################################################
+# Loading data
+# ------------
+# Load everything we need to perform source localization on the sample dataset.
+
+from mayavi import mlab
+import mne
+from mne.datasets import sample
+from mne.minimum_norm import make_inverse_operator, apply_inverse
+
+data_path = sample.data_path()
+evokeds = mne.read_evokeds(data_path + '/MEG/sample/sample_audvis-ave.fif')
+left_auditory = evokeds[0].apply_baseline()
+fwd = mne.read_forward_solution(
+    data_path + '/MEG/sample/sample_audvis-meg-eeg-oct-6-fwd.fif',
+    surf_ori=True)
+noise_cov = mne.read_cov(data_path + '/MEG/sample/sample_audvis-cov.fif')
+subjects_dir = data_path + '/subjects'
+
+###############################################################################
+# The source space
+# ----------------
+# Let's start by examining the source space as constructed by the
+# :func:`mne.setup_source_space` function. Dipoles are placed along fixed
+# intervals on the cortex, determined by the ``spacing`` parameter. The source
+# space does not define the orientation for these dipoles.
+
+lh = fwd['src'][0]  # Visualize the left hemisphere
+verts = lh['rr']  # The vertices of the source space
+tris = lh['tris']  # Groups of three vertices that form triangles
+dip_pos = lh['rr'][lh['vertno']]  # The position of the dipoles
+white = (1.0, 1.0, 1.0)  # RGB values for a white color
+gray = (0.5, 0.5, 0.5)  # RGB values for a gray color
+red = (1.0, 0.0, 0.0)  # RGB valued for a red color
+
+mlab.figure(size=(600, 400), bgcolor=white)
+
+# Plot the cortex
+mlab.triangular_mesh(verts[:, 0], verts[:, 1], verts[:, 2], tris, color=gray)
+
+# Mark the position of the dipoles with small red dots
+mlab.points3d(dip_pos[:, 0], dip_pos[:, 1], dip_pos[:, 2], color=red,
+              scale_factor=1E-3)
+
+mlab.view(azimuth=180, distance=0.25)
+
+###############################################################################
+# Fixed dipole orientations
+# -------------------------
+# While the source space defines the position of the dipoles, the inverse
+# operator defines the possible orientations of them. One of the options is to
+# assign a fixed orientation. Since the neural currents from which MEG and EEG
+# signals originate flows mostly perpendicular to the cortex [1]_, restricting
+# the orientation of the dipoles accordingly places a useful restriction on the
+# source estimate.
+#
+# By specifying ``fixed=True`` when calling
+# :func:`mne.minimum_norm.make_inverse_operator`, the dipole orientations are
+# fixed to be orthogonal to the surface of the cortex, pointing outwards. Let's
+# visualize this:
+
+mlab.figure(size=(600, 400), bgcolor=white)
+
+# Plot the cortex
+mlab.triangular_mesh(verts[:, 0], verts[:, 1], verts[:, 2], tris, color=gray)
+
+# Show the dipoles as arrows pointing along the surface normal
+normals = lh['nn'][lh['vertno']]
+mlab.quiver3d(dip_pos[:, 0], dip_pos[:, 1], dip_pos[:, 2],
+              normals[:, 0], normals[:, 1], normals[:, 2],
+              color=red, scale_factor=1E-3)
+
+mlab.view(azimuth=180, distance=0.1)
+
+###############################################################################
+# Restricting the dipole orientations in this manner leads to the following
+# source estimate for the sample data:
+
+# Compute the source estimate for the 'left - auditory' condition in the sample
+# dataset.
+inv = make_inverse_operator(left_auditory.info, fwd, noise_cov, fixed=True)
+stc = apply_inverse(left_auditory, inv, pick_ori=None)
+
+# Visualize it at the moment of peak activity.
+_, time_max = stc.get_peak(hemi='lh')
+brain = stc.plot(surface='white', subjects_dir=subjects_dir,
+                 initial_time=time_max, time_unit='s', size=(600, 400))
+
+###############################################################################
+# The direction of the estimated current is now restricted to two directions:
+# inward and outward. In the plot, blue areas indicate current flowing inwards
+# and red areas indicate current flowing outwards. Given the curvature of the
+# cortex, groups of dipoles tend to point in the same direction: the direction
+# of the electromagnetic field picked up by the sensors.
+
+###############################################################################
+# Loose dipole orientations
+# -------------------------
+# Forcing the source dipoles to be strictly orthogonal to the cortex makes the
+# source estimate sensitive to the spacing of the dipoles along the cortex,
+# since the curvature of the cortex changes within each ~10 square mm patch.
+# Furthermore, misalignment of the MEG/EEG and MRI coordinate frames is more
+# critical when the source dipole orientations are strictly constrained [2]_.
+# To lift the restriction on the orientation of the dipoles, the inverse
+# operator has the ability to place not one, but three dipoles at each
+# location defined by the source space. These three dipoles are placed
+# orthogonally to form a Cartesian coordinate system. Let's visualize this:
+mlab.figure(size=(600, 400), bgcolor=white)
+
+# Define some more colors
+green = (0.0, 1.0, 0.0)
+blue = (0.0, 0.0, 1.0)
+
+# Plot the cortex
+mlab.triangular_mesh(verts[:, 0], verts[:, 1], verts[:, 2], tris, color=gray)
+
+# Make an inverse operator with loose dipole orientations
+inv = make_inverse_operator(left_auditory.info, fwd, noise_cov, fixed=False,
+                            loose=1.0)
+
+# Show the three dipoles defined at each location in the source space
+dip_dir = inv['source_nn'].reshape(-1, 3, 3)
+dip_dir = dip_dir[:len(dip_pos)]  # Only select left hemisphere
+for ori, color in zip((0, 1, 2), (red, green, blue)):
+    mlab.quiver3d(dip_pos[:, 0], dip_pos[:, 1], dip_pos[:, 2],
+                  dip_dir[:, ori, 0], dip_dir[:, ori, 1], dip_dir[:, ori, 2],
+                  color=color, scale_factor=1E-3)
+
+mlab.view(azimuth=180, distance=0.1)
+
+###############################################################################
+# When computing the source estimate, the activity at each of the three dipoles
+# is collapsed into the XYZ components of a single vector, which leads to the
+# following source estimate for the sample data:
+
+# Compute the source estimate, indicate that we want a vector solution
+stc = apply_inverse(left_auditory, inv, pick_ori='vector')
+
+# Visualize it at the moment of peak activity.
+_, time_max = stc.magnitude().get_peak(hemi='lh')
+brain = stc.plot(subjects_dir=subjects_dir, initial_time=time_max,
+                 time_unit='s', size=(600, 400), overlay_alpha=0)
+
+###############################################################################
+# Limiting orientations, but not fixing them
+# ------------------------------------------
+# Often, the best results will be obtained by allowing the dipoles to have
+# somewhat free orientation, but not stray too far from a orientation that is
+# perpendicular to the cortex. The ``loose`` parameter of the
+# :func:`mne.minimum_norm.make_inverse_operator` allows you to specify a value
+# between 0 (fixed) and 1 (unrestricted or "free") to indicate the amount the
+# orientation is allowed to deviate from the surface normal.
+
+# Set loose to 0.2, the default value
+inv = make_inverse_operator(left_auditory.info, fwd, noise_cov, fixed=False,
+                            loose=0.2)
+stc = apply_inverse(left_auditory, inv, pick_ori='vector')
+
+# Visualize it at the moment of peak activity.
+_, time_max = stc.magnitude().get_peak(hemi='lh')
+brain = stc.plot(subjects_dir=subjects_dir, initial_time=time_max,
+                 time_unit='s', size=(600, 400), overlay_alpha=0)
+
+###############################################################################
+# Discarding dipole orientation information
+# -----------------------------------------
+# Often, further analysis of the data does not need information about the
+# orientation of the dipoles, but rather their magnitudes. The ``pick_ori``
+# parameter of the :func:`mne.minimum_norm.apply_inverse` function allows you
+# to specify whether to return the full vector solution (``'vector'``) or
+# rather the magnitude of the vectors (``None``, the default) or only the
+# activity in the direction perpendicular to the cortex (``'normal'``).
+
+# Only retain vector magnitudes
+stc = apply_inverse(left_auditory, inv, pick_ori=None)
+
+# Visualize it at the moment of peak activity.
+_, time_max = stc.get_peak(hemi='lh')
+brain = stc.plot(surface='white', subjects_dir=subjects_dir,
+                 initial_time=time_max, time_unit='s', size=(600, 400))
+
+###############################################################################
+# References
+# ----------
+# .. [1] Hämäläinen, M. S., Hari, R., Ilmoniemi, R. J., Knuutila, J., &
+#    Lounasmaa, O. V. "Magnetoencephalography - theory, instrumentation, and
+#    applications to noninvasive studies of the working human brain", Reviews
+#    of Modern Physics, 1993. http://dx.doi.org/10.1103/RevModPhys.65.413
+#
+# .. [2] Lin, F. H., Belliveau, J. W., Dale, A. M., & Hämäläinen, M. S. (2006).
+#    Distributed current estimates using cortical orientation constraints.
+#    Human Brain Mapping, 27(1), 1–13. http://doi.org/10.1002/hbm.20155
diff --git a/tutorials/plot_ecog.py b/tutorials/plot_ecog.py
new file mode 100644
index 0000000..08165ac
--- /dev/null
+++ b/tutorials/plot_ecog.py
@@ -0,0 +1,76 @@
+"""
+======================
+Working with ECoG data
+======================
+
+MNE supports working with more than just MEG and EEG data. Here we show some
+of the functions that can be used to facilitate working with
+electrocorticography (ECoG) data.
+"""
+# Authors: Eric Larson <larson.eric.d at gmail.com>
+#          Chris Holdgraf <choldgraf at gmail.com>
+#
+# License: BSD (3-clause)
+
+import numpy as np
+import matplotlib.pyplot as plt
+from scipy.io import loadmat
+from mayavi import mlab
+
+import mne
+from mne.viz import plot_alignment, snapshot_brain_montage
+
+print(__doc__)
+
+###############################################################################
+# Let's load some ECoG electrode locations and names, and turn them into
+# a :class:`mne.channels.DigMontage` class.
+
+mat = loadmat(mne.datasets.misc.data_path() + '/ecog/sample_ecog.mat')
+ch_names = mat['ch_names'].tolist()
+elec = mat['elec']
+dig_ch_pos = dict(zip(ch_names, elec))
+mon = mne.channels.DigMontage(dig_ch_pos=dig_ch_pos)
+print('Created %s channel positions' % len(ch_names))
+
+###############################################################################
+# Now that we have our electrode positions in MRI coordinates, we can create
+# our measurement info structure.
+
+info = mne.create_info(ch_names, 1000., 'ecog', montage=mon)
+
+###############################################################################
+# We can then plot the locations of our electrodes on our subject's brain.
+#
+# .. note:: These are not real electrodes for this subject, so they
+#           do not align to the cortical surface perfectly.
+
+subjects_dir = mne.datasets.sample.data_path() + '/subjects'
+fig = plot_alignment(info, subject='sample', subjects_dir=subjects_dir,
+                     surfaces=['pial'])
+mlab.view(200, 70)
+
+###############################################################################
+# Sometimes it is useful to make a scatterplot for the current figure view.
+# This is best accomplished with matplotlib. We can capture an image of the
+# current mayavi view, along with the xy position of each electrode, with the
+# `snapshot_brain_montage` function.
+
+# We'll once again plot the surface, then take a snapshot.
+fig = plot_alignment(info, subject='sample', subjects_dir=subjects_dir,
+                     surfaces='pial')
+mlab.view(200, 70)
+xy, im = snapshot_brain_montage(fig, mon)
+
+# Convert from a dictionary to array to plot
+xy_pts = np.vstack(xy[ch] for ch in info['ch_names'])
+
+# Define an arbitrary "activity" pattern for viz
+activity = np.linspace(100, 200, xy_pts.shape[0])
+
+# This allows us to use matplotlib to create arbitrary 2d scatterplots
+_, ax = plt.subplots(figsize=(10, 10))
+ax.imshow(im)
+ax.scatter(*xy_pts.T, c=activity, s=200, cmap='coolwarm')
+ax.set_axis_off()
+plt.show()
diff --git a/tutorials/plot_eeg_erp.py b/tutorials/plot_eeg_erp.py
index 02003dc..2f70e83 100644
--- a/tutorials/plot_eeg_erp.py
+++ b/tutorials/plot_eeg_erp.py
@@ -22,8 +22,8 @@ from mne.datasets import sample
 data_path = sample.data_path()
 raw_fname = data_path + '/MEG/sample/sample_audvis_filt-0-40_raw.fif'
 event_fname = data_path + '/MEG/sample/sample_audvis_filt-0-40_raw-eve.fif'
-raw = mne.io.read_raw_fif(raw_fname, add_eeg_ref=False, preload=True)
-raw.set_eeg_reference()  # set EEG average reference
+raw = mne.io.read_raw_fif(raw_fname, preload=True)
+raw.set_eeg_reference('average', projection=True)  # set EEG average reference
 
 ###############################################################################
 # Let's restrict the data to the EEG channels
@@ -59,7 +59,7 @@ print(raw.info['chs'][0]['loc'])
 
 ###############################################################################
 # And it's actually possible to plot the channel locations using
-# the :func:`mne.io.Raw.plot_sensors` method
+# :func:`mne.io.Raw.plot_sensors`.
 
 raw.plot_sensors()
 raw.plot_sensors('3d')  # in 3D
@@ -69,14 +69,14 @@ raw.plot_sensors('3d')  # in 3D
 # -------------------
 #
 # In the case where your data don't have locations you can set them
-# using a :func:`mne.channels.Montage`. MNE comes with a set of default
+# using a :class:`mne.channels.Montage`. MNE comes with a set of default
 # montages. To read one of them do:
 
 montage = mne.channels.read_montage('standard_1020')
 print(montage)
 
 ###############################################################################
-# To apply a montage on your data use the :func:`mne.io.set_montage`
+# To apply a montage on your data use the ``set_montage`` method.
 # function. Here don't actually call this function as our demo dataset
 # already contains good EEG channel locations.
 #
@@ -91,7 +91,7 @@ print(montage)
 # This explicitly prevents MNE from adding a default EEG average reference
 # required for source localization.
 
-raw_no_ref, _ = mne.io.set_eeg_reference(raw, [])
+raw_no_ref, _ = mne.set_eeg_reference(raw, [])
 
 ###############################################################################
 # We next define Epochs and compute an ERP for the left auditory condition.
@@ -99,7 +99,7 @@ reject = dict(eeg=180e-6, eog=150e-6)
 event_id, tmin, tmax = {'left/auditory': 1}, -0.2, 0.5
 events = mne.read_events(event_fname)
 epochs_params = dict(events=events, event_id=event_id, tmin=tmin, tmax=tmax,
-                     reject=reject, add_eeg_ref=False)
+                     reject=reject)
 
 evoked_no_ref = mne.Epochs(raw_no_ref, **epochs_params).average()
 del raw_no_ref  # save memory
@@ -111,7 +111,7 @@ evoked_no_ref.plot_topomap(times=[0.1], size=3., title=title)
 ###############################################################################
 # **Average reference**: This is normally added by default, but can also
 # be added explicitly.
-raw_car, _ = mne.io.set_eeg_reference(raw)
+raw_car, _ = mne.set_eeg_reference(raw, 'average', projection=True)
 evoked_car = mne.Epochs(raw_car, **epochs_params).average()
 del raw_car  # save memory
 
@@ -122,7 +122,7 @@ evoked_car.plot_topomap(times=[0.1], size=3., title=title)
 ###############################################################################
 # **Custom reference**: Use the mean of channels EEG 001 and EEG 002 as
 # a reference
-raw_custom, _ = mne.io.set_eeg_reference(raw, ['EEG 001', 'EEG 002'])
+raw_custom, _ = mne.set_eeg_reference(raw, ['EEG 001', 'EEG 002'])
 evoked_custom = mne.Epochs(raw_custom, **epochs_params).average()
 del raw_custom  # save memory
 
@@ -160,7 +160,7 @@ mne.combine_evoked([left, -right], weights='equal').plot_joint()
 # This is an equal-weighting difference. If you have imbalanced trial numbers,
 # you could also consider either equalizing the number of events per
 # condition (using
-# :meth:`epochs.equalize_epochs_counts <mne.Epochs.equalize_event_counts`).
+# :meth:`epochs.equalize_event_counts <mne.Epochs.equalize_event_counts>`).
 # As an example, first, we create individual ERPs for each condition.
 
 aud_l = epochs["auditory", "left"].average()
diff --git a/tutorials/plot_epoching_and_averaging.py b/tutorials/plot_epoching_and_averaging.py
index 4248fcf..4ae10b2 100644
--- a/tutorials/plot_epoching_and_averaging.py
+++ b/tutorials/plot_epoching_and_averaging.py
@@ -17,8 +17,8 @@ import mne
 # First let's read in the raw sample data.
 data_path = mne.datasets.sample.data_path()
 fname = op.join(data_path, 'MEG', 'sample', 'sample_audvis_raw.fif')
-raw = mne.io.read_raw_fif(fname, add_eeg_ref=False)
-raw.set_eeg_reference()  # set EEG average reference
+raw = mne.io.read_raw_fif(fname)
+raw.set_eeg_reference('average', projection=True)  # set EEG average reference
 
 ###############################################################################
 # To create time locked epochs, we first need a set of events that contain the
@@ -41,7 +41,8 @@ raw.plot(n_channels=10, order=order, block=True)
 # from an outside source (like a separate file of events), pay special
 # attention in aligning the events correctly with the raw data.
 events = mne.find_events(raw)
-print(events)
+print('Found %s events, first five:' % len(events))
+print(events[:5])
 
 # Plot the events to get an idea of the paradigm
 # Specify colors and an event_id dictionary for the legend.
@@ -105,12 +106,12 @@ picks = mne.pick_types(raw.info, meg=True, eeg=False, eog=True)
 # for EEG and EOG electrodes.
 #
 # .. note:: In this tutorial, we don't preprocess the data. This is not
-#           something you would normally do. See our :ref:`tutorials` on
+#           something you would normally do. See our :ref:`documentation` on
 #           preprocessing for more.
 baseline = (None, 0.0)
 reject = {'mag': 4e-12, 'eog': 200e-6}
 epochs = mne.Epochs(raw, events=events, event_id=event_id, tmin=tmin,
-                    tmax=tmax, reject=reject, picks=picks, add_eeg_ref=False)
+                    tmax=tmax, baseline=baseline, reject=reject, picks=picks)
 
 ###############################################################################
 # Let's plot the epochs to see the results. The number at the top refers to the
diff --git a/tutorials/plot_epochs_to_data_frame.py b/tutorials/plot_epochs_to_data_frame.py
index e99d743..048c0df 100644
--- a/tutorials/plot_epochs_to_data_frame.py
+++ b/tutorials/plot_epochs_to_data_frame.py
@@ -102,8 +102,8 @@ data_path = sample.data_path()
 raw_fname = data_path + '/MEG/sample/sample_audvis_filt-0-40_raw.fif'
 event_fname = data_path + '/MEG/sample/sample_audvis_filt-0-40_raw-eve.fif'
 
-raw = mne.io.read_raw_fif(raw_fname, add_eeg_ref=False)
-raw.set_eeg_reference()  # set EEG average reference
+raw = mne.io.read_raw_fif(raw_fname)
+raw.set_eeg_reference('average', projection=True)  # set EEG average reference
 
 # For simplicity we will only consider the first 10 epochs
 events = mne.read_events(event_fname)[:10]
@@ -120,8 +120,7 @@ reject = dict(grad=4000e-13, eog=150e-6)
 event_id = dict(auditory_l=1, auditory_r=2, visual_l=3, visual_r=4)
 
 epochs = mne.Epochs(raw, events, event_id, tmin, tmax, proj=True, picks=picks,
-                    baseline=baseline, preload=True, reject=reject,
-                    add_eeg_ref=False)
+                    baseline=baseline, preload=True, reject=reject)
 
 ###############################################################################
 # Export DataFrame
@@ -133,10 +132,10 @@ epochs = mne.Epochs(raw, events, event_id, tmin, tmax, proj=True, picks=picks,
 # factors in a long table style commonly used for analyzing repeated measure
 # designs.
 
-index, scale_time, scalings = ['epoch', 'time'], 1e3, dict(grad=1e13)
+index, scaling_time, scalings = ['epoch', 'time'], 1e3, dict(grad=1e13)
 
-df = epochs.to_data_frame(picks=None, scalings=scalings, scale_time=scale_time,
-                          index=index)
+df = epochs.to_data_frame(picks=None, scalings=scalings,
+                          scaling_time=scaling_time, index=index)
 
 # Create MEG channel selector and drop EOG channel.
 meg_chs = [c for c in df.columns if 'MEG' in c]
diff --git a/tutorials/plot_forward.py b/tutorials/plot_forward.py
index a4d2554..a3f3f85 100644
--- a/tutorials/plot_forward.py
+++ b/tutorials/plot_forward.py
@@ -79,8 +79,8 @@ mne.viz.plot_bem(subject=subject, subjects_dir=subjects_dir,
 trans = data_path + '/MEG/sample/sample_audvis_raw-trans.fif'
 
 info = mne.io.read_info(raw_fname)
-mne.viz.plot_trans(info, trans, subject=subject, dig=True,
-                   meg_sensors=True, subjects_dir=subjects_dir)
+mne.viz.plot_alignment(info, trans, subject=subject, dig=True,
+                       meg=['helmet', 'sensors'], subjects_dir=subjects_dir)
 
 ###############################################################################
 # Compute Source Space
@@ -94,12 +94,11 @@ mne.viz.plot_trans(info, trans, subject=subject, dig=True,
 # and spacing parameter.
 
 src = mne.setup_source_space(subject, spacing='oct6',
-                             subjects_dir=subjects_dir,
-                             add_dist=False, overwrite=True)
+                             subjects_dir=subjects_dir, add_dist=False)
 print(src)
 
 ###############################################################################
-# src contains two parts, one for the left hemisphere (4098 locations) and
+# ``src`` contains two parts, one for the left hemisphere (4098 locations) and
 # one for the right hemisphere (4098 locations). Sources can be visualized on
 # top of the BEM surfaces.
 
@@ -115,7 +114,7 @@ from mayavi import mlab  # noqa
 from surfer import Brain  # noqa
 
 brain = Brain('sample', 'lh', 'inflated', subjects_dir=subjects_dir)
-surf = brain._geo
+surf = brain.geo['lh']
 
 vertidx = np.where(src[0]['inuse'])[0]
 
@@ -154,8 +153,7 @@ bem = mne.make_bem_solution(model)
 # See :func:`mne.make_forward_solution` for details on parameters meaning.
 
 fwd = mne.make_forward_solution(raw_fname, trans=trans, src=src, bem=bem,
-                                fname=None, meg=True, eeg=False,
-                                mindist=5.0, n_jobs=2)
+                                meg=True, eeg=False, mindist=5.0, n_jobs=2)
 print(fwd)
 
 ###############################################################################
@@ -166,16 +164,39 @@ leadfield = fwd['sol']['data']
 print("Leadfield size : %d sensors x %d dipoles" % leadfield.shape)
 
 ###############################################################################
+# To extract the numpy array containing the forward operator corresponding to
+# the source space `fwd['src']` with cortical orientation constraint
+# we can use the following:
+
+fwd_fixed = mne.convert_forward_solution(fwd, surf_ori=True, force_fixed=True,
+                                         use_cps=True)
+leadfield = fwd_fixed['sol']['data']
+print("Leadfield size : %d sensors x %d dipoles" % leadfield.shape)
+
+###############################################################################
+# This is equivalent to the following code that explicitly applies the
+# forward operator to a source estimate composed of the identity operator:
+n_dipoles = leadfield.shape[1]
+vertices = [src_hemi['vertno'] for src_hemi in fwd_fixed['src']]
+stc = mne.SourceEstimate(1e-9 * np.eye(n_dipoles), vertices, tmin=0., tstep=1)
+leadfield = mne.apply_forward(fwd_fixed, stc, info).data / 1e-9
+
+###############################################################################
 # To save to disk a forward solution you can use
 # :func:`mne.write_forward_solution` and to read it back from disk
 # :func:`mne.read_forward_solution`. Don't forget that FIF files containing
 # forward solution should end with *-fwd.fif*.
+#
+# To get a fixed-orientation forward solution, use
+# :func:`mne.convert_forward_solution` to convert the free-orientation
+# solution to (surface-oriented) fixed orientation.
 
 ###############################################################################
 # Exercise
 # --------
 #
-# By looking at :ref:`sphx_glr_auto_examples_forward_plot_read_forward.py`
+# By looking at
+# :ref:`sphx_glr_auto_examples_forward_plot_forward_sensitivity_maps.py`
 # plot the sensitivity maps for EEG and compare it with the MEG, can you
 # justify the claims that:
 #
diff --git a/tutorials/plot_ica_from_raw.py b/tutorials/plot_ica_from_raw.py
index 4b466ad..cca6851 100644
--- a/tutorials/plot_ica_from_raw.py
+++ b/tutorials/plot_ica_from_raw.py
@@ -21,17 +21,25 @@ from mne.preprocessing import create_ecg_epochs, create_eog_epochs
 from mne.datasets import sample
 
 ###############################################################################
-# Setup paths and prepare raw data
+# Setup paths and prepare raw data.
 
 data_path = sample.data_path()
 raw_fname = data_path + '/MEG/sample/sample_audvis_filt-0-40_raw.fif'
 
-raw = mne.io.read_raw_fif(raw_fname, preload=True, add_eeg_ref=False)
+raw = mne.io.read_raw_fif(raw_fname, preload=True)
 raw.filter(1, 45, n_jobs=1, l_trans_bandwidth=0.5, h_trans_bandwidth=0.5,
-           filter_length='10s', phase='zero-double')
+           filter_length='10s', phase='zero-double', fir_design='firwin2')
+raw.annotations = mne.Annotations([1], [10], 'BAD')
+raw.plot(block=True)
+
+# For the sake of example we annotate first 10 seconds of the recording as
+# 'BAD'. This part of data is excluded from the ICA decomposition by default.
+# To turn this behavior off, pass ``reject_by_annotation=False`` to
+# :meth:`mne.preprocessing.ICA.fit`.
+raw.annotations = mne.Annotations([0], [10], 'BAD')
 
 ###############################################################################
-# 1) Fit ICA model using the FastICA algorithm
+# 1) Fit ICA model using the FastICA algorithm.
 
 # Other available choices are `infomax` or `extended-infomax`
 # We pass a float value between 0 and 1 to select n_components based on the
@@ -81,7 +89,7 @@ eog_inds = eog_inds[:n_max_eog]
 ica.exclude += eog_inds
 
 ###############################################################################
-# 3) Assess component selection and unmixing quality
+# 3) Assess component selection and unmixing quality.
 
 # estimate average artifact
 ecg_evoked = ecg_epochs.average()
diff --git a/tutorials/plot_info.py b/tutorials/plot_info.py
index 031c9f8..1b85dea 100644
--- a/tutorials/plot_info.py
+++ b/tutorials/plot_info.py
@@ -2,29 +2,27 @@
 .. _tut_info_objects:
 
 The :class:`Info <mne.Info>` data structure
-==============================================
-"""
+===========================================
+
+The :class:`Info <mne.Info>` data object is typically created
+when data is imported into MNE-Python and contains details such as:
 
-from __future__ import print_function
+- date, subject information, and other recording details
+- the sampling rate
+- information about the data channels (name, type, position, etc.)
+- digitized points
+- sensor–head coordinate transformation matrices
+
+and so forth. See the :class:`the API reference <mne.Info>`
+for a complete list of all data fields. Once created, this object is passed
+around throughout the data analysis pipeline.
+"""
 
 import mne
 import os.path as op
 
 ###############################################################################
-# The :class:`Info <mne.Info>` data object is typically created
-# when data is imported into MNE-Python and contains details such as:
-#
-#  - date, subject information, and other recording details
-#  - the sampling rate
-#  - information about the data channels (name, type, position, etc.)
-#  - digitized points
-#  - sensor–head coordinate transformation matrices
-#
-# and so forth. See the :class:`the API reference <mne.Info>`
-# for a complete list of all data fields. Once created, this object is passed
-# around throughout the data analysis pipeline.
-#
-# It behaves as a nested Python dictionary:
+# :class:`mne.Info` behaves as a nested Python dictionary:
 
 # Read the info object from an example recording
 info = mne.io.read_info(
@@ -61,6 +59,28 @@ channel_indices = mne.pick_channels(info['ch_names'], ['MEG 0312', 'EEG 005'])
 channel_indices = mne.pick_channels_regexp(info['ch_names'], 'MEG *')
 
 ###############################################################################
+# Channel types
+# -------------
+#
+# MNE supports different channel types:
+#
+# - eeg : For EEG channels with data stored in Volts (V)
+# - meg (mag) : For MEG magnetometers channels stored in Tesla (T)
+# - meg (grad) : For MEG gradiometers channels stored in Tesla/Meter (T/m)
+# - ecg : For ECG channels stored in Volts (V)
+# - seeg : For Stereotactic EEG channels in Volts (V).
+# - ecog : For Electrocorticography (ECoG) channels in Volts (V).
+# - fnirs (HBO) : Functional near-infrared spectroscopy oxyhemoglobin data.
+# - fnirs (HBR) : Functional near-infrared spectroscopy deoxyhemoglobin data.
+# - emg : For EMG channels stored in Volts (V)
+# - bio : For biological channels (AU).
+# - stim : For the stimulus (a.k.a. trigger) channels (AU)
+# - resp : For the response-trigger channel (AU)
+# - chpi : For HPI coil channels (T).
+# - exci : Flux excitation channel used to be a stimulus channel.
+# - ias : For Internal Active Shielding data (maybe on Triux only).
+# - syst : System status channel information (on Triux systems only).
+#
 # Get channel indices by type
 channel_indices = mne.pick_types(info, meg=True)  # MEG only
 channel_indices = mne.pick_types(info, eeg=True)  # EEG only
diff --git a/tutorials/plot_introduction.py b/tutorials/plot_introduction.py
index 67dc9d9..ded7609 100644
--- a/tutorials/plot_introduction.py
+++ b/tutorials/plot_introduction.py
@@ -50,7 +50,6 @@ What you're not supposed to do with MNE Python
 
     - **Brain and head surface segmentation** for use with BEM
       models -- use Freesurfer.
-    - **Raw movement compensation** -- use Elekta Maxfilter™
 
 
 .. note:: This package is based on the FIF file format from Neuromag. It
@@ -61,10 +60,10 @@ What you're not supposed to do with MNE Python
 Installation of the required materials
 ---------------------------------------
 
-See :ref:`getting_started` with Python.
+See :ref:`install_python_and_mne_python`.
 
 .. note:: The expected location for the MNE-sample data is
-    my-path-to/mne-python/examples. If you downloaded data and an example asks
+    ``~/mne_data``. If you downloaded data and an example asks
     you whether to download it again, make sure
     the data reside in the examples directory and you run the script from its
     current directory.
@@ -118,7 +117,7 @@ mne.get_config_path()
 
 ##############################################################################
 # By default logging messages print to the console, but look at
-# mne.set_log_file() to save output to a file.
+# :func:`mne.set_log_file` to save output to a file.
 #
 # Access raw data
 # ^^^^^^^^^^^^^^^
@@ -134,7 +133,7 @@ print(raw_fname)
 #
 # Read data from file:
 
-raw = mne.io.read_raw_fif(raw_fname, add_eeg_ref=False)
+raw = mne.io.read_raw_fif(raw_fname)
 print(raw)
 print(raw.info)
 
@@ -178,7 +177,7 @@ print(events[:5])
 mne.set_config('MNE_STIM_CHANNEL', 'STI101', set_env=True)
 
 ##############################################################################
-# Events are stored as 2D numpy array where the first column is the time
+# Events are stored as a 2D numpy array where the first column is the time
 # instant and the last one is the event number. It is therefore easy to
 # manipulate.
 #
@@ -222,8 +221,7 @@ reject = dict(grad=4000e-13, mag=4e-12, eog=150e-6)
 # Read epochs:
 
 epochs = mne.Epochs(raw, events, event_id, tmin, tmax, proj=True, picks=picks,
-                    baseline=baseline, preload=False, reject=reject,
-                    add_eeg_ref=False)
+                    baseline=baseline, preload=False, reject=reject)
 print(epochs)
 
 ##############################################################################
@@ -285,7 +283,7 @@ evoked2 = mne.read_evokeds(
 # Two evoked objects can be contrasted using :func:`mne.combine_evoked`.
 # This function can use ``weights='equal'``, which provides a simple
 # element-by-element subtraction (and sets the
-# :attr:`mne.Evoked.nave` attribute properly based on the underlying number
+# ``mne.Evoked.nave`` attribute properly based on the underlying number
 # of trials) using either equivalent call:
 
 contrast = mne.combine_evoked([evoked1, evoked2], weights=[0.5, -0.5])
diff --git a/tutorials/plot_mne_dspm_source_localization.py b/tutorials/plot_mne_dspm_source_localization.py
index 7b4ded1..09a71b7 100644
--- a/tutorials/plot_mne_dspm_source_localization.py
+++ b/tutorials/plot_mne_dspm_source_localization.py
@@ -4,7 +4,7 @@
 Source localization with MNE/dSPM/sLORETA
 =========================================
 
-The aim of this tutorials is to teach you how to compute and apply a linear
+The aim of this tutorial is to teach you how to compute and apply a linear
 inverse method such as MNE/dSPM/sLORETA on evoked/raw/epochs data.
 
 """
@@ -16,14 +16,15 @@ from mne.datasets import sample
 from mne.minimum_norm import (make_inverse_operator, apply_inverse,
                               write_inverse_operator)
 
+# sphinx_gallery_thumbnail_number = 9
+
 ###############################################################################
 # Process MEG data
 
 data_path = sample.data_path()
 raw_fname = data_path + '/MEG/sample/sample_audvis_filt-0-40_raw.fif'
 
-raw = mne.io.read_raw_fif(raw_fname, add_eeg_ref=False)
-raw.set_eeg_reference()  # set EEG average reference
+raw = mne.io.read_raw_fif(raw_fname)  # already has an average reference
 events = mne.find_events(raw, stim_channel='STI 014')
 
 event_id = dict(aud_r=1)  # event trigger and conditions
@@ -36,7 +37,7 @@ baseline = (None, 0)  # means from the first instant to t = 0
 reject = dict(grad=4000e-13, mag=4e-12, eog=150e-6)
 
 epochs = mne.Epochs(raw, events, event_id, tmin, tmax, proj=True, picks=picks,
-                    baseline=baseline, reject=reject, add_eeg_ref=False)
+                    baseline=baseline, reject=reject)
 
 ###############################################################################
 # Compute regularized noise covariance
@@ -67,7 +68,8 @@ evoked.plot_white(noise_cov)
 # Read the forward solution and compute the inverse operator
 
 fname_fwd = data_path + '/MEG/sample/sample_audvis-meg-oct-6-fwd.fif'
-fwd = mne.read_forward_solution(fname_fwd, surf_ori=True)
+fwd = mne.read_forward_solution(fname_fwd)
+fwd = mne.convert_forward_solution(fwd, surf_ori=True)
 
 # Restrict forward solution as necessary for MEG
 fwd = mne.pick_types_forward(fwd, meg=True, eeg=False)
@@ -90,13 +92,14 @@ lambda2 = 1. / snr ** 2
 stc = apply_inverse(evoked, inverse_operator, lambda2,
                     method=method, pick_ori=None)
 
-del fwd, inverse_operator, epochs  # to save memory
+del fwd, epochs  # to save memory
 
 ###############################################################################
 # Visualization
 # -------------
 # View activation time-series
 
+plt.figure()
 plt.plot(1e3 * stc.times, stc.data[::100, :].T)
 plt.xlabel('time (ms)')
 plt.ylabel('%s value' % method)
@@ -120,18 +123,42 @@ brain.show_view('lateral')
 # Morph data to average brain
 # ---------------------------
 
-fs_vertices = [np.arange(10242)] * 2
+fs_vertices = [np.arange(10242)] * 2  # fsaverage is special this way
 morph_mat = mne.compute_morph_matrix('sample', 'fsaverage', stc.vertices,
                                      fs_vertices, smooth=None,
                                      subjects_dir=subjects_dir)
 stc_fsaverage = stc.morph_precomputed('fsaverage', fs_vertices, morph_mat)
-brain_fsaverage = stc_fsaverage.plot(surface='inflated', hemi='rh',
-                                     subjects_dir=subjects_dir,
-                                     clim=dict(kind='value', lims=[8, 12, 15]),
-                                     initial_time=time_max, time_unit='s')
+brain_fsaverage = stc_fsaverage.plot(
+    surface='inflated', hemi='rh', subjects_dir=subjects_dir,
+    clim=dict(kind='value', lims=[8, 12, 15]), initial_time=time_max,
+    time_unit='s', size=(800, 800), smoothing_steps=5)
 brain_fsaverage.show_view('lateral')
 
 ###############################################################################
+# Dipole orientations
+# -------------------
+# The ``pick_ori`` parameter of the
+# :func:`mne.minimum_norm.apply_inverse` function controls
+# the orientation of the dipoles. One useful setting is ``pick_ori='vector'``,
+# which will return an estimate that does not only contain the source power at
+# each dipole, but also the orientation of the dipoles.
+
+stc_vec = apply_inverse(evoked, inverse_operator, lambda2,
+                        method=method, pick_ori='vector')
+stc_vec.plot(hemi='rh', subjects_dir=subjects_dir,
+             clim=dict(kind='value', lims=[8, 12, 15]),
+             initial_time=time_max, time_unit='s')
+
+###############################################################################
+# Note that there is a relationship between the orientation of the dipoles and
+# the surface of the cortex. For this reason, we do not use an inflated
+# cortical surface for visualization, but the original surface used to define
+# the source space.
+#
+# For more information about dipole orientations, see
+# :ref:`tut_dipole_orentiations`.
+
+###############################################################################
 # Exercise
 # --------
 #    - By changing the method parameter to 'sloreta' recompute the source
diff --git a/tutorials/plot_modifying_data_inplace.py b/tutorials/plot_modifying_data_inplace.py
index ddcab02..3d7e087 100644
--- a/tutorials/plot_modifying_data_inplace.py
+++ b/tutorials/plot_modifying_data_inplace.py
@@ -3,9 +3,12 @@
 
 Modifying data in-place
 =======================
-"""
 
-from __future__ import print_function
+It is often necessary to modify data once you have loaded it into memory.
+Common examples of this are signal processing, feature extraction, and data
+cleaning. Some functionality is pre-built into MNE-python, though it is also
+possible to apply an arbitrary function to the data.
+"""
 
 import mne
 import os.path as op
@@ -13,15 +16,10 @@ import numpy as np
 from matplotlib import pyplot as plt
 
 ###############################################################################
-# It is often necessary to modify data once you have loaded it into memory.
-# Common examples of this are signal processing, feature extraction, and data
-# cleaning. Some functionality is pre-built into MNE-python, though it is also
-# possible to apply an arbitrary function to the data.
-
 # Load an example dataset, the preload flag loads the data into memory now
 data_path = op.join(mne.datasets.sample.data_path(), 'MEG',
                     'sample', 'sample_audvis_raw.fif')
-raw = mne.io.read_raw_fif(data_path, preload=True, add_eeg_ref=False)
+raw = mne.io.read_raw_fif(data_path, preload=True)
 raw = raw.crop(0, 10)
 print(raw)
 
@@ -33,11 +31,11 @@ print(raw)
 
 filt_bands = [(1, 3), (3, 10), (10, 20), (20, 60)]
 f, (ax, ax2) = plt.subplots(2, 1, figsize=(15, 10))
-_ = ax.plot(raw._data[0])
-for fband in filt_bands:
+data, times = raw[0]
+_ = ax.plot(data[0])
+for fmin, fmax in filt_bands:
     raw_filt = raw.copy()
-    raw_filt.filter(*fband, h_trans_bandwidth='auto', l_trans_bandwidth='auto',
-                    filter_length='auto', phase='zero')
+    raw_filt.filter(fmin, fmax, fir_design='firwin')
     _ = ax2.plot(raw_filt[0][0][0])
 ax2.legend(filt_bands)
 ax.set_title('Raw data')
@@ -51,11 +49,11 @@ ax2.set_title('Band-pass filtered data')
 
 raw_band = raw.copy()
 raw_band.filter(12, 18, l_trans_bandwidth=2., h_trans_bandwidth=2.,
-                filter_length='auto', phase='zero')
+                fir_design='firwin')
 raw_hilb = raw_band.copy()
 hilb_picks = mne.pick_types(raw_band.info, meg=False, eeg=True)
 raw_hilb.apply_hilbert(hilb_picks)
-print(raw_hilb._data.dtype)
+print(raw_hilb[0][0].dtype)
 
 ###############################################################################
 # Finally, it is possible to apply arbitrary functions to your data to do
@@ -68,13 +66,13 @@ print(raw_hilb._data.dtype)
 
 # Take the amplitude and phase
 raw_amp = raw_hilb.copy()
-raw_amp.apply_function(np.abs, hilb_picks, float, 1)
+raw_amp.apply_function(np.abs, hilb_picks)
 raw_phase = raw_hilb.copy()
-raw_phase.apply_function(np.angle, hilb_picks, float, 1)
+raw_phase.apply_function(np.angle, hilb_picks)
 
 f, (a1, a2) = plt.subplots(2, 1, figsize=(15, 10))
-a1.plot(raw_band._data[hilb_picks[0]])
-a1.plot(raw_amp._data[hilb_picks[0]])
-a2.plot(raw_phase._data[hilb_picks[0]])
+a1.plot(raw_band[hilb_picks[0]][0][0].real)
+a1.plot(raw_amp[hilb_picks[0]][0][0].real)
+a2.plot(raw_phase[hilb_picks[0]][0][0].real)
 a1.set_title('Amplitude of frequency band')
 a2.set_title('Phase of frequency band')
diff --git a/tutorials/plot_object_epochs.py b/tutorials/plot_object_epochs.py
index 992775c..61b83cd 100644
--- a/tutorials/plot_object_epochs.py
+++ b/tutorials/plot_object_epochs.py
@@ -3,9 +3,13 @@
 
 The :class:`Epochs <mne.Epochs>` data structure: epoched data
 =============================================================
-"""
 
-from __future__ import print_function
+:class:`Epochs <mne.Epochs>` objects are a way of representing continuous
+data as a collection of time-locked trials, stored in an array of shape
+``(n_events, n_channels, n_times)``. They are useful for many statistical
+methods in neuroscience, and make it easy to quickly overview what occurs
+during a trial.
+"""
 
 import mne
 import os.path as op
@@ -13,14 +17,8 @@ import numpy as np
 from matplotlib import pyplot as plt
 
 ###############################################################################
-# :class:`Epochs <mne.Epochs>` objects are a way of representing continuous
-# data as a collection of time-locked trials, stored in an array of
-# `shape(n_events, n_channels, n_times)`. They are useful for many statistical
-# methods in neuroscience, and make it easy to quickly overview what occurs
-# during a trial.
-#
 # :class:`Epochs <mne.Epochs>` objects can be created in three ways:
-#  1. From a :class:`Raw <mne.io.RawFIF>` object, along with event times
+#  1. From a :class:`Raw <mne.io.Raw>` object, along with event times
 #  2. From an :class:`Epochs <mne.Epochs>` object that has been saved as a
 #     `.fif` file
 #  3. From scratch using :class:`EpochsArray <mne.EpochsArray>`. See
@@ -29,8 +27,7 @@ from matplotlib import pyplot as plt
 data_path = mne.datasets.sample.data_path()
 # Load a dataset that contains events
 raw = mne.io.read_raw_fif(
-    op.join(data_path, 'MEG', 'sample', 'sample_audvis_raw.fif'),
-    add_eeg_ref=False)
+    op.join(data_path, 'MEG', 'sample', 'sample_audvis_raw.fif'))
 
 # If your raw object has a stim channel, you can construct an event array
 # easily
@@ -58,7 +55,7 @@ event_id = {'Auditory/Left': 1, 'Auditory/Right': 2}
 # Expose the raw data as epochs, cut from -0.1 s to 1.0 s relative to the event
 # onsets
 epochs = mne.Epochs(raw, events, event_id, tmin=-0.1, tmax=1,
-                    baseline=(None, 0), preload=True, add_eeg_ref=False)
+                    baseline=(None, 0), preload=True)
 print(epochs)
 
 ###############################################################################
@@ -67,7 +64,8 @@ print(epochs)
 # information, as well as a number of attributes unique to the events contained
 # within the object.
 
-print(epochs.events[:3], epochs.event_id, sep='\n\n')
+print(epochs.events[:3])
+print(epochs.event_id)
 
 ###############################################################################
 # You can select subsets of epochs by indexing the :class:`Epochs <mne.Epochs>`
diff --git a/tutorials/plot_object_evoked.py b/tutorials/plot_object_evoked.py
index c66404d..9234325 100644
--- a/tutorials/plot_object_evoked.py
+++ b/tutorials/plot_object_evoked.py
@@ -3,16 +3,18 @@
 
 The :class:`Evoked <mne.Evoked>` data structure: evoked/averaged data
 =====================================================================
+
+The :class:`Evoked <mne.Evoked>` data structure is mainly used for storing
+averaged data over trials. In MNE the evoked objects are usually created by
+averaging epochs data with :func:`mne.Epochs.average`.
 """
+
 import os.path as op
 
 import mne
 
 ###############################################################################
-# The :class:`Evoked <mne.Evoked>` data structure is mainly used for storing
-# averaged data over trials. In MNE the evoked objects are created by averaging
-# epochs data with :func:`mne.Epochs.average`. Here we read the evoked dataset
-# from a file.
+# Here for convenience we read the evoked dataset from a file.
 data_path = mne.datasets.sample.data_path()
 fname = op.join(data_path, 'MEG', 'sample', 'sample_audvis-ave.fif')
 evokeds = mne.read_evokeds(fname, baseline=(None, 0), proj=True)
@@ -24,8 +26,8 @@ print(evokeds)
 # categories of
 # ``['Left Auditory', 'Right Auditory', 'Left Visual', 'Right Visual']``.
 # We can also use ``condition`` parameter to read in only one category.
-evoked = mne.read_evokeds(fname, condition='Left Auditory', baseline=(None, 0),
-                          proj=True)
+evoked = mne.read_evokeds(fname, condition='Left Auditory')
+evoked.apply_baseline((None, 0)).apply_proj()
 print(evoked)
 
 ###############################################################################
@@ -70,5 +72,5 @@ evoked = mne.EvokedArray(data, evoked.info, tmin=evoked.times[0])
 evoked.plot()
 
 ###############################################################################
-# To write an evoked dataset to a file, use the :func:`mne.Evoked.save' method.
+# To write an evoked dataset to a file, use the :meth:`mne.Evoked.save` method.
 # To save multiple categories to a single file, see :func:`mne.write_evokeds`.
diff --git a/tutorials/plot_object_raw.py b/tutorials/plot_object_raw.py
index efe6d5c..1fade35 100644
--- a/tutorials/plot_object_raw.py
+++ b/tutorials/plot_object_raw.py
@@ -4,44 +4,41 @@
 
 The :class:`Raw <mne.io.Raw>` data structure: continuous data
 =============================================================
-"""
 
-from __future__ import print_function
+Continuous data is stored in objects of type :class:`Raw <mne.io.Raw>`.
+The core data structure is simply a 2D numpy array (channels × samples)
+(in memory or loaded on demand) combined with an
+:class:`Info <mne.Info>` object (`.info` attribute)
+(see :ref:`tut_info_objects`).
+
+The most common way to load continuous data is from a .fif file. For more
+information on :ref:`loading data from other formats <ch_convert>`, or
+creating it :ref:`from scratch <tut_creating_data_structures>`.
+"""
 
 import mne
 import os.path as op
 from matplotlib import pyplot as plt
 
 ###############################################################################
-# Continuous data is stored in objects of type :class:`Raw <mne.io.Raw>`.
-# The core data structure is simply a 2D numpy array (channels × samples,
-# stored in a private attribute called `._data`) combined with an
-# :class:`Info <mne.Info>` object (`.info` attribute)
-# (see :ref:`tut_info_objects`).
-#
-# The most common way to load continuous data is from a .fif file. For more
-# information on :ref:`loading data from other formats <ch_convert>`, or
-# creating it :ref:`from scratch <tut_creating_data_structures>`.
-
-
-###############################################################################
 # Loading continuous data
 # -----------------------
+#
+# Load an example dataset, the preload flag loads the data into memory now:
 
-# Load an example dataset, the preload flag loads the data into memory now
 data_path = op.join(mne.datasets.sample.data_path(), 'MEG',
                     'sample', 'sample_audvis_raw.fif')
-raw = mne.io.read_raw_fif(data_path, preload=True, add_eeg_ref=False)
-raw.set_eeg_reference()  # set EEG average reference
+raw = mne.io.read_raw_fif(data_path, preload=True)
+raw.set_eeg_reference('average', projection=True)  # set EEG average reference
 
 # Give the sample rate
 print('sample rate:', raw.info['sfreq'], 'Hz')
 # Give the size of the data matrix
-print('channels x samples:', raw._data.shape)
+print('%s channels x %s samples' % (len(raw), len(raw.times)))
 
 ###############################################################################
-# .. note:: Accessing the `._data` attribute is done here for educational
-#           purposes. However this is a private attribute as its name starts
+# .. note:: This size can also be obtained by examining `raw._data.shape`.
+#           However this is a private attribute as its name starts
 #           with an `_`. This suggests that you should **not** access this
 #           variable directly but rely on indexing syntax detailed just below.
 
@@ -89,7 +86,10 @@ grad_only = raw.copy().pick_types(meg='grad')
 # Or you can use custom channel names
 pick_chans = ['MEG 0112', 'MEG 0111', 'MEG 0122', 'MEG 0123']
 specific_chans = raw.copy().pick_channels(pick_chans)
-print(meg_only, eeg_only, grad_only, specific_chans, sep='\n')
+print(meg_only)
+print(eeg_only)
+print(grad_only)
+print(specific_chans)
 
 ###############################################################################
 # Notice the different scalings of these types
diff --git a/tutorials/plot_point_spread.py b/tutorials/plot_point_spread.py
index 4512a32..74cd956 100644
--- a/tutorials/plot_point_spread.py
+++ b/tutorials/plot_point_spread.py
@@ -12,6 +12,7 @@ signal with point-spread by applying a forward and inverse solution.
 import os.path as op
 
 import numpy as np
+from mayavi import mlab
 
 import mne
 from mne.datasets import sample
@@ -31,7 +32,7 @@ lambda2 = 1.0 / snr ** 2
 
 # signal simulation parameters
 # do not add extra noise to the known signals
-evoked_snr = np.inf
+nave = np.inf
 T = 100
 times = np.linspace(0, 1, T)
 dt = times[1] - times[0]
@@ -51,8 +52,9 @@ fname_evoked = op.join(data_path, 'MEG', 'sample',
 # Load the MEG data
 # -----------------
 
-fwd = mne.read_forward_solution(fname_fwd, force_fixed=True,
-                                surf_ori=True)
+fwd = mne.read_forward_solution(fname_fwd)
+fwd = mne.convert_forward_solution(fwd, force_fixed=True, surf_ori=True,
+                                   use_cps=False)
 fwd['info']['bads'] = []
 inv_op = read_inverse_operator(fname_inv)
 
@@ -131,10 +133,12 @@ stc_gen = simulate_stc(fwd['src'], labels, signal, times[0], dt,
 #
 # Note that the original signals are highly concentrated (point) sources.
 #
-kwargs = dict(subjects_dir=subjects_dir, hemi='split', views=['lat', 'med'],
-              smoothing_steps=4, time_unit='s', initial_time=0.05)
+kwargs = dict(subjects_dir=subjects_dir, hemi='split', smoothing_steps=4,
+              time_unit='s', initial_time=0.05, size=1200,
+              views=['lat', 'med'])
 clim = dict(kind='value', pos_lims=[1e-9, 1e-8, 1e-7])
-brain_gen = stc_gen.plot(clim=clim, **kwargs)
+figs = [mlab.figure(1), mlab.figure(2), mlab.figure(3), mlab.figure(4)]
+brain_gen = stc_gen.plot(clim=clim, figure=figs, **kwargs)
 
 ###############################################################################
 # Simulate sensor-space signals
@@ -142,10 +146,10 @@ brain_gen = stc_gen.plot(clim=clim, **kwargs)
 #
 # Use the forward solution and add Gaussian noise to simulate sensor-space
 # (evoked) data from the known source-space signals. The amount of noise is
-# controlled by `evoked_snr` (higher values imply less noise).
+# controlled by `nave` (higher values imply less noise).
 #
-evoked_gen = simulate_evoked(fwd, stc_gen, evoked.info, cov, evoked_snr,
-                             tmin=0., tmax=1., random_state=seed)
+evoked_gen = simulate_evoked(fwd, stc_gen, evoked.info, cov, nave,
+                             random_state=seed)
 
 # Map the simulated sensor-space data to source-space using the inverse
 # operator.
@@ -160,7 +164,8 @@ stc_inv = apply_inverse(evoked_gen, inv_op, lambda2, method=method)
 # This spread is due to the minimum norm solution so that the signal leaks to
 # nearby vertices with similar orientations so that signal ends up crossing the
 # sulci and gyri.
-brain_inv = stc_inv.plot(**kwargs)
+figs = [mlab.figure(5), mlab.figure(6), mlab.figure(7), mlab.figure(8)]
+brain_inv = stc_inv.plot(figure=figs, **kwargs)
 
 ###############################################################################
 # Exercises
diff --git a/tutorials/plot_python_intro.py b/tutorials/plot_python_intro.py
index c3c9ee8..6e89749 100644
--- a/tutorials/plot_python_intro.py
+++ b/tutorials/plot_python_intro.py
@@ -3,26 +3,26 @@
 
 Introduction to Python
 ======================
-"""
 
+Python is a modern, general-purpose, object-oriented, high-level programming
+language. First make sure you have a working python environment and
+dependencies (see :ref:`install_python_and_mne_python`). If you are
+completely new to python, don't worry, it's just like any other programming
+language, only easier. Here are a few great resources to get you started:
+
+* `SciPy lectures <http://scipy-lectures.github.io>`_
+* `Learn X in Y minutes: Python <https://learnxinyminutes.com/docs/python/>`_
+* `NumPy for MATLAB users <https://docs.scipy.org/doc/numpy-dev/user/numpy-for-matlab-users.html>`_  # noqa
+
+We highly recommend watching the Scipy videos and reading through these
+sites to get a sense of how scientific computing is done in Python.
+"""
 ###############################################################################
-# Python is a modern, general-purpose, object-oriented, high-level programming
-# language. First make sure you have a working python environment and
-# dependencies (see :ref:`install_python_and_mne_python`). If you are
-# completely new to python, don't worry, it's just like any other programming
-# language, only easier. Here are a few great resources to get you started:
-#
-# * `SciPy lectures <http://scipy-lectures.github.io>`_
-# * `Learn X in Y minutes: Python <https://learnxinyminutes.com/docs/python/>`_
-# * `NumPy for MATLAB users <https://docs.scipy.org/doc/numpy-dev/user/numpy-for-matlab-users.html>`_  # noqa
-#
-# We highly recommend watching the Scipy videos and reading through these
-# sites to get a sense of how scientific computing is done in Python.
-#
 # Here are few bulletin points to familiarise yourself with python:
 #
-# Everything is dynamically typed. No need to declare simple data
-# structures or variables separately.
+# * Everything is dynamically typed. No need to declare simple data
+#   structures or variables separately.
+
 a = 3
 print(type(a))
 b = [1, 2.5, 'This is a string']
@@ -31,16 +31,16 @@ c = 'Hello world!'
 print(type(c))
 
 ###############################################################################
-# If you come from a background of matlab, remember that indexing in python
-# starts from zero:
+# * If you come from a background of matlab, remember that indexing in python
+#   starts from zero:
 a = [1, 2, 3, 4]
 print('This is the zeroth value in the list: {}'.format(a[0]))
 
 ###############################################################################
-# No need to reinvent the wheel. Scipy and Numpy are battle field tested
-# libraries that have a vast variety of functions for your needs. Consult the
-# documentation and remember, you can always ask the interpreter for help with
-# a question mark at the end of a function::
+# * No need to reinvent the wheel. Scipy and Numpy are battle field tested
+#   libraries that have a vast variety of functions for your needs. Consult the
+#   documentation and remember, you can always ask the IPython interpreter for
+#   help with a question mark at the end of a function::
 #
-#    >>> import numpy as np
-#    >>> np.arange?
+#      >>> import numpy as np
+#      >>> np.arange?
diff --git a/tutorials/plot_receptive_field.py b/tutorials/plot_receptive_field.py
new file mode 100644
index 0000000..0b7a0ca
--- /dev/null
+++ b/tutorials/plot_receptive_field.py
@@ -0,0 +1,371 @@
+# -*- coding: utf-8 -*-
+"""
+=====================================================================
+Spectro-temporal receptive field (STRF) estimation on continuous data
+=====================================================================
+
+This demonstrates how an encoding model can be fit with multiple continuous
+inputs. In this case, we simulate the model behind a spectro-temporal receptive
+field (or STRF). First, we create a linear filter that maps patterns in
+spectro-temporal space onto an output, representing neural activity. We fit
+a receptive field model that attempts to recover the original linear filter
+that was used to create this data.
+
+References
+----------
+Estimation of spectro-temporal and spatio-temporal receptive fields using
+modeling with continuous inputs is described in:
+
+.. [1] Theunissen, F. E. et al. Estimating spatio-temporal receptive
+       fields of auditory and visual neurons from their responses to
+       natural stimuli. Network 12, 289-316 (2001).
+
+.. [2] Willmore, B. & Smyth, D. Methods for first-order kernel
+       estimation: simple-cell receptive fields from responses to
+       natural scenes. Network 14, 553-77 (2003).
+
+.. [3] Crosse, M. J., Di Liberto, G. M., Bednar, A. & Lalor, E. C. (2016).
+       The Multivariate Temporal Response Function (mTRF) Toolbox:
+       A MATLAB Toolbox for Relating Neural Signals to Continuous Stimuli.
+       Frontiers in Human Neuroscience 10, 604.
+       doi:10.3389/fnhum.2016.00604
+
+.. [4] Holdgraf, C. R. et al. Rapid tuning shifts in human auditory cortex
+       enhance speech intelligibility. Nature Communications, 7, 13654 (2016).
+       doi:10.1038/ncomms13654
+
+.. [5] Crosse, M. J., Di Liberto, G. M., Bednar, A. & Lalor, E. C. (2016).
+       The Multivariate Temporal Response Function (mTRF) Toolbox:
+       A MATLAB Toolbox for Relating Neural Signals to Continuous Stimuli.
+       Frontiers in Human Neuroscience 10, 604. doi:10.3389/fnhum.2016.00604
+"""
+# Authors: Chris Holdgraf <choldgraf at gmail.com>
+#          Eric Larson <larson.eric.d at gmail.com>
+#
+# License: BSD (3-clause)
+
+# sphinx_gallery_thumbnail_number = 7
+
+import numpy as np
+import matplotlib.pyplot as plt
+
+import mne
+from mne.decoding import ReceptiveField, TimeDelayingRidge
+
+from scipy.stats import multivariate_normal
+from scipy.io import loadmat
+from sklearn.preprocessing import scale
+rng = np.random.RandomState(1337)  # To make this example reproducible
+
+###############################################################################
+# Load audio data
+# ---------------
+#
+# We'll read in the audio data from [3]_ in order to simulate a response.
+#
+# In addition, we'll downsample the data along the time dimension in order to
+# speed up computation. Note that depending on the input values, this may
+# not be desired. For example if your input stimulus varies more quickly than
+# 1/2 the sampling rate to which we are downsampling.
+
+# Read in audio that's been recorded in epochs.
+path_audio = mne.datasets.mtrf.data_path()
+data = loadmat(path_audio + '/speech_data.mat')
+audio = data['spectrogram'].T
+sfreq = float(data['Fs'][0, 0])
+n_decim = 2
+audio = mne.filter.resample(audio, down=n_decim, npad='auto')
+sfreq /= n_decim
+
+###############################################################################
+# Create a receptive field
+# ------------------------
+#
+# We'll simulate a linear receptive field for a theoretical neural signal. This
+# defines how the signal will respond to power in this receptive field space.
+n_freqs = 20
+tmin, tmax = -0.1, 0.4
+
+# To simulate the data we'll create explicit delays here
+delays_samp = np.arange(np.round(tmin * sfreq),
+                        np.round(tmax * sfreq) + 1).astype(int)
+delays_sec = delays_samp / sfreq
+freqs = np.linspace(50, 5000, n_freqs)
+grid = np.array(np.meshgrid(delays_sec, freqs))
+
+# We need data to be shaped as n_epochs, n_features, n_times, so swap axes here
+grid = grid.swapaxes(0, -1).swapaxes(0, 1)
+
+# Simulate a temporal receptive field with a Gabor filter
+means_high = [.1, 500]
+means_low = [.2, 2500]
+cov = [[.001, 0], [0, 500000]]
+gauss_high = multivariate_normal.pdf(grid, means_high, cov)
+gauss_low = -1 * multivariate_normal.pdf(grid, means_low, cov)
+weights = gauss_high + gauss_low  # Combine to create the "true" STRF
+kwargs = dict(vmax=np.abs(weights).max(), vmin=-np.abs(weights).max(),
+              cmap='RdBu_r', shading='gouraud')
+
+fig, ax = plt.subplots()
+ax.pcolormesh(delays_sec, freqs, weights, **kwargs)
+ax.set(title='Simulated STRF', xlabel='Time Lags (s)', ylabel='Frequency (Hz)')
+plt.setp(ax.get_xticklabels(), rotation=45)
+plt.autoscale(tight=True)
+mne.viz.tight_layout()
+
+
+###############################################################################
+# Simulate a neural response
+# --------------------------
+#
+# Using this receptive field, we'll create an artificial neural response to
+# a stimulus.
+#
+# To do this, we'll create a time-delayed version of the receptive field, and
+# then calculate the dot product between this and the stimulus. Note that this
+# is effectively doing a convolution between the stimulus and the receptive
+# field. See `here <https://en.wikipedia.org/wiki/Convolution>`_ for more
+# information.
+
+# Reshape audio to split into epochs, then make epochs the first dimension.
+n_epochs, n_seconds = 16, 5
+audio = audio[:, :int(n_seconds * sfreq * n_epochs)]
+X = audio.reshape([n_freqs, n_epochs, -1]).swapaxes(0, 1)
+n_times = X.shape[-1]
+
+# Delay the spectrogram according to delays so it can be combined w/ the STRF
+# Lags will now be in axis 1, then we reshape to vectorize
+delays = np.arange(np.round(tmin * sfreq),
+                   np.round(tmax * sfreq) + 1).astype(int)
+
+# Iterate through indices and append
+X_del = np.zeros((len(delays),) + X.shape)
+for ii, ix_delay in enumerate(delays):
+    # These arrays will take/put particular indices in the data
+    take = [slice(None)] * X.ndim
+    put = [slice(None)] * X.ndim
+    if ix_delay > 0:
+        take[-1] = slice(None, -ix_delay)
+        put[-1] = slice(ix_delay, None)
+    elif ix_delay < 0:
+        take[-1] = slice(-ix_delay, None)
+        put[-1] = slice(None, ix_delay)
+    X_del[ii][put] = X[take]
+
+# Now set the delayed axis to the 2nd dimension
+X_del = np.rollaxis(X_del, 0, 3)
+X_del = X_del.reshape([n_epochs, -1, n_times])
+n_features = X_del.shape[1]
+weights_sim = weights.ravel()
+
+# Simulate a neural response to the sound, given this STRF
+y = np.zeros((n_epochs, n_times))
+for ii, iep in enumerate(X_del):
+    # Simulate this epoch and add random noise
+    noise_amp = .002
+    y[ii] = np.dot(weights_sim, iep) + noise_amp * rng.randn(n_times)
+
+# Plot the first 2 trials of audio and the simulated electrode activity
+X_plt = scale(np.hstack(X[:2]).T).T
+y_plt = scale(np.hstack(y[:2]))
+time = np.arange(X_plt.shape[-1]) / sfreq
+fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(6, 6), sharex=True)
+ax1.pcolormesh(time, freqs, X_plt, vmin=0, vmax=4, cmap='Reds')
+ax1.set_title('Input auditory features')
+ax1.set(ylim=[freqs.min(), freqs.max()], ylabel='Frequency (Hz)')
+ax2.plot(time, y_plt)
+ax2.set(xlim=[time.min(), time.max()], title='Simulated response',
+        xlabel='Time (s)', ylabel='Activity (a.u.)')
+mne.viz.tight_layout()
+
+
+###############################################################################
+# Fit a model to recover this receptive field
+# -------------------------------------------
+#
+# Finally, we'll use the :class:`mne.decoding.ReceptiveField` class to recover
+# the linear receptive field of this signal. Note that properties of the
+# receptive field (e.g. smoothness) will depend on the autocorrelation in the
+# inputs and outputs.
+
+# Create training and testing data
+train, test = np.arange(n_epochs - 1), n_epochs - 1
+X_train, X_test, y_train, y_test = X[train], X[test], y[train], y[test]
+X_train, X_test, y_train, y_test = [np.rollaxis(ii, -1, 0) for ii in
+                                    (X_train, X_test, y_train, y_test)]
+# Model the simulated data as a function of the spectrogram input
+alphas = np.logspace(-3, 3, 7)
+scores = np.zeros_like(alphas)
+models = []
+for ii, alpha in enumerate(alphas):
+    rf = ReceptiveField(tmin, tmax, sfreq, freqs, estimator=alpha)
+    rf.fit(X_train, y_train)
+
+    # Now make predictions about the model output, given input stimuli.
+    scores[ii] = rf.score(X_test, y_test)
+    models.append(rf)
+
+times = rf.delays_ / float(rf.sfreq)
+
+# Choose the model that performed best on the held out data
+ix_best_alpha = np.argmax(scores)
+best_mod = models[ix_best_alpha]
+coefs = best_mod.coef_[0]
+best_pred = best_mod.predict(X_test)[:, 0]
+
+# Plot the original STRF, and the one that we recovered with modeling.
+fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(6, 3), sharey=True, sharex=True)
+ax1.pcolormesh(delays_sec, freqs, weights, **kwargs)
+ax2.pcolormesh(times, rf.feature_names, coefs, **kwargs)
+ax1.set_title('Original STRF')
+ax2.set_title('Best Reconstructed STRF')
+plt.setp([iax.get_xticklabels() for iax in [ax1, ax2]], rotation=45)
+plt.autoscale(tight=True)
+mne.viz.tight_layout()
+
+# Plot the actual response and the predicted response on a held out stimulus
+time_pred = np.arange(best_pred.shape[0]) / sfreq
+fig, ax = plt.subplots()
+ax.plot(time_pred, y_test, color='k', alpha=.2, lw=4)
+ax.plot(time_pred, best_pred, color='r', lw=1)
+ax.set(title='Original and predicted activity', xlabel='Time (s)')
+ax.legend(['Original', 'Predicted'])
+plt.autoscale(tight=True)
+mne.viz.tight_layout()
+
+
+###############################################################################
+# Visualize the effects of regularization
+# ---------------------------------------
+#
+# Above we fit a :class:`mne.decoding.ReceptiveField` model for one of many
+# values for the ridge regularization parameter. Here we will plot the model
+# score as well as the model coefficients for each value, in order to
+# visualize how coefficients change with different levels of regularization.
+# These issues as well as the STRF pipeline are described in detail
+# in [1]_, [2]_, and [4]_.
+
+# Plot model score for each ridge parameter
+fig = plt.figure(figsize=(10, 4))
+ax = plt.subplot2grid([2, len(alphas)], [1, 0], 1, len(alphas))
+ax.plot(np.arange(len(alphas)), scores, marker='o', color='r')
+ax.annotate('Best parameter', (ix_best_alpha, scores[ix_best_alpha]),
+            (ix_best_alpha, scores[ix_best_alpha] - .1),
+            arrowprops={'arrowstyle': '->'})
+plt.xticks(np.arange(len(alphas)), ["%.0e" % ii for ii in alphas])
+ax.set(xlabel="Ridge regularization value", ylabel="Score ($R^2$)",
+       xlim=[-.4, len(alphas) - .6])
+mne.viz.tight_layout()
+
+# Plot the STRF of each ridge parameter
+for ii, (rf, i_alpha) in enumerate(zip(models, alphas)):
+    ax = plt.subplot2grid([2, len(alphas)], [0, ii], 1, 1)
+    ax.pcolormesh(times, rf.feature_names, rf.coef_[0], **kwargs)
+    plt.xticks([], [])
+    plt.yticks([], [])
+    plt.autoscale(tight=True)
+fig.suptitle('Model coefficients / scores for many ridge parameters', y=1)
+mne.viz.tight_layout()
+
+###############################################################################
+# Using different regularization types
+# ------------------------------------
+# In addition to the standard ridge regularization, the
+# :class:`mne.decoding.TimeDelayingRidge` class also exposes
+# `Laplacian <https://en.wikipedia.org/wiki/Laplacian_matrix>`_ regularization
+# term as:
+#
+# .. math::
+#    \left[\begin{matrix}
+#         1 & -1 &   &   & & \\
+#        -1 &  2 & -1 &   & & \\
+#           & -1 & 2 & -1 & & \\
+#           & & \ddots & \ddots & \ddots & \\
+#           & & & -1 & 2 & -1 \\
+#           & & &    & -1 & 1\end{matrix}\right]
+#
+# This imposes a smoothness constraint of nearby time samples and/or features.
+# Quoting [5]_:
+#
+#    Tikhonov [identity] regularization (Equation 5) reduces overfitting by
+#    smoothing the TRF estimate in a way that is insensitive to
+#    the amplitude of the signal of interest. However, the Laplacian
+#    approach (Equation 6) reduces off-sample error whilst preserving
+#    signal amplitude (Lalor et al., 2006). As a result, this approach
+#    usually leads to an improved estimate of the system’s response (as
+#    indexed by MSE) compared to Tikhonov regularization.
+#
+
+scores_lap = np.zeros_like(alphas)
+models_lap = []
+for ii, alpha in enumerate(alphas):
+    estimator = TimeDelayingRidge(tmin, tmax, sfreq, reg_type='laplacian',
+                                  alpha=alpha)
+    rf = ReceptiveField(tmin, tmax, sfreq, freqs, estimator=estimator)
+    rf.fit(X_train, y_train)
+
+    # Now make predictions about the model output, given input stimuli.
+    scores_lap[ii] = rf.score(X_test, y_test)
+    models_lap.append(rf)
+
+ix_best_alpha_lap = np.argmax(scores_lap)
+
+###############################################################################
+# Compare model performance
+# -------------------------
+# Below we visualize the model performance of each regularization method
+# (ridge vs. Laplacian) for different levels of alpha. As you can see, the
+# Laplacian method performs better in general, because it imposes a smoothness
+# constraint along the time and feature dimensions of the coefficients.
+# This matches the "true" receptive field structure and results in a better
+# model fit.
+
+fig = plt.figure(figsize=(10, 6))
+ax = plt.subplot2grid([3, len(alphas)], [2, 0], 1, len(alphas))
+ax.plot(np.arange(len(alphas)), scores_lap, marker='o', color='r')
+ax.plot(np.arange(len(alphas)), scores, marker='o', color='0.5', ls=':')
+ax.annotate('Best Laplacian', (ix_best_alpha_lap,
+                               scores_lap[ix_best_alpha_lap]),
+            (ix_best_alpha_lap, scores_lap[ix_best_alpha_lap] - .1),
+            arrowprops={'arrowstyle': '->'})
+ax.annotate('Best Ridge', (ix_best_alpha, scores[ix_best_alpha]),
+            (ix_best_alpha, scores[ix_best_alpha] - .1),
+            arrowprops={'arrowstyle': '->'})
+plt.xticks(np.arange(len(alphas)), ["%.0e" % ii for ii in alphas])
+ax.set(xlabel="Laplacian regularization value", ylabel="Score ($R^2$)",
+       xlim=[-.4, len(alphas) - .6])
+mne.viz.tight_layout()
+
+# Plot the STRF of each ridge parameter
+xlim = times[[0, -1]]
+for ii, (rf_lap, rf, i_alpha) in enumerate(zip(models_lap, models, alphas)):
+    ax = plt.subplot2grid([3, len(alphas)], [0, ii], 1, 1)
+    ax.pcolormesh(times, rf_lap.feature_names, rf_lap.coef_[0], **kwargs)
+    ax.set(xticks=[], yticks=[], xlim=xlim)
+    if ii == 0:
+        ax.set(ylabel='Laplacian')
+    ax = plt.subplot2grid([3, len(alphas)], [1, ii], 1, 1)
+    ax.pcolormesh(times, rf.feature_names, rf.coef_[0], **kwargs)
+    ax.set(xticks=[], yticks=[], xlim=xlim)
+    if ii == 0:
+        ax.set(ylabel='Ridge')
+fig.suptitle('Model coefficients / scores for laplacian regularization', y=1)
+mne.viz.tight_layout()
+
+###############################################################################
+# Plot the original STRF, and the one that we recovered with modeling.
+rf = models[ix_best_alpha]
+rf_lap = models_lap[ix_best_alpha_lap]
+fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(9, 3),
+                                    sharey=True, sharex=True)
+ax1.pcolormesh(delays_sec, freqs, weights, **kwargs)
+ax2.pcolormesh(times, rf.feature_names, rf.coef_[0], **kwargs)
+ax3.pcolormesh(times, rf_lap.feature_names, rf_lap.coef_[0], **kwargs)
+ax1.set_title('Original STRF')
+ax2.set_title('Best Ridge STRF')
+ax3.set_title('Best Laplacian STRF')
+plt.setp([iax.get_xticklabels() for iax in [ax1, ax2, ax3]], rotation=45)
+plt.autoscale(tight=True)
+mne.viz.tight_layout()
+
+plt.show()
diff --git a/tutorials/plot_sensors_decoding.py b/tutorials/plot_sensors_decoding.py
index cb5a62d..8de2950 100644
--- a/tutorials/plot_sensors_decoding.py
+++ b/tutorials/plot_sensors_decoding.py
@@ -1,7 +1,7 @@
 """
-==========================
-Decoding sensor space data
-==========================
+=================================
+Decoding sensor space data (MVPA)
+=================================
 
 Decoding, a.k.a MVPA or supervised machine learning applied to MEG
 data in sensor space. Here the classifier is applied to every time
@@ -10,29 +10,36 @@ point.
 import numpy as np
 import matplotlib.pyplot as plt
 
-from sklearn.metrics import roc_auc_score
-from sklearn.cross_validation import StratifiedKFold
+from sklearn.pipeline import make_pipeline
+from sklearn.preprocessing import StandardScaler
+from sklearn.linear_model import LogisticRegression
 
 import mne
 from mne.datasets import sample
-from mne.decoding import TimeDecoding, GeneralizationAcrossTime
+from mne.decoding import (SlidingEstimator, GeneralizingEstimator,
+                          cross_val_multiscore, LinearModel, get_coef)
 
 data_path = sample.data_path()
 
 plt.close('all')
 
+# sphinx_gallery_thumbnail_number = 4
+
 ###############################################################################
 # Set parameters
-raw_fname = data_path + '/MEG/sample/sample_audvis_filt-0-40_raw.fif'
-event_fname = data_path + '/MEG/sample/sample_audvis_filt-0-40_raw-eve.fif'
-tmin, tmax = -0.2, 0.5
-event_id = dict(aud_l=1, vis_l=3)
+raw_fname = data_path + '/MEG/sample/sample_audvis_raw.fif'
+tmin, tmax = -0.200, 0.500
+event_id = dict(audio_left=1, visual_left=3)
 
 # Setup for reading the raw data
-raw = mne.io.read_raw_fif(raw_fname, preload=True, add_eeg_ref=False)
-raw.set_eeg_reference()  # set EEG average reference
-raw.filter(2, None, method='iir')  # replace baselining with high-pass
-events = mne.read_events(event_fname)
+raw = mne.io.read_raw_fif(raw_fname, preload=True)
+
+# The subsequent decoding analyses only capture evoked responses, so we can
+# low-pass the MEG data. Usually a value more like 40 Hz would be used,
+# but here low-pass at 20 so we can more heavily decimate, and allow
+# the examlpe to run faster.
+raw.filter(None, 20., fir_design='firwin')
+events = mne.find_events(raw, 'STI 014')
 
 # Set up pick list: EEG + MEG - bad channels (modify to your needs)
 raw.info['bads'] += ['MEG 2443', 'EEG 053']  # bads + 2 more
@@ -41,55 +48,91 @@ picks = mne.pick_types(raw.info, meg='grad', eeg=False, stim=True, eog=True,
 
 # Read epochs
 epochs = mne.Epochs(raw, events, event_id, tmin, tmax, proj=True,
-                    picks=picks, baseline=None, preload=True,
-                    reject=dict(grad=4000e-13, eog=150e-6), add_eeg_ref=False)
-
-epochs_list = [epochs[k] for k in event_id]
-mne.epochs.equalize_epoch_counts(epochs_list)
-data_picks = mne.pick_types(epochs.info, meg=True, exclude='bads')
+                    picks=picks, baseline=(None, 0.), preload=True,
+                    reject=dict(grad=4000e-13, eog=150e-6), decim=10)
+epochs.pick_types(meg=True, exclude='bads')
 
 ###############################################################################
 # Temporal decoding
 # -----------------
 #
-# We'll use the default classifer for a binary classification problem
-# which is a linear Support Vector Machine (SVM).
+# We'll use a Logistic Regression for a binary classification as machine
+# learning model.
 
-td = TimeDecoding(predict_mode='cross-validation', n_jobs=1)
+# We will train the classifier on all left visual vs auditory trials on MEG
 
-# Fit
-td.fit(epochs)
+X = epochs.get_data()  # MEG signals: n_epochs, n_channels, n_times
+y = epochs.events[:, 2]  # target: Audio left or right
 
-# Compute accuracy
-td.score(epochs)
+clf = make_pipeline(StandardScaler(), LogisticRegression())
 
-# Plot scores across time
-td.plot(title='Sensor space decoding')
+time_decod = SlidingEstimator(clf, n_jobs=1, scoring='roc_auc')
 
-###############################################################################
-# Generalization Across Time
-# --------------------------
-#
-# This runs the analysis used in [1]_ and further detailed in [2]_
-#
-# Here we'll use a stratified cross-validation scheme.
+scores = cross_val_multiscore(time_decod, X, y, cv=5, n_jobs=1)
+
+# Mean scores across cross-validation splits
+scores = np.mean(scores, axis=0)
 
-# make response vector
-y = np.zeros(len(epochs.events), dtype=int)
-y[epochs.events[:, 2] == 3] = 1
-cv = StratifiedKFold(y=y)  # do a stratified cross-validation
+# Plot
+fig, ax = plt.subplots()
+ax.plot(epochs.times, scores, label='score')
+ax.axhline(.5, color='k', linestyle='--', label='chance')
+ax.set_xlabel('Times')
+ax.set_ylabel('AUC')  # Area Under the Curve
+ax.legend()
+ax.axvline(.0, color='k', linestyle='-')
+ax.set_title('Sensor space decoding')
+plt.show()
 
-# define the GeneralizationAcrossTime object
-gat = GeneralizationAcrossTime(predict_mode='cross-validation', n_jobs=1,
-                               cv=cv, scorer=roc_auc_score)
+# You can retrieve the spatial filters and spatial patterns if you explicitly
+# use a LinearModel
+clf = make_pipeline(StandardScaler(), LinearModel(LogisticRegression()))
+time_decod = SlidingEstimator(clf, n_jobs=1, scoring='roc_auc')
+time_decod.fit(X, y)
 
-# fit and score
-gat.fit(epochs, y=y)
-gat.score(epochs)
+coef = get_coef(time_decod, 'patterns_', inverse_transform=True)
+evoked = mne.EvokedArray(coef, epochs.info, tmin=epochs.times[0])
+evoked.plot_joint(times=np.arange(0., .500, .100), title='patterns')
 
-# let's visualize now
-gat.plot()
-gat.plot_diagonal()
+###############################################################################
+# Temporal Generalization
+# -----------------------
+#
+# This runs the analysis used in [1]_ and further detailed in [2]_
+#
+# The idea is to fit the models on each time instant and see how it
+# generalizes to any other time point.
+
+# define the Temporal Generalization object
+time_gen = GeneralizingEstimator(clf, n_jobs=1, scoring='roc_auc')
+
+scores = cross_val_multiscore(time_gen, X, y, cv=5, n_jobs=1)
+
+# Mean scores across cross-validation splits
+scores = np.mean(scores, axis=0)
+
+# Plot the diagonal (it's exactly the same as the time-by-time decoding above)
+fig, ax = plt.subplots()
+ax.plot(epochs.times, np.diag(scores), label='score')
+ax.axhline(.5, color='k', linestyle='--', label='chance')
+ax.set_xlabel('Times')
+ax.set_ylabel('AUC')
+ax.legend()
+ax.axvline(.0, color='k', linestyle='-')
+ax.set_title('Decoding MEG sensors over time')
+plt.show()
+
+# Plot the full matrix
+fig, ax = plt.subplots(1, 1)
+im = ax.imshow(scores, interpolation='lanczos', origin='lower', cmap='RdBu_r',
+               extent=epochs.times[[0, -1, 0, -1]], vmin=0., vmax=1.)
+ax.set_xlabel('Testing Time (s)')
+ax.set_ylabel('Training Time (s)')
+ax.set_title('Temporal Generalization')
+ax.axvline(0, color='k')
+ax.axhline(0, color='k')
+plt.colorbar(im, ax=ax)
+plt.show()
 
 ###############################################################################
 # Exercise
diff --git a/tutorials/plot_sensors_time_frequency.py b/tutorials/plot_sensors_time_frequency.py
index a4f7904..d81a879 100644
--- a/tutorials/plot_sensors_time_frequency.py
+++ b/tutorials/plot_sensors_time_frequency.py
@@ -1,5 +1,4 @@
 """
-
 .. _tut_sensors_time_frequency:
 
 =============================================
@@ -27,7 +26,7 @@ data_path = somato.data_path()
 raw_fname = data_path + '/MEG/somato/sef_raw_sss.fif'
 
 # Setup for reading the raw data
-raw = mne.io.read_raw_fif(raw_fname, add_eeg_ref=False)
+raw = mne.io.read_raw_fif(raw_fname)
 events = mne.find_events(raw, stim_channel='STI 014')
 
 # picks MEG gradiometers
@@ -38,7 +37,7 @@ event_id, tmin, tmax = 1, -1., 3.
 baseline = (None, 0)
 epochs = mne.Epochs(raw, events, event_id, tmin, tmax, picks=picks,
                     baseline=baseline, reject=dict(grad=4000e-13, eog=350e-6),
-                    preload=True, add_eeg_ref=False)
+                    preload=True)
 
 epochs.resample(150., npad='auto')  # resample to reduce computation time
 
@@ -87,7 +86,8 @@ plt.show()
 # but you can also use :func:`mne.time_frequency.tfr_multitaper`
 # or :func:`mne.time_frequency.tfr_stockwell`.
 
-freqs = np.arange(6, 30, 3)  # define frequencies of interest
+# define frequencies of interest (log-spaced)
+freqs = np.logspace(*np.log10([6, 35]), num=8)
 n_cycles = freqs / 2.  # different number of cycle per frequency
 power, itc = tfr_morlet(epochs, freqs=freqs, n_cycles=n_cycles, use_fft=True,
                         return_itc=True, decim=3, n_jobs=1)
@@ -102,7 +102,7 @@ power, itc = tfr_morlet(epochs, freqs=freqs, n_cycles=n_cycles, use_fft=True,
 #     You can also select a portion in the time-frequency plane to
 #     obtain a topomap for a certain time-frequency region.
 power.plot_topo(baseline=(-0.5, 0), mode='logratio', title='Average power')
-power.plot([82], baseline=(-0.5, 0), mode='logratio')
+power.plot([82], baseline=(-0.5, 0), mode='logratio', title=power.ch_names[82])
 
 fig, axis = plt.subplots(1, 2, figsize=(7, 4))
 power.plot_topomap(ch_type='grad', tmin=0.5, tmax=1.5, fmin=8, fmax=12,
diff --git a/tutorials/plot_source_alignment.py b/tutorials/plot_source_alignment.py
new file mode 100644
index 0000000..2555f91
--- /dev/null
+++ b/tutorials/plot_source_alignment.py
@@ -0,0 +1,98 @@
+"""
+.. _tut_source_alignment:
+
+Source alignment
+================
+
+The aim of this tutorial is to show how to visually assess that the data
+are well aligned in space for computing the forward solution.
+"""
+import os.path as op
+
+import mne
+from mne.datasets import sample
+
+print(__doc__)
+
+
+###############################################################################
+# Set parameters
+# --------------
+data_path = sample.data_path()
+subjects_dir = op.join(data_path, 'subjects')
+raw_fname = op.join(data_path, 'MEG', 'sample', 'sample_audvis_raw.fif')
+tr_fname = op.join(data_path, 'MEG', 'sample', 'sample_audvis_raw-trans.fif')
+raw = mne.io.read_raw_fif(raw_fname)
+
+
+###############################################################################
+# :func:`mne.viz.plot_alignment` is a very useful function for inspecting
+# the surface alignment before source analysis. If the ``subjects_dir`` and
+# ``subject`` parameters are provided, the function automatically looks for the
+# Freesurfer surfaces from the subject's folder. Here we use trans=None, which
+# (incorrectly!) equates the MRI and head coordinate frames.
+mne.viz.plot_alignment(raw.info, trans=None, subject='sample',
+                       subjects_dir=subjects_dir, surfaces=['head', 'brain'])
+
+
+###############################################################################
+# It is quite clear that things are not well aligned for estimating the
+# sources. We need to provide the function with a transformation that aligns
+# the MRI with the MEG data. Here we use a precomputed matrix, but you can try
+# creating it yourself using :func:`mne.gui.coregistration`.
+#
+# Aligning the data using GUI
+# ---------------------------
+# Uncomment the following line to align the data yourself.
+#
+# * First you must load the digitization data from the raw file
+#   (``Head Shape Source``). The MRI data is already loaded if you provide the
+#   ``subject`` and ``subjects_dir``. Toggle ``Always Show Head Points`` to see
+#   the digitization points.
+# * To set the landmarks, toggle ``Edit`` radio button in ``MRI Fiducials``.
+# * Set the landmarks by clicking the radio button (LPA, Nasion, RPA) and then
+#   clicking the corresponding point in the image.
+# * After doing this for all the landmarks, toggle ``Lock`` radio button. You
+#   can omit outlier points, so that they don't interfere with the finetuning.
+#
+#   .. note:: You can save the fiducials to a file and pass
+#             ``mri_fiducials=True`` to plot them in
+#             :func:`mne.viz.plot_alignment`. The fiducials are saved to the
+#             subject's bem folder by default.
+# * Click ``Fit Head Shape``. This will align the digitization points to the
+#   head surface. Sometimes the fitting algorithm doesn't find the correct
+#   alignment immediately. You can try first fitting using LPA/RPA or fiducials
+#   and then align according to the digitization. You can also finetune
+#   manually with the controls on the right side of the panel.
+# * Click ``Save As...`` (lower right corner of the panel), set the filename
+#   and read it with :func:`mne.read_trans`.
+
+# mne.gui.coregistration(subject='sample', subjects_dir=subjects_dir)
+trans = mne.read_trans(tr_fname)
+src = mne.read_source_spaces(op.join(data_path, 'MEG', 'sample',
+                                     'sample_audvis-meg-oct-6-meg-inv.fif'))
+mne.viz.plot_alignment(raw.info, trans=trans, subject='sample', src=src,
+                       subjects_dir=subjects_dir, surfaces=['head', 'white'])
+
+
+###############################################################################
+# The previous is possible if you have the surfaces available from Freesurfer.
+# The function automatically searches for the correct surfaces from the
+# provided ``subjects_dir``. Otherwise it is possible to use the sphere
+# conductor model. It is passed through ``bem`` parameter.
+#
+# .. note:: ``bem`` also accepts bem solutions (:func:`mne.read_bem_solution`)
+#           or a list of bem surfaces (:func:`mne.read_bem_surfaces`).
+sphere = mne.make_sphere_model(info=raw.info, r0='auto', head_radius='auto')
+mne.viz.plot_alignment(raw.info, subject='sample', eeg='projected',
+                       meg='helmet', bem=sphere, dig=True,
+                       surfaces=['brain', 'inner_skull', 'outer_skull',
+                                 'outer_skin'])
+
+
+###############################################################################
+# For more information see step by step instructions
+# `for subjects with structural MRI
+# <http://www.slideshare.net/mne-python/mnepython-coregistration>`_ and `for
+# subjects for which no MRI is available
+# <http://www.slideshare.net/mne-python/mnepython-scale-mri>`_.
diff --git a/tutorials/plot_stats_cluster_1samp_test_time_frequency.py b/tutorials/plot_stats_cluster_1samp_test_time_frequency.py
index 8803557..cde93e9 100644
--- a/tutorials/plot_stats_cluster_1samp_test_time_frequency.py
+++ b/tutorials/plot_stats_cluster_1samp_test_time_frequency.py
@@ -68,9 +68,9 @@ evoked = epochs.average()
 # operations such as nonparametric statistics) if you don't need high
 # spectrotemporal resolution.
 decim = 5
-frequencies = np.arange(8, 40, 2)  # define frequencies of interest
+freqs = np.arange(8, 40, 2)  # define frequencies of interest
 sfreq = raw.info['sfreq']  # sampling in Hz
-tfr_epochs = tfr_morlet(epochs, frequencies, n_cycles=4., decim=decim,
+tfr_epochs = tfr_morlet(epochs, freqs, n_cycles=4., decim=decim,
                         average=False, return_itc=False, n_jobs=1)
 
 # Baseline power
@@ -110,10 +110,10 @@ vmax = np.max(np.abs(T_obs))
 vmin = -vmax
 plt.subplot(2, 1, 1)
 plt.imshow(T_obs, cmap=plt.cm.gray,
-           extent=[times[0], times[-1], frequencies[0], frequencies[-1]],
+           extent=[times[0], times[-1], freqs[0], freqs[-1]],
            aspect='auto', origin='lower', vmin=vmin, vmax=vmax)
 plt.imshow(T_obs_plot, cmap=plt.cm.RdBu_r,
-           extent=[times[0], times[-1], frequencies[0], frequencies[-1]],
+           extent=[times[0], times[-1], freqs[0], freqs[-1]],
            aspect='auto', origin='lower', vmin=vmin, vmax=vmax)
 plt.colorbar()
 plt.xlabel('Time (ms)')
diff --git a/tutorials/plot_stats_cluster_methods.py b/tutorials/plot_stats_cluster_methods.py
index e80ec0f..237057d 100644
--- a/tutorials/plot_stats_cluster_methods.py
+++ b/tutorials/plot_stats_cluster_methods.py
@@ -6,24 +6,15 @@
 Permutation t-test on toy data with spatial clustering
 ======================================================
 
-Following the illustrative example of Ridgway et al. 2012,
+Following the illustrative example of Ridgway et al. 2012 [1]_,
 this demonstrates some basic ideas behind both the "hat"
 variance adjustment method, as well as threshold-free
-cluster enhancement (TFCE) methods in mne-python.
+cluster enhancement (TFCE) [2]_ methods in mne-python.
 
 This toy dataset consists of a 40 x 40 square with a "signal"
 present in the center (at pixel [20, 20]) with white noise
 added and a 5-pixel-SD normal smoothing kernel applied.
 
-For more information, see:
-Ridgway et al. 2012, "The problem of low variance voxels in
-statistical parametric mapping; a new hat avoids a 'haircut'",
-NeuroImage. 2012 Feb 1;59(3):2131-41.
-
-Smith and Nichols 2009, "Threshold-free cluster enhancement:
-addressing problems of smoothing, threshold dependence, and
-localisation in cluster inference", NeuroImage 44 (2009) 83-98.
-
 In the top row plot the T statistic over space, peaking toward the
 center. Note that it has peaky edges. Second, with the "hat" variance
 correction/regularization, the peak becomes correctly centered. Third,
@@ -49,8 +40,8 @@ methods tightens the area declared significant (again FWER corrected),
 and allows for evaluation of each point independently instead of as
 a single, broad cluster.
 
-Note that this example does quite a bit of processing, so even on a
-fast machine it can take a few minutes to complete.
+.. note:: This example does quite a bit of processing, so even on a
+          fast machine it can take a few minutes to complete.
 """
 # Authors: Eric Larson <larson.eric.d at gmail.com>
 # License: BSD (3-clause)
@@ -145,7 +136,7 @@ T_obs_hat, clusters, p_values, H0 = \
     spatio_temporal_cluster_1samp_test(X, n_jobs=1, threshold=threshold,
                                        connectivity=connectivity,
                                        tail=1, n_permutations=n_permutations,
-                                       stat_fun=stat_fun)
+                                       stat_fun=stat_fun, buffer_size=None)
 
 #    Let's put the cluster data in a readable format
 ps_hat = np.zeros(width * width)
@@ -167,7 +158,7 @@ T_obs_tfce_hat, clusters, p_values, H0 = \
     spatio_temporal_cluster_1samp_test(X, n_jobs=1, threshold=threshold_tfce,
                                        connectivity=connectivity,
                                        tail=1, n_permutations=n_permutations,
-                                       stat_fun=stat_fun)
+                                       stat_fun=stat_fun, buffer_size=None)
 T_obs_tfce_hat = T_obs_tfce_hat.reshape((width, width))
 ps_tfce_hat = -np.log10(p_values.reshape((width, width)))
 
@@ -210,3 +201,14 @@ for ax in axs:
     cbar.set_ticklabels(['%0.1f' % p for p in p_lims])
 
 plt.show()
+
+###############################################################################
+# References
+# ----------
+# .. [1] Ridgway et al. 2012, "The problem of low variance voxels in
+#        statistical parametric mapping; a new hat avoids a 'haircut'",
+#        NeuroImage. 2012 Feb 1;59(3):2131-41.
+#
+# .. [2] Smith and Nichols 2009, "Threshold-free cluster enhancement:
+#        addressing problems of smoothing, threshold dependence, and
+#        localisation in cluster inference", NeuroImage 44 (2009) 83-98.
diff --git a/tutorials/plot_stats_cluster_spatio_temporal.py b/tutorials/plot_stats_cluster_spatio_temporal.py
index 059b34d..b6b93a2 100644
--- a/tutorials/plot_stats_cluster_spatio_temporal.py
+++ b/tutorials/plot_stats_cluster_spatio_temporal.py
@@ -180,7 +180,8 @@ stc_all_cluster_vis = summarize_clusters_stc(clu, tstep=tstep,
 #    shows all the clusters, weighted by duration
 subjects_dir = op.join(data_path, 'subjects')
 # blue blobs are for condition A < condition B, red for A > B
-brain = stc_all_cluster_vis.plot(hemi='both', views='lateral',
-                                 subjects_dir=subjects_dir,
-                                 time_label='Duration significant (ms)')
-brain.save_image('clusters.png')
+brain = stc_all_cluster_vis.plot(
+    hemi='both', views='lateral', subjects_dir=subjects_dir,
+    time_label='Duration significant (ms)', size=(800, 800),
+    smoothing_steps=5)
+# brain.save_image('clusters.png')
diff --git a/tutorials/plot_stats_cluster_spatio_temporal_repeated_measures_anova.py b/tutorials/plot_stats_cluster_spatio_temporal_repeated_measures_anova.py
index ab3f429..d25c511 100644
--- a/tutorials/plot_stats_cluster_spatio_temporal_repeated_measures_anova.py
+++ b/tutorials/plot_stats_cluster_spatio_temporal_repeated_measures_anova.py
@@ -66,7 +66,7 @@ epochs = mne.Epochs(raw, events, event_id, tmin, tmax, picks=picks,
 
 #    Equalize trial counts to eliminate bias (which would otherwise be
 #    introduced by the abs() performed below)
-epochs.equalize_event_counts(event_id, copy=False)
+epochs.equalize_event_counts(event_id)
 
 ###############################################################################
 # Transform to source space
diff --git a/tutorials/plot_stats_cluster_time_frequency.py b/tutorials/plot_stats_cluster_time_frequency.py
index e573cd3..53bd7fb 100644
--- a/tutorials/plot_stats_cluster_time_frequency.py
+++ b/tutorials/plot_stats_cluster_time_frequency.py
@@ -75,14 +75,14 @@ epochs_condition_2.pick_channels([ch_name])
 # operations such as nonparametric statistics) if you don't need high
 # spectrotemporal resolution.
 decim = 2
-frequencies = np.arange(7, 30, 3)  # define frequencies of interest
+freqs = np.arange(7, 30, 3)  # define frequencies of interest
 n_cycles = 1.5
 
-tfr_epochs_1 = tfr_morlet(epochs_condition_1, frequencies,
+tfr_epochs_1 = tfr_morlet(epochs_condition_1, freqs,
                           n_cycles=n_cycles, decim=decim,
                           return_itc=False, average=False)
 
-tfr_epochs_2 = tfr_morlet(epochs_condition_2, frequencies,
+tfr_epochs_2 = tfr_morlet(epochs_condition_2, freqs,
                           n_cycles=n_cycles, decim=decim,
                           return_itc=False, average=False)
 
@@ -119,10 +119,10 @@ for c, p_val in zip(clusters, cluster_p_values):
         T_obs_plot[c] = T_obs[c]
 
 plt.imshow(T_obs,
-           extent=[times[0], times[-1], frequencies[0], frequencies[-1]],
+           extent=[times[0], times[-1], freqs[0], freqs[-1]],
            aspect='auto', origin='lower', cmap='gray')
 plt.imshow(T_obs_plot,
-           extent=[times[0], times[-1], frequencies[0], frequencies[-1]],
+           extent=[times[0], times[-1], freqs[0], freqs[-1]],
            aspect='auto', origin='lower', cmap='RdBu_r')
 
 plt.xlabel('Time (ms)')
diff --git a/tutorials/plot_stats_cluster_time_frequency_repeated_measures_anova.py b/tutorials/plot_stats_cluster_time_frequency_repeated_measures_anova.py
index dfff054..de8c3d3 100644
--- a/tutorials/plot_stats_cluster_time_frequency_repeated_measures_anova.py
+++ b/tutorials/plot_stats_cluster_time_frequency_repeated_measures_anova.py
@@ -69,13 +69,13 @@ epochs.pick_channels([ch_name])  # restrict example to one channel
 # We have to make sure all conditions have the same counts, as the ANOVA
 # expects a fully balanced data matrix and does not forgive imbalances that
 # generously (risk of type-I error).
-epochs.equalize_event_counts(event_id, copy=False)
+epochs.equalize_event_counts(event_id)
 
 # Factor to down-sample the temporal dimension of the TFR computed by
 # tfr_morlet.
 decim = 2
-frequencies = np.arange(7, 30, 3)  # define frequencies of interest
-n_cycles = frequencies / frequencies[0]
+freqs = np.arange(7, 30, 3)  # define frequencies of interest
+n_cycles = freqs / freqs[0]
 zero_mean = False  # don't correct morlet wavelet to be of mean zero
 # To have a true wavelet zero_mean should be True but here for illustration
 # purposes it helps to spot the evoked response.
@@ -85,7 +85,7 @@ zero_mean = False  # don't correct morlet wavelet to be of mean zero
 # ---------------------------------------------
 epochs_power = list()
 for condition in [epochs[k] for k in event_id]:
-    this_tfr = tfr_morlet(condition, frequencies, n_cycles=n_cycles,
+    this_tfr = tfr_morlet(condition, freqs, n_cycles=n_cycles,
                           decim=decim, average=False, zero_mean=zero_mean,
                           return_itc=False)
     this_tfr.apply_baseline(mode='ratio', baseline=(None, 0))
@@ -101,14 +101,14 @@ for condition in [epochs[k] for k in event_id]:
 # factor levels for each factor.
 
 n_conditions = len(epochs.event_id)
-n_replications = epochs.events.shape[0] / n_conditions
+n_replications = epochs.events.shape[0] // n_conditions
 
 factor_levels = [2, 2]  # number of levels in each factor
 effects = 'A*B'  # this is the default signature for computing all effects
 # Other possible options are 'A' or 'B' for the corresponding main effects
 # or 'A:B' for the interaction effect only (this notation is borrowed from the
 # R formula language)
-n_frequencies = len(frequencies)
+n_freqs = len(freqs)
 times = 1e3 * epochs.times[::decim]
 n_times = len(times)
 
@@ -117,7 +117,7 @@ n_times = len(times)
 # are the first dimension and the conditions are the second dimension.
 data = np.swapaxes(np.asarray(epochs_power), 1, 0)
 # reshape last two dimensions in one mass-univariate observation-vector
-data = data.reshape(n_replications, n_conditions, n_frequencies * n_times)
+data = data.reshape(n_replications, n_conditions, n_freqs * n_times)
 
 # so we have replications * conditions * observations:
 print(data.shape)
@@ -153,12 +153,12 @@ for effect, sig, effect_label in zip(fvals, pvals, effect_labels):
     plt.figure()
     # show naive F-values in gray
     plt.imshow(effect.reshape(8, 211), cmap=plt.cm.gray, extent=[times[0],
-               times[-1], frequencies[0], frequencies[-1]], aspect='auto',
+               times[-1], freqs[0], freqs[-1]], aspect='auto',
                origin='lower')
     # create mask for significant Time-frequency locations
     effect = np.ma.masked_array(effect, [sig > .05])
     plt.imshow(effect.reshape(8, 211), cmap='RdBu_r', extent=[times[0],
-               times[-1], frequencies[0], frequencies[-1]], aspect='auto',
+               times[-1], freqs[0], freqs[-1]], aspect='auto',
                origin='lower')
     plt.colorbar()
     plt.xlabel('Time (ms)')
@@ -188,6 +188,7 @@ def stat_fun(*args):
     return f_mway_rm(np.swapaxes(args, 1, 0), factor_levels=factor_levels,
                      effects=effects, return_pvals=False)[0]
 
+
 # The ANOVA returns a tuple f-values and p-values, we will pick the former.
 pthresh = 0.00001  # set threshold rather high to save some time
 f_thresh = f_threshold_mway_rm(n_replications, factor_levels, effects,
@@ -208,7 +209,7 @@ T_obs_plot = np.ma.masked_array(T_obs,
 plt.figure()
 for f_image, cmap in zip([T_obs, T_obs_plot], [plt.cm.gray, 'RdBu_r']):
     plt.imshow(f_image, cmap=cmap, extent=[times[0], times[-1],
-               frequencies[0], frequencies[-1]], aspect='auto',
+               freqs[0], freqs[-1]], aspect='auto',
                origin='lower')
 plt.xlabel('Time (ms)')
 plt.ylabel('Frequency (Hz)')
@@ -225,7 +226,7 @@ T_obs_plot2 = np.ma.masked_array(T_obs, np.invert(mask))
 plt.figure()
 for f_image, cmap in zip([T_obs, T_obs_plot2], [plt.cm.gray, 'RdBu_r']):
     plt.imshow(f_image, cmap=cmap, extent=[times[0], times[-1],
-               frequencies[0], frequencies[-1]], aspect='auto',
+               freqs[0], freqs[-1]], aspect='auto',
                origin='lower')
 
 plt.xlabel('Time (ms)')
diff --git a/tutorials/plot_stats_spatio_temporal_cluster_sensors.py b/tutorials/plot_stats_spatio_temporal_cluster_sensors.py
index 8faea54..4ef74ae 100644
--- a/tutorials/plot_stats_spatio_temporal_cluster_sensors.py
+++ b/tutorials/plot_stats_spatio_temporal_cluster_sensors.py
@@ -24,7 +24,7 @@ from mne.viz import plot_topomap
 import mne
 from mne.stats import spatio_temporal_cluster_test
 from mne.datasets import sample
-from mne.channels import read_ch_connectivity
+from mne.channels import find_ch_connectivity
 
 print(__doc__)
 
@@ -40,8 +40,7 @@ tmax = 0.5
 
 # Setup for reading the raw data
 raw = mne.io.read_raw_fif(raw_fname, preload=True)
-raw.filter(1, 30, l_trans_bandwidth='auto', h_trans_bandwidth='auto',
-           filter_length='auto', phase='zero')
+raw.filter(1, 30, fir_design='firwin')
 events = mne.read_events(event_fname)
 
 ###############################################################################
@@ -55,7 +54,7 @@ epochs = mne.Epochs(raw, events, event_id, tmin, tmax, picks=picks,
                     baseline=None, reject=reject, preload=True)
 
 epochs.drop_channels(['EOG 061'])
-epochs.equalize_event_counts(event_id, copy=False)
+epochs.equalize_event_counts(event_id)
 
 condition_names = 'Aud_L', 'Aud_R', 'Vis_L', 'Vis_R'
 X = [epochs[k].get_data() for k in condition_names]  # as 3D matrix
@@ -63,9 +62,9 @@ X = [np.transpose(x, (0, 2, 1)) for x in X]  # transpose for clustering
 
 
 ###############################################################################
-# Load FieldTrip neighbor definition to setup sensor connectivity
-# ---------------------------------------------------------------
-connectivity, ch_names = read_ch_connectivity('neuromag306mag')
+# Find the FieldTrip neighbor definition to setup sensor connectivity
+# -------------------------------------------------------------------
+connectivity, ch_names = find_ch_connectivity(epochs.info, ch_type='mag')
 
 print(type(connectivity))  # it's a sparse matrix!
 
diff --git a/tutorials/plot_visualize_epochs.py b/tutorials/plot_visualize_epochs.py
index 692c62a..79bffa4 100644
--- a/tutorials/plot_visualize_epochs.py
+++ b/tutorials/plot_visualize_epochs.py
@@ -10,12 +10,14 @@ import os.path as op
 import mne
 
 data_path = op.join(mne.datasets.sample.data_path(), 'MEG', 'sample')
-raw = mne.io.read_raw_fif(op.join(data_path, 'sample_audvis_raw.fif'),
-                          add_eeg_ref=False)
-raw.set_eeg_reference()  # set EEG average reference
+raw = mne.io.read_raw_fif(
+    op.join(data_path, 'sample_audvis_raw.fif'), preload=True)
+raw.load_data().filter(None, 9, fir_design='firwin')
+raw.set_eeg_reference('average', projection=True)  # set EEG average reference
+event_id = {'auditory/left': 1, 'auditory/right': 2, 'visual/left': 3,
+            'visual/right': 4, 'smiley': 5, 'button': 32}
 events = mne.read_events(op.join(data_path, 'sample_audvis_raw-eve.fif'))
-picks = mne.pick_types(raw.info, meg='grad')
-epochs = mne.Epochs(raw, events, [1, 2], picks=picks, add_eeg_ref=False)
+epochs = mne.Epochs(raw, events, event_id=event_id, tmin=-0.2, tmax=.5)
 
 ###############################################################################
 # This tutorial focuses on visualization of epoched data. All of the functions
@@ -23,6 +25,10 @@ epochs = mne.Epochs(raw, events, [1, 2], picks=picks, add_eeg_ref=False)
 # intelligence to work with epoched data. All the methods return a handle to
 # matplotlib figure instance.
 #
+# Events used for constructing the epochs here are the triggers for subject
+# being presented a smiley face at the center of the visual field. More of the
+# paradigm at :ref:`BABDHIFJ`.
+#
 # All plotting functions start with ``plot``. Let's start with the most
 # obvious. :func:`mne.Epochs.plot` offers an interactive browser that allows
 # rejection by hand when called in combination with a keyword ``block=True``.
@@ -30,31 +36,58 @@ epochs = mne.Epochs(raw, events, [1, 2], picks=picks, add_eeg_ref=False)
 epochs.plot(block=True)
 
 ###############################################################################
-# The numbers at the top refer to the event id of the epoch. We only have
-# events with id numbers of 1 and 2 since we included only those when
-# constructing the epochs.
+# The numbers at the top refer to the event id of the epoch. The number at the
+# bottom is the running numbering for the epochs.
 #
 # Since we did no artifact correction or rejection, there are epochs
-# contaminated with blinks and saccades. For instance, epoch number 9 (see
-# numbering at the bottom) seems to be contaminated by a blink (scroll to the
-# bottom to view the EOG channel). This epoch can be marked for rejection by
-# clicking on top of the browser window. The epoch should turn red when you
-# click it. This means that it will be dropped as the browser window is closed.
-# You should check out `help` at the lower left corner of the window for more
-# information about the interactive features.
+# contaminated with blinks and saccades. For instance, epoch number 1 seems to
+# be contaminated by a blink (scroll to the bottom to view the EOG channel).
+# This epoch can be marked for rejection by clicking on top of the browser
+# window. The epoch should turn red when you click it. This means that it will
+# be dropped as the browser window is closed.
+#
+# It is possible to plot event markers on epoched data by passing ``events``
+# keyword to the epochs plotter. The events are plotted as vertical lines and
+# they follow the same coloring scheme as :func:`mne.viz.plot_events`. The
+# events plotter gives you all the events with a rough idea of the timing.
+# Since the colors are the same, the event plotter can also function as a
+# legend for the epochs plotter events. It is also possible to pass your own
+# colors via ``event_colors`` keyword. Here we can plot the reaction times
+# between seeing the smiley face and the button press (event 32).
 #
+# When events are passed, the epoch numbering at the bottom is switched off by
+# default to avoid overlaps. You can turn it back on via settings dialog by
+# pressing `o` key. You should check out `help` at the lower left corner of the
+# window for more information about the interactive features.
+events = mne.pick_events(events, include=[5, 32])
+mne.viz.plot_events(events)
+epochs['smiley'].plot(events=events)
+
+###############################################################################
 # To plot individual channels as an image, where you see all the epochs at one
 # glance, you can use function :func:`mne.Epochs.plot_image`. It shows the
-# amplitude of the signal over all the epochs plus an average of the
-# activation. We explicitly set interactive colorbar on (it is also on by
-# default for plotting functions with a colorbar except the topo plots). In
+# amplitude of the signal over all the epochs plus an average (evoked response)
+# of the activation. We explicitly set interactive colorbar on (it is also on
+# by default for plotting functions with a colorbar except the topo plots). In
 # interactive mode you can scale and change the colormap with mouse scroll and
 # up/down arrow keys. You can also drag the colorbar with left/right mouse
 # button. Hitting space bar resets the scale.
-epochs.plot_image(97, cmap='interactive')
+epochs.plot_image(278, cmap='interactive', sigma=1., vmin=-250, vmax=250)
 
+###############################################################################
+# We can also give an overview of all channels by calculating  the global
+# field power (or other other aggregation methods). However, combining
+# multiple channel types (e.g., MEG and EEG) in this way is not sensible.
+# Instead, we can use the ``group_by`` parameter. Setting ``group_by`` to
+# 'type' combines channels by type.
+# ``group_by`` can also be used to group channels into arbitrary groups, e.g.
+# regions of interests, by providing a dictionary containing
+# group name -> channel indices mappings.
+epochs.plot_image(combine='gfp', group_by='type', sigma=2., cmap="YlGnBu_r")
+
+###############################################################################
 # You also have functions for plotting channelwise information arranged into a
 # shape of the channel array. The image plotting uses automatic scaling by
 # default, but noisy channels and different channel types can cause the scaling
 # to be a bit off. Here we define the limits by hand.
-epochs.plot_topo_image(vmin=-200, vmax=200, title='ERF images')
+epochs.plot_topo_image(vmin=-250, vmax=250, title='ERF images', sigma=2.)
diff --git a/tutorials/plot_visualize_evoked.py b/tutorials/plot_visualize_evoked.py
index c66db59..aa7364d 100644
--- a/tutorials/plot_visualize_evoked.py
+++ b/tutorials/plot_visualize_evoked.py
@@ -4,6 +4,8 @@
 =====================
 Visualize Evoked data
 =====================
+
+In this tutorial we focus on plotting functions of :class:`mne.Evoked`.
 """
 import os.path as op
 import numpy as np
@@ -11,9 +13,10 @@ import matplotlib.pyplot as plt
 
 import mne
 
+# sphinx_gallery_thumbnail_number = 9
+
 ###############################################################################
-# In this tutorial we focus on plotting functions of :class:`mne.Evoked`.
-# Here we read the evoked object from a file. Check out
+# First we read the evoked object from a file. Check out
 # :ref:`tut_epoching_and_averaging` to get to this stage from raw data.
 data_path = mne.datasets.sample.data_path()
 fname = op.join(data_path, 'MEG', 'sample', 'sample_audvis-ave.fif')
@@ -33,15 +36,15 @@ evoked_r_vis = evoked[3]
 ###############################################################################
 # Let's start with a simple one. We plot event related potentials / fields
 # (ERP/ERF). The bad channels are not plotted by default. Here we explicitly
-# set the exclude parameter to show the bad channels in red. All plotting
+# set the ``exclude`` parameter to show the bad channels in red. All plotting
 # functions of MNE-python return a handle to the figure instance. When we have
 # the handle, we can customise the plots to our liking.
 fig = evoked_l_aud.plot(exclude=())
 
 ###############################################################################
-# All plotting functions of MNE-python returns a handle to the figure instance.
-# When we have the handle, we can customise the plots to our liking. We can get
-# rid of the empty space with a simple function call.
+# All plotting functions of MNE-python return a handle to the figure instance.
+# When we have the handle, we can customise the plots to our liking. For
+# example, we can get rid of the empty space with a simple function call.
 fig.tight_layout()
 
 ###############################################################################
@@ -79,14 +82,18 @@ evoked_r_aud.plot_topomap(times='peaks', ch_type='mag')
 # or simply write ``evoked_r_aud.plot_topomap?`` in your python console to
 # see the different parameters you can pass to this function. Most of the
 # plotting functions also accept ``axes`` parameter. With that, you can
-# customise your plots even further. First we shall create a set of matplotlib
+# customise your plots even further. First we create a set of matplotlib
 # axes in a single figure and plot all of our evoked categories next to each
 # other.
-fig, ax = plt.subplots(1, 5)
-evoked_l_aud.plot_topomap(times=0.1, axes=ax[0], show=False)
-evoked_r_aud.plot_topomap(times=0.1, axes=ax[1], show=False)
-evoked_l_vis.plot_topomap(times=0.1, axes=ax[2], show=False)
-evoked_r_vis.plot_topomap(times=0.1, axes=ax[3], show=True)
+fig, ax = plt.subplots(1, 5, figsize=(8, 2))
+kwargs = dict(times=0.1, show=False, vmin=-300, vmax=300)
+evoked_l_aud.plot_topomap(axes=ax[0], colorbar=True, **kwargs)
+evoked_r_aud.plot_topomap(axes=ax[1], colorbar=False, **kwargs)
+evoked_l_vis.plot_topomap(axes=ax[2], colorbar=False, **kwargs)
+evoked_r_vis.plot_topomap(axes=ax[3], colorbar=False, **kwargs)
+for ax, title in zip(ax[:4], ['Aud/L', 'Aud/R', 'Vis/L', 'Vis/R']):
+    ax.set_title(title)
+plt.show()
 
 ###############################################################################
 # Notice that we created five axes, but had only four categories. The fifth
@@ -98,33 +105,36 @@ evoked_r_vis.plot_topomap(times=0.1, axes=ax[3], show=True)
 # using for your python session. See http://matplotlib.org/users/shell.html for
 # more information.
 #
-# We can combine the two kinds of plots in one figure using the ``plot_joint``
-# method of Evoked objects. Called as-is (``evoked.plot_joint()``), this
-# function should give a stylish and informative display of spatio-temporal
-# dynamics. Also note the ``topomap_args`` and ``ts_args`` parameters of
-# :func:`mne.Evoked.plot_joint`. You can pass key-value pairs as a python
-# dictionary that gets passed as parameters to the topomaps
-# (:func:`mne.Evoked.plot_topomap`) and time series (:func:`mne.Evoked.plot`)
-# of the joint plot.
-# For specific styling, use these ``topomap_args`` and ``ts_args``
-# arguments. Here, topomaps at specific time points (70 and 105 msec) are
-# shown, sensors are not plotted, and the Global Field Power is shown:
+# We can combine the two kinds of plots in one figure using the
+# :func:`mne.Evoked.plot_joint` method of Evoked objects. Called as-is
+# (``evoked.plot_joint()``), this function should give an informative display
+# of spatio-temporal dynamics.
+# You can directly style the time series part and the topomap part of the plot
+# using the ``topomap_args`` and ``ts_args`` parameters. You can pass key-value
+# pairs as a python dictionary. These are then passed as parameters to the
+# topomaps (:func:`mne.Evoked.plot_topomap`) and time series
+# (:func:`mne.Evoked.plot`) of the joint plot.
+# For an example of specific styling using these ``topomap_args`` and
+# ``ts_args`` arguments, here, topomaps at specific time points
+# (90 and 200 ms) are shown, sensors are not plotted (via an argument
+# forwarded to `plot_topomap`), and the Global Field Power is shown:
 ts_args = dict(gfp=True)
 topomap_args = dict(sensors=False)
-evoked_r_aud.plot_joint(title='right auditory', times=[.07, .105],
+evoked_r_aud.plot_joint(title='right auditory', times=[.09, .20],
                         ts_args=ts_args, topomap_args=topomap_args)
 
 ###############################################################################
-# Sometimes, you may want to compare two conditions at a selection of sensors,
-# or e.g. for the Global Field Power. For this, you can use the function
-# :func:`mne.viz.plot_compare_evokeds`. The easiest way is to create a  Python
-# dictionary, where the keys are condition names and the values are
+# Sometimes, you may want to compare two or more conditions at a selection of
+# sensors, or e.g. for the Global Field Power. For this, you can use the
+# function :func:`mne.viz.plot_compare_evokeds`. The easiest way is to create
+# a  Python dictionary, where the keys are condition names and the values are
 # :class:`mne.Evoked` objects. If you provide lists of :class:`mne.Evoked`
 # objects, such as those for multiple subjects, the grand average is plotted,
 # along with a confidence interval band - this can be used to contrast
 # conditions for a whole experiment.
 # First, we load in the evoked objects into a dictionary, setting the keys to
-# '/'-separated tags. Then, we plot with :func:`mne.viz.plot_compare_evokeds`.
+# '/'-separated tags (as we can do with event_ids for epochs). Then, we plot
+# with :func:`mne.viz.plot_compare_evokeds`.
 # The plot is styled with dictionary arguments, again using "/"-separated tags.
 # We plot a MEG channel with a strong auditory response.
 conditions = ["Left Auditory", "Right Auditory", "Left visual", "Right visual"]
@@ -138,8 +148,8 @@ colors = dict(Left="Crimson", Right="CornFlowerBlue")
 linestyles = dict(Auditory='-', visual='--')
 pick = evoked_dict["Left/Auditory"].ch_names.index('MEG 1811')
 
-mne.viz.plot_compare_evokeds(evoked_dict, picks=pick,
-                             colors=colors, linestyles=linestyles)
+mne.viz.plot_compare_evokeds(evoked_dict, picks=pick, colors=colors,
+                             linestyles=linestyles)
 
 ###############################################################################
 # We can also plot the activations as images. The time runs along the x-axis
@@ -153,16 +163,15 @@ evoked_r_aud.plot_image(picks=picks)
 # Finally we plot the sensor data as a topographical view. In the simple case
 # we plot only left auditory responses, and then we plot them all in the same
 # figure for comparison. Click on the individual plots to open them bigger.
-title = 'MNE sample data (condition : %s)'
-evoked_l_aud.plot_topo(title=title % evoked_l_aud.comment)
-colors = 'yellow', 'green', 'red', 'blue'
-mne.viz.plot_evoked_topo(evoked, color=colors,
-                         title=title % 'Left/Right Auditory/Visual')
+title = 'MNE sample data\n(condition : %s)'
+evoked_l_aud.plot_topo(title=title % evoked_l_aud.comment,
+                       background_color='k', color=['white'])
+mne.viz.plot_evoked_topo(evoked, title=title % 'Left/Right Auditory/Visual',
+                         background_color='w')
 
 ###############################################################################
 # Visualizing field lines in 3D
 # -----------------------------
-#
 # We now compute the field maps to project MEG and EEG data to MEG helmet
 # and scalp surface.
 #
diff --git a/tutorials/plot_visualize_raw.py b/tutorials/plot_visualize_raw.py
index fa01f5a..5b2d9b8 100644
--- a/tutorials/plot_visualize_raw.py
+++ b/tutorials/plot_visualize_raw.py
@@ -6,13 +6,14 @@ Visualize Raw data
 
 """
 import os.path as op
+import numpy as np
 
 import mne
 
 data_path = op.join(mne.datasets.sample.data_path(), 'MEG', 'sample')
 raw = mne.io.read_raw_fif(op.join(data_path, 'sample_audvis_raw.fif'),
-                          add_eeg_ref=False)
-raw.set_eeg_reference()  # set EEG average reference
+                          preload=True)
+raw.set_eeg_reference('average', projection=True)  # set EEG average reference
 events = mne.read_events(op.join(data_path, 'sample_audvis_raw-eve.fif'))
 
 ###############################################################################
@@ -25,7 +26,7 @@ events = mne.read_events(op.join(data_path, 'sample_audvis_raw-eve.fif'))
 #
 # To visually inspect your raw data, you can use the python equivalent of
 # ``mne_browse_raw``.
-raw.plot(block=True)
+raw.plot(block=True, lowpass=40)
 
 ###############################################################################
 # The channels are color coded by channel type. Generally MEG channels are
@@ -38,23 +39,44 @@ raw.plot(block=True)
 # with parameter ``scalings``. If you don't know the scaling factor for
 # channels, you can automatically set them by passing scalings='auto'. With
 # ``pageup/pagedown`` and ``home/end`` keys you can adjust the amount of data
-# viewed at once. To see all the interactive features, hit ``?`` or click
-# ``help`` in the lower left corner of the browser window.
+# viewed at once.
 #
-# The channels are sorted by channel type by default. You can use the ``order``
-# parameter of :func:`raw.plot <mne.io.Raw.plot>` to group the channels in a
-# different way. ``order='selection'`` uses the same channel groups as MNE-C's
-# mne_browse_raw (see :ref:`CACCJEJD`). The selections are defined in
-# ``mne-python/mne/data/mne_analyze.sel`` and by modifying the channels there,
-# you can define your own selection groups. Notice that this also affects the
-# selections returned by :func:`mne.read_selection`. By default the selections
-# only work for Neuromag data, but ``order='position'`` tries to mimic this
-# behavior for any data with sensor positions available. The channels are
-# grouped by sensor positions to 8 evenly sized regions. Notice that for this
-# to work effectively, all the data channels in the channel array must be
-# present. The ``order`` parameter can also be passed as an array of ints
-# (picks) to plot the channels in the given order.
-raw.plot(order='selection')
+# Drawing annotations
+# -------------------
+#
+# You can enter annotation mode by pressing ``a`` key. In annotation mode you
+# can mark segments of data (and modify existing annotations) with the left
+# mouse button. You can use the description of any existing annotation or
+# create a new description by typing when the annotation dialog is active.
+# Notice that the description starting with the keyword ``'bad'`` means that
+# the segment will be discarded when epoching the data. Existing annotations
+# can be deleted with the right mouse button.  Annotation mode is exited by
+# pressing ``a`` again or closing the annotation window. See also
+# :class:`mne.Annotations` and :ref:`marking_bad_segments`. To see all the
+# interactive features, hit ``?`` key or click ``help`` in the lower left
+# corner of the browser window.
+#
+# .. warning:: Annotations are modified in-place immediately at run-time.
+#              Deleted annotations cannot be retrieved after deletion.
+#
+# The channels are sorted by channel type by default. You can use the
+# ``group_by`` parameter of :func:`raw.plot <mne.io.Raw.plot>` to group the
+# channels in a different way. ``group_by='selection'`` uses the same channel
+# groups as MNE-C's mne_browse_raw (see :ref:`CACCJEJD`). The selections are
+# defined in ``mne-python/mne/data/mne_analyze.sel`` and by modifying the
+# channels there, you can define your own selection groups. Notice that this
+# also affects the selections returned by :func:`mne.read_selection`. By
+# default the selections only work for Neuromag data, but
+# ``group_by='position'`` tries to mimic this behavior for any data with sensor
+# positions available. The channels are grouped by sensor positions to 8 evenly
+# sized regions. Notice that for this to work effectively, all the data
+# channels in the channel array must be present. The ``order`` parameter allows
+# to customize the order and select a subset of channels for plotting (picks).
+# Here we use the butterfly mode and group the channels by position. To toggle
+# between regular and butterfly modes, press 'b' key when the plotter window is
+# active. Notice that ``group_by`` also affects the channel groupings in
+# butterfly mode.
+raw.plot(butterfly=True, group_by='position')
 
 ###############################################################################
 # We read the events from a file and passed it as a parameter when calling the
@@ -88,7 +110,7 @@ raw.plot_projs_topomap()
 raw.plot()
 
 ###############################################################################
-# Now click the `proj` button at the lower right corner of the browser
+# Now click the ``proj`` button at the lower right corner of the browser
 # window. A selection dialog should appear, where you can toggle the projectors
 # on and off. Notice that the first four are already applied to the data and
 # toggling them does not change the data. However the newly added projectors
@@ -98,8 +120,10 @@ raw.plot()
 # projectors.
 #
 # Raw container also lets us easily plot the power spectra over the raw data.
-# See the API documentation for more info.
-raw.plot_psd()
+# Here we plot the data using ``spatial_colors`` to map the line colors to
+# channel locations (default in versions >= 0.15.0). Other option is to use the
+# ``average`` (default in < 0.15.0). See the API documentation for more info.
+raw.plot_psd(tmax=np.inf, average=False)
 
 ###############################################################################
 # Plotting channel-wise power spectra is just as easy. The layout is inferred

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-med/python-mne.git



More information about the debian-med-commit mailing list