mne-tools / mne-icalabel

Automatic labeling of ICA components in Python.
https://mne.tools/mne-icalabel/dev/index.html
BSD 3-Clause "New" or "Revised" License
94 stars 15 forks source link

[GUI] Additional PyQt5 dependency? #103

Closed adswa closed 2 years ago

adswa commented 2 years ago

Describe the bug

Invoking the GUI after an installation of mne-icalabel[gui] via pip on a Windows 11 and Debian bookworm machine in a fresh Python 3.9 environment fails with an

ImportError: Failed to import any qt binding

Installing PyQt5 resolves the error.

Steps to reproduce

On both machines, I performed the following installations:

pip install ipython
pip install mne-icalabel
pip install sklearn
pip install mne-icalabel[gui]

and then followed the tutorial exactly. Here is the outcome

click to expand Windows CMD ```python C:\Users\adina\repos>pip install mne-icalabel[gui] Requirement already satisfied: mne-icalabel[gui] in c:\users\adina\appdata\local\programs\python\python39\lib\site-packages (0.3.1) Requirement already satisfied: pooch in c:\users\adina\appdata\local\programs\python\python39\lib\site-packages (from mne-icalabel[gui]) (1.6.0) Requirement already satisfied: torch in c:\users\adina\appdata\local\programs\python\python39\lib\site-packages (from mne-icalabel[gui]) (1.12.1) Requirement already satisfied: mne>=1.1 in c:\users\adina\appdata\local\programs\python\python39\lib\site-packages (from mne-icalabel[gui]) (1.1.0) Requirement already satisfied: numpy>=1.16.0 in c:\users\adina\appdata\local\programs\python\python39\lib\site-packages (from mne-icalabel[gui]) (1.23.2) Requirement already satisfied: scipy>=1.2.0 in c:\users\adina\appdata\local\programs\python\python39\lib\site-packages (from mne-icalabel[gui]) (1.9.0) Collecting qtpy Downloading QtPy-2.2.0-py3-none-any.whl (82 kB) |████████████████████████████████| 82 kB 2.9 MB/s Collecting mne-qt-browser Downloading mne_qt_browser-0.3.1-py3-none-any.whl (70 kB) |████████████████████████████████| 70 kB 4.5 MB/s Requirement already satisfied: matplotlib in c:\users\adina\appdata\local\programs\python\python39\lib\site-packages (from mne-icalabel[gui]) (3.5.3) Requirement already satisfied: decorator in c:\users\adina\appdata\local\programs\python\python39\lib\site-packages (from mne>=1.1->mne-icalabel[gui]) (5.1.1) Requirement already satisfied: jinja2 in c:\users\adina\appdata\local\programs\python\python39\lib\site-packages (from mne>=1.1->mne-icalabel[gui]) (3.1.2) Requirement already satisfied: packaging in c:\users\adina\appdata\local\programs\python\python39\lib\site-packages (from mne>=1.1->mne-icalabel[gui]) (21.3) Requirement already satisfied: tqdm in c:\users\adina\appdata\local\programs\python\python39\lib\site-packages (from mne>=1.1->mne-icalabel[gui]) (4.62.3) Requirement already satisfied: appdirs>=1.3.0 in c:\users\adina\appdata\local\programs\python\python39\lib\site-packages (from pooch->mne-icalabel[gui]) (1.4.4) Requirement already satisfied: requests>=2.19.0 in c:\users\adina\appdata\local\programs\python\python39\lib\site-packages (from pooch->mne-icalabel[gui]) (2.27.1) Requirement already satisfied: pyparsing!=3.0.5,>=2.0.2 in c:\users\adina\appdata\local\programs\python\python39\lib\site-packages (from packaging->mne>=1.1->mne-icalabel[gui]) (3.0.9) Requirement already satisfied: certifi>=2017.4.17 in c:\users\adina\appdata\local\programs\python\python39\lib\site-packages (from requests>=2.19.0->pooch->mne-icalabel[gui]) (2021.10.8) Requirement already satisfied: charset-normalizer~=2.0.0 in c:\users\adina\appdata\local\programs\python\python39\lib\site-packages (from requests>=2.19.0->pooch->mne-icalabel[gui]) (2.0.7) Requirement already satisfied: idna<4,>=2.5 in c:\users\adina\appdata\local\programs\python\python39\lib\site-packages (from requests>=2.19.0->pooch->mne-icalabel[gui]) (3.3) Requirement already satisfied: urllib3<1.27,>=1.21.1 in c:\users\adina\appdata\local\programs\python\python39\lib\site-packages (from requests>=2.19.0->pooch->mne-icalabel[gui]) (1.26.7) Requirement already satisfied: MarkupSafe>=2.0 in c:\users\adina\appdata\local\programs\python\python39\lib\site-packages (from jinja2->mne>=1.1->mne-icalabel[gui]) (2.1.1) Requirement already satisfied: kiwisolver>=1.0.1 in c:\users\adina\appdata\local\programs\python\python39\lib\site-packages (from matplotlib->mne-icalabel[gui]) (1.4.4) Requirement already satisfied: cycler>=0.10 in c:\users\adina\appdata\local\programs\python\python39\lib\site-packages (from matplotlib->mne-icalabel[gui]) (0.11.0) Requirement already satisfied: pillow>=6.2.0 in c:\users\adina\appdata\local\programs\python\python39\lib\site-packages (from matplotlib->mne-icalabel[gui]) (9.2.0) Requirement already satisfied: python-dateutil>=2.7 in c:\users\adina\appdata\local\programs\python\python39\lib\site-packages (from matplotlib->mne-icalabel[gui]) (2.8.2) Requirement already satisfied: fonttools>=4.22.0 in c:\users\adina\appdata\local\programs\python\python39\lib\site-packages (from matplotlib->mne-icalabel[gui]) (4.36.0) Requirement already satisfied: six>=1.5 in c:\users\adina\appdata\local\programs\python\python39\lib\site-packages (from python-dateutil>=2.7->matplotlib->mne-icalabel[gui]) (1.16.0) Collecting colorspacious Downloading colorspacious-1.1.2-py2.py3-none-any.whl (37 kB) Collecting scooby Downloading scooby-0.6.0-py3-none-any.whl (14 kB) Collecting qdarkstyle Downloading QDarkStyle-3.1-py2.py3-none-any.whl (870 kB) |████████████████████████████████| 870 kB 6.8 MB/s Collecting darkdetect Downloading darkdetect-0.7.1-py2.py3-none-any.whl (8.2 kB) Collecting pyqtgraph>=0.12.3 Downloading pyqtgraph-0.12.4-py3-none-any.whl (995 kB) |████████████████████████████████| 995 kB 3.2 MB/s Requirement already satisfied: typing-extensions in c:\users\adina\appdata\local\programs\python\python39\lib\site-packages (from torch->mne-icalabel[gui]) (4.3.0) Requirement already satisfied: colorama in c:\users\adina\appdata\local\programs\python\python39\lib\site-packages (from tqdm->mne>=1.1->mne-icalabel[gui]) (0.4.4) Installing collected packages: qtpy, scooby, qdarkstyle, pyqtgraph, darkdetect, colorspacious, mne-qt-browser Successfully installed colorspacious-1.1.2 darkdetect-0.7.1 mne-qt-browser-0.3.1 pyqtgraph-0.12.4 qdarkstyle-3.1 qtpy-2.2.0 scooby-0.6.0 WARNING: You are using pip version 21.2.4; however, version 22.2.2 is available. You should consider upgrading via the 'C:\Users\adina\AppData\Local\Programs\Python\Python39\python.exe -m pip install --upgrade pip' command. C:\Users\adina\repos>ipython Python 3.9.9 (tags/v3.9.9:ccb0e6a, Nov 15 2021, 18:08:50) [MSC v.1929 64 bit (AMD64)] Type 'copyright', 'credits' or 'license' for more information IPython 8.4.0 -- An enhanced Interactive Python. Type '?' for help. In [1]: import os ...: ...: import mne ...: from mne.preprocessing import ICA ...: ...: from mne_icalabel.gui import label_ica_components In [2]: sample_data_folder = mne.datasets.sample.data_path() ...: sample_data_raw_file = os.path.join( ...: sample_data_folder, "MEG", "sample", "sample_audvis_filt-0-40_raw.fif" ...: ) ...: raw = mne.io.read_raw_fif(sample_data_raw_file) ...: ...: # Here we'll crop to 60 seconds and drop gradiometer channels for speed ...: raw.crop(tmax=60.0).pick_types(meg="mag", eeg=True, stim=True, eog=True) ...: raw.load_data() Opening raw data file C:\Users\adina\mne_data\MNE-sample-data\MEG\sample\sample_audvis_filt-0-40_raw.fif... Read a total of 4 projection items: PCA-v1 (1 x 102) idle PCA-v2 (1 x 102) idle PCA-v3 (1 x 102) idle Average EEG reference (1 x 60) idle Range : 6450 ... 48149 = 42.956 ... 320.665 secs Ready. Reading 0 ... 9009 = 0.000 ... 59.999 secs... Out[2]: In [3]: # high-pass filter the data and then perform ICA ...: filt_raw = raw.copy().filter(l_freq=1.0, h_freq=None) ...: ica = ICA(n_components=15, max_iter="auto", random_state=97) ...: ica.fit(filt_raw) Filtering raw data in 1 contiguous segment Setting up high-pass filter at 1 Hz FIR filter parameters --------------------- Designing a one-pass, zero-phase, non-causal highpass filter: - Windowed time-domain design (firwin) method - Hamming window with 0.0194 passband ripple and 53 dB stopband attenuation - Lower passband edge: 1.00 - Lower transition bandwidth: 1.00 Hz (-6 dB cutoff frequency: 0.50 Hz) - Filter length: 497 samples (3.310 sec) [Parallel(n_jobs=1)]: Using backend SequentialBackend with 1 concurrent workers. [Parallel(n_jobs=1)]: Done 1 out of 1 | elapsed: 0.0s remaining: 0.0s [Parallel(n_jobs=1)]: Done 2 out of 2 | elapsed: 0.0s remaining: 0.0s [Parallel(n_jobs=1)]: Done 3 out of 3 | elapsed: 0.0s remaining: 0.0s [Parallel(n_jobs=1)]: Done 4 out of 4 | elapsed: 0.0s remaining: 0.0s [Parallel(n_jobs=1)]: Done 161 out of 161 | elapsed: 0.0s finished Fitting ICA to data using 161 channels (please be patient, this may take a while) Selecting by number: 15 components Fitting ICA took 0.8s. Out[3]: In [4]: gui = label_ica_components(raw, ica) ...: ...: # The `ica` object is modified to contain the component labels ...: # after closing the GUI and can now be saved ...: # gui.close() # typically you close when done ...: ...: # Now, we can take a look at the components, which were modified in-place ...: # for the ICA instance. ...: print(ica.labels_) --------------------------------------------------------------------------- ImportError Traceback (most recent call last) Input In [4], in () ----> 1 gui = label_ica_components(raw, ica) 3 # The `ica` object is modified to contain the component labels 4 # after closing the GUI and can now be saved 5 # gui.close() # typically you close when done 6 7 # Now, we can take a look at the components, which were modified in-place 8 # for the ICA instance. 9 print(ica.labels_) File ~\AppData\Local\Programs\Python\Python39\lib\site-packages\mne_icalabel\gui\__init__.py:26, in label_ica_components(inst, ica, show, block) 5 """Launch the IC labelling GUI. 6 7 Parameters (...) 22 The graphical user interface (GUI) window. 23 """ 24 from mne.viz.backends._utils import _init_mne_qtapp, _qt_app_exec ---> 26 from ._label_components import ICAComponentLabeler 28 # get application 29 app = _init_mne_qtapp() File ~\AppData\Local\Programs\Python\Python39\lib\site-packages\mne_icalabel\gui\_label_components.py:4, in 1 from typing import Dict, List, Union 3 from matplotlib import pyplot as plt ----> 4 from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg 5 from mne import BaseEpochs 6 from mne.io import BaseRaw File ~\AppData\Local\Programs\Python\Python39\lib\site-packages\matplotlib\backends\backend_qt5agg.py:7, in 4 from .. import backends 6 backends._QT_FORCE_QT5_BINDING = True ----> 7 from .backend_qtagg import ( # noqa: F401, E402 # pylint: disable=W0611 8 _BackendQTAgg, FigureCanvasQTAgg, FigureManagerQT, NavigationToolbar2QT, 9 backend_version, FigureCanvasAgg, FigureCanvasQT 10 ) 13 @_BackendQTAgg.export 14 class _BackendQT5Agg(_BackendQTAgg): 15 pass File ~\AppData\Local\Programs\Python\Python39\lib\site-packages\matplotlib\backends\backend_qtagg.py:9, in 5 import ctypes 7 from matplotlib.transforms import Bbox ----> 9 from .qt_compat import QT_API, _enum, _setDevicePixelRatio 10 from .. import cbook 11 from .backend_agg import FigureCanvasAgg File ~\AppData\Local\Programs\Python\Python39\lib\site-packages\matplotlib\backends\qt_compat.py:142, in 140 break 141 else: --> 142 raise ImportError("Failed to import any qt binding") 143 else: # We should not get there. 144 raise AssertionError(f"Unexpected QT_API: {QT_API}") ImportError: Failed to import any qt binding ```
click to expand Linux zsh ``` (joss) adina@muninn in ~/repos/mne-icalabel on git:main! ❱ ipython Python 3.9.12 (main, Mar 24 2022, 13:02:21) Type 'copyright', 'credits' or 'license' for more information IPython 8.4.0 -- An enhanced Interactive Python. Type '?' for help. In [1]: import os ...: ...: import mne ...: from mne.preprocessing import ICA ...: ...: from mne_icalabel.gui import label_ica_components In [2]: sample_data_folder = mne.datasets.sample.data_path() ...: sample_data_raw_file = os.path.join( ...: sample_data_folder, "MEG", "sample", "sample_audvis_filt-0-40_raw.fif" ...: ) ...: raw = mne.io.read_raw_fif(sample_data_raw_file) ...: ...: # Here we'll crop to 60 seconds and drop gradiometer channels for speed ...: raw.crop(tmax=60.0).pick_types(meg="mag", eeg=True, stim=True, eog=True) ...: raw.load_data() Opening raw data file /home/adina/mne_data/MNE-sample-data/MEG/sample/sample_audvis_filt-0-40_raw.fif... Read a total of 4 projection items: PCA-v1 (1 x 102) idle PCA-v2 (1 x 102) idle PCA-v3 (1 x 102) idle Average EEG reference (1 x 60) idle Range : 6450 ... 48149 = 42.956 ... 320.665 secs Ready. Reading 0 ... 9009 = 0.000 ... 59.999 secs... Out[2]: In [3]: # high-pass filter the data and then perform ICA ...: filt_raw = raw.copy().filter(l_freq=1.0, h_freq=None) ...: ica = ICA(n_components=15, max_iter="auto", random_state=97) ...: ica.fit(filt_raw) Filtering raw data in 1 contiguous segment Setting up high-pass filter at 1 Hz FIR filter parameters --------------------- Designing a one-pass, zero-phase, non-causal highpass filter: - Windowed time-domain design (firwin) method - Hamming window with 0.0194 passband ripple and 53 dB stopband attenuation - Lower passband edge: 1.00 - Lower transition bandwidth: 1.00 Hz (-6 dB cutoff frequency: 0.50 Hz) - Filter length: 497 samples (3.310 sec) [Parallel(n_jobs=1)]: Using backend SequentialBackend with 1 concurrent workers. [Parallel(n_jobs=1)]: Done 1 out of 1 | elapsed: 0.0s remaining: 0.0s [Parallel(n_jobs=1)]: Done 2 out of 2 | elapsed: 0.0s remaining: 0.0s [Parallel(n_jobs=1)]: Done 3 out of 3 | elapsed: 0.0s remaining: 0.0s [Parallel(n_jobs=1)]: Done 4 out of 4 | elapsed: 0.0s remaining: 0.0s [Parallel(n_jobs=1)]: Done 161 out of 161 | elapsed: 0.1s finished Fitting ICA to data using 161 channels (please be patient, this may take a while) Selecting by number: 15 components Fitting ICA took 0.5s. Out[3]: In [4]: gui = label_ica_components(raw, ica) ...: ...: # The `ica` object is modified to contain the component labels ...: # after closing the GUI and can now be saved ...: # gui.close() # typically you close when done ...: ...: # Now, we can take a look at the components, which were modified in-place ...: # for the ICA instance. ...: print(ica.labels_) --------------------------------------------------------------------------- ImportError Traceback (most recent call last) Input In [4], in () ----> 1 gui = label_ica_components(raw, ica) 3 # The `ica` object is modified to contain the component labels 4 # after closing the GUI and can now be saved 5 # gui.close() # typically you close when done 6 7 # Now, we can take a look at the components, which were modified in-place 8 # for the ICA instance. 9 print(ica.labels_) File ~/repos/mne-icalabel/mne_icalabel/gui/__init__.py:26, in label_ica_components(inst, ica, show, block) 5 """Launch the IC labelling GUI. 6 7 Parameters (...) 22 The graphical user interface (GUI) window. 23 """ 24 from mne.viz.backends._utils import _init_mne_qtapp, _qt_app_exec ---> 26 from ._label_components import ICAComponentLabeler 28 # get application 29 app = _init_mne_qtapp() File ~/repos/mne-icalabel/mne_icalabel/gui/_label_components.py:4, in 1 from typing import Dict, List, Union 3 from matplotlib import pyplot as plt ----> 4 from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg 5 from mne import BaseEpochs 6 from mne.io import BaseRaw File ~/env/joss/lib/python3.9/site-packages/matplotlib/backends/backend_qt5agg.py:7, in 4 from .. import backends 6 backends._QT_FORCE_QT5_BINDING = True ----> 7 from .backend_qtagg import ( # noqa: F401, E402 # pylint: disable=W0611 8 _BackendQTAgg, FigureCanvasQTAgg, FigureManagerQT, NavigationToolbar2QT, 9 backend_version, FigureCanvasAgg, FigureCanvasQT 10 ) 13 @_BackendQTAgg.export 14 class _BackendQT5Agg(_BackendQTAgg): 15 pass File ~/env/joss/lib/python3.9/site-packages/matplotlib/backends/backend_qtagg.py:9, in 5 import ctypes 7 from matplotlib.transforms import Bbox ----> 9 from .qt_compat import QT_API, _enum, _setDevicePixelRatio 10 from .. import cbook 11 from .backend_agg import FigureCanvasAgg File ~/env/joss/lib/python3.9/site-packages/matplotlib/backends/qt_compat.py:142, in 140 break 141 else: --> 142 raise ImportError("Failed to import any qt binding") 143 else: # We should not get there. 144 raise AssertionError(f"Unexpected QT_API: {QT_API}") ImportError: Failed to import any qt binding ```

Installing PyQt5, which currently isn't listed as a dependency for the [gui] components, solved the issue on both machines.

mscheltienne commented 2 years ago

Yes, we took the same approach as mne-qt-browser: install qtpy but don't install one of the 4 possibles qt bindings. c.f. their requirement file. It is up to the user to install in the environment one of PyQt5, PyQt6, PySide2, or Pyside6.

The difference with mne-qt-browser is that we raise if the bindings are missing, while the browser will swap the backend to matplotlib.

The error message should be improved, it's not super explicit, and maybe also let's add a note in the tutorial. Would that be acceptable to resolve this issue?

adswa commented 2 years ago

The error message should be improved, it's not super explicit, and maybe also let's add a note in the tutorial. Would that be acceptable to resolve this issue?

sure. thanks for the delineation. :)