diff --git a/.github/workflows/flake.yml b/.github/workflows/flake.yml index 10071522..34a389cd 100644 --- a/.github/workflows/flake.yml +++ b/.github/workflows/flake.yml @@ -15,7 +15,7 @@ jobs: - name: Set up Python 🔧 uses: actions/setup-python@v4 with: - python-version: '3.9' + python-version: '3.11' - name: Install dependencies 🔧 run: | diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 070eb19a..cf221145 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.8, 3.9] + python-version: ["3.10", "3.11", "3.12"] steps: - name: Checkout 🛎️ diff --git a/.github/workflows/test_doc.yml b/.github/workflows/test_doc.yml index 0b05695d..2b4849c4 100644 --- a/.github/workflows/test_doc.yml +++ b/.github/workflows/test_doc.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.8", "3.9", "3.10", "3.11"] + python-version: ["3.10", "3.11", "3.12"] steps: - name: Checkout 🛎️ @@ -40,7 +40,7 @@ jobs: - name: Set up Python 🔧 uses: actions/setup-python@v4 with: - python-version: '3.8' + python-version: '3.11' - name: Install dependencies 🔧 run: | diff --git a/.gitignore b/.gitignore index e915b2ef..0837652b 100644 --- a/.gitignore +++ b/.gitignore @@ -122,4 +122,6 @@ docs/source/auto_examples/ node_modules/ package.json -yarn.lock \ No newline at end of file +yarn.lock +docs/source/sg_execution_times.rst + diff --git a/docs/source/conf.py b/docs/source/conf.py index f4851136..2e3ac49b 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -89,7 +89,7 @@ # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = None +language = 'en' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. @@ -276,7 +276,7 @@ # -- Options for intersphinx extension --------------------------------------- # Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {"https://docs.python.org/": None} +intersphinx_mapping = {"python": ("https://docs.python.org/3", None)} # -- Options for todo extension ---------------------------------------------- diff --git a/docs/source/overview/ovw_whatsnew.rst b/docs/source/overview/ovw_whatsnew.rst index 6a498c46..07e97699 100644 --- a/docs/source/overview/ovw_whatsnew.rst +++ b/docs/source/overview/ovw_whatsnew.rst @@ -12,6 +12,14 @@ Bug fixes * Support mne-python versions newer than 1.5.0, up to the current release (:commit:`9a81a6dc`) * Restore a compatibility shim so mne keeps working with NumPy >= 2.4, which removed ``np.in1d`` while mne still relies on it internally (:commit:`ad7cfe65`) * Fix type comparison in :func:`frites.dataset.ds_utils.multi_to_uni_conditions` and in :func:`frites.plot.plot_conn_circle` (:commit:`cee7ed4a`) +* Fix :func:`frites.conn.conn_spec` in multitaper mode : the cross- and auto-spectra are now averaged over tapers (the complex coefficients used to be averaged before, cancelling most of the signal). Coherence and PLV values change for every multitaper user (:pull:`69`) +* Fix the hanning smoothing kernel of :func:`frites.conn.conn_spec` (``sm_times`` of 2 samples gave a NaN output, 3 samples no smoothing) (:pull:`69`) +* The cross-spectrum (``metric='sxy'``) of :func:`frites.conn.conn_spec` is now complex (the imaginary part used to be silently dropped) (:pull:`69`) +* :func:`frites.conn.conn_spec` sets ``zero_mean=False`` explicitly in the time-frequency decomposition : mne changed its default to ``True``, which silently changed the results between mne versions + +Dependencies +++++++++++++ +* Tested against mne 1.5 up to 1.13 (mne >= 1.13 requires Python >= 3.11) and Python 3.10 to 3.12 ; the continuous integration matrix moved from Python 3.8/3.9 to 3.10/3.11/3.12 v0.4.4 ------ @@ -22,7 +30,7 @@ New Features * New function :func:`frites.core.ent_nd_g` to compute entropy on tensors (:commit:`17587a15`) * New function :func:`frites.conn.conn_ii` to estimate the interaction information (:commit:`10938b46`) * New function :func:`frites.conn.conn_pid` to estimate the partial information decomposition (:commit:`ac9798dd`) -- New function :func:`frites.conn.conn_fit` to estimate the feature specific information transfer (:PR:`59`) - :author:`aopy` +- New function :func:`frites.conn.conn_fit` to estimate the feature specific information transfer (:pull:`59`) - :author:`aopy` Bug fixes +++++++++ diff --git a/frites/__init__.py b/frites/__init__.py index 3d7edd20..640eed49 100644 --- a/frites/__init__.py +++ b/frites/__init__.py @@ -70,8 +70,10 @@ def set_mpl_style(style='frites'): Style name. Use either "frites" for a white background or "ggfrites" for a grey brackground like with ggplot. """ - from pkg_resources import resource_filename + # pkg_resources (setuptools) is no longer available by default on + # Python >= 3.12 and is deprecated; use the stdlib instead + from importlib.resources import files import matplotlib.pyplot as plt assert style in ["frites", "ggfrites"] - path_style = resource_filename('frites', f'data/{style}.mplstyle') - plt.style.use(path_style) + path_style = files('frites') / 'data' / f'{style}.mplstyle' + plt.style.use(str(path_style)) diff --git a/frites/conn/conn_tf.py b/frites/conn/conn_tf.py index bce27cd5..389c35c8 100644 --- a/frites/conn/conn_tf.py +++ b/frites/conn/conn_tf.py @@ -63,6 +63,12 @@ def _tf_decomp(data, sf, freqs, mode='morlet', n_cycles=7.0, mt_bandwidth=None, complex coefficients across tapers cancels most of the signal and does not give the multitaper estimator. """ + # mne changed the default of `zero_mean` from False (<=1.5) to True. Fix it + # explicitly so that the results do not depend on the mne version (a + # user-defined value in kw_cwt / kw_mt takes precedence) + kw_cwt = {'zero_mean': False, **kw_cwt} + kw_mt = {'zero_mean': False, **kw_mt} + if mode == 'morlet': out = tfr_array_morlet( data, sf, freqs, n_cycles=n_cycles, output='complex', decim=decim, diff --git a/frites/conn/tests/test_conn_spec.py b/frites/conn/tests/test_conn_spec.py index e1d341e3..fce30943 100644 --- a/frites/conn/tests/test_conn_spec.py +++ b/frites/conn/tests/test_conn_spec.py @@ -150,7 +150,7 @@ def test_multitaper_reference(self): # reference : (trials, chans, tapers, freqs, times) w = tfr_array_multitaper(x.data, sfreq, freqs, n_cycles=n_cycles, time_bandwidth=tw, output='complex', - verbose=False) + zero_mean=False, verbose=False) s_xy = (w[:, 0] * np.conj(w[:, 1])).mean(1) s_xx = (np.abs(w[:, 0]) ** 2).mean(1) s_yy = (np.abs(w[:, 1]) ** 2).mean(1) @@ -183,7 +183,7 @@ def test_sxy_complex(self): assert np.abs(sxy.data.imag).max() > 0 # morlet cross-spectrum is exactly w_x * conj(w_y) w = tfr_array_morlet(x.data, sfreq, freqs, n_cycles=n_cycles, - output='complex', verbose=False) + output='complex', zero_mean=False, verbose=False) sxy = conn_spec(x, metric='sxy', dtype=np.complex128, **kw) np.testing.assert_allclose(sxy.sel(roi='x-y').data, w[:, 0] * np.conj(w[:, 1]), rtol=1e-6) diff --git a/setup.py b/setup.py index 040d1310..a2857251 100644 --- a/setup.py +++ b/setup.py @@ -30,8 +30,8 @@ def read(fname): core_deps = ['matplotlib', 'networkx', 'numba', 'dcor'] test_deps = ['pytest', 'pytest-sugar', 'pytest-cov', 'codecov'] doc_deps = [ - 'sphinx==7.0.0 ', 'sphinx-gallery', 'pydata-sphinx-theme>=0.6.3', - 'sphinxcontrib-bibtex==1.0.0', 'numpydoc', 'xlrd', 'openpyxl', 'seaborn', + 'sphinx>=7.4', 'sphinx-gallery', 'pydata-sphinx-theme>=0.6.3', + 'sphinxcontrib-bibtex>=2.6', 'numpydoc', 'xlrd', 'openpyxl', 'seaborn', 'memory-profiler', 'sphinx-panels', 'sphinx-copybutton' ] flake_deps = ['flake8', 'pep8-naming'] @@ -67,8 +67,7 @@ def read(fname): 'Intended Audience :: Science/Research', 'Intended Audience :: Education', 'Intended Audience :: Developers', - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", ])