Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions eegdash/features/extractors.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

from abc import ABC, abstractmethod
from collections.abc import Callable
from functools import partial
Expand Down
10 changes: 7 additions & 3 deletions eegdash/features/feature_bank/complexity.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,12 @@ def preprocess(self, x, m=2, r=0.2, l=1):
counts_m = np.empty((*x.shape[:-1], (x.shape[-1] - m + 1) // l))
counts_mp1 = np.empty((*x.shape[:-1], (x.shape[-1] - m) // l))
for i in np.ndindex(x.shape[:-1]):
counts_m[*i, :] = _channel_app_samp_entropy_counts(x[i], m, rr[i], l)
counts_mp1[*i, :] = _channel_app_samp_entropy_counts(x[i], m + 1, rr[i], l)
counts_m[i + (slice(None),)] = _channel_app_samp_entropy_counts(
x[i], m, rr[i], l
)
counts_mp1[i + (slice(None),)] = _channel_app_samp_entropy_counts(
x[i], m + 1, rr[i], l
)
return counts_m, counts_mp1


Expand All @@ -62,7 +66,7 @@ def complexity_sample_entropy(counts_m, counts_mp1):
def complexity_svd_entropy(x, m=10, tau=1):
x_emb = np.empty((*x.shape[:-1], (x.shape[-1] - m + 1) // tau, m))
for i in np.ndindex(x.shape[:-1]):
x_emb[*i, :, :] = _create_embedding(x[i], m, tau)
x_emb[i + (slice(None), slice(None))] = _create_embedding(x[i], m, tau)
s = np.linalg.svdvals(x_emb)
s /= s.sum(axis=-1, keepdims=True)
return -np.sum(s * np.log(s), axis=-1)
Expand Down
2 changes: 1 addition & 1 deletion eegdash/features/feature_bank/dimensionality.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def dimensionality_higuchi_fractal_dim(x, k_max=10, eps=1e-7):
for i in np.ndindex(x.shape[:-1]):
for k in range(1, k_max + 1):
for m in range(k):
L_km[m] = np.mean(np.abs(np.diff(x[*i, m:], n=k)))
L_km[m] = np.mean(np.abs(np.diff(x[i + (slice(m, None),)], n=k)))
L_k[k - 1] = (N - 1) * np.sum(L_km[:k]) / (k**3)
L_k = np.maximum(L_k, eps)
hfd[i] = np.linalg.lstsq(log_k, np.log(L_k))[0][0]
Expand Down
2 changes: 2 additions & 0 deletions eegdash/features/inspect.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

import inspect
from collections.abc import Callable

Expand Down
2 changes: 2 additions & 0 deletions eegdash/features/serialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

"""

from __future__ import annotations

from pathlib import Path

import pandas as pd
Expand Down
67 changes: 67 additions & 0 deletions tests/test_features.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
"""Test for features module Python 3.10+ compatibility."""

import pytest


def test_import_features_module():
"""Test that the features module can be imported without syntax errors.

This test ensures Python 3.10+ compatibility by verifying that:
1. Type annotations with list[], type[], and | syntax work (via __future__ imports)
2. No Python 3.11+ exclusive syntax is used (like *unpacking in subscripts)
"""
try:
import eegdash.features

assert eegdash.features is not None
except SyntaxError as e:
pytest.fail(f"SyntaxError when importing eegdash.features: {e}")
except ImportError as e:
pytest.fail(f"ImportError when importing eegdash.features: {e}")


def test_import_features_submodules():
"""Test that all features submodules can be imported."""
submodules = [
"eegdash.features.inspect",
"eegdash.features.extractors",
"eegdash.features.serialization",
"eegdash.features.datasets",
"eegdash.features.decorators",
"eegdash.features.feature_bank",
"eegdash.features.feature_bank.complexity",
"eegdash.features.feature_bank.dimensionality",
"eegdash.features.feature_bank.signal",
"eegdash.features.feature_bank.spectral",
"eegdash.features.feature_bank.connectivity",
"eegdash.features.feature_bank.csp",
]

for module_name in submodules:
try:
__import__(module_name)
except SyntaxError as e:
pytest.fail(f"SyntaxError when importing {module_name}: {e}")
except ImportError:
# Some imports might fail due to missing dependencies, that's ok
# We only care about SyntaxError
pass


def test_features_basic_functionality():
"""Test basic features module functionality."""
from eegdash.features import (
get_all_feature_extractors,
get_all_feature_kinds,
get_all_features,
)

# These should return lists without errors
features = get_all_features()
assert isinstance(features, list)

extractors = get_all_feature_extractors()
assert isinstance(extractors, list)

kinds = get_all_feature_kinds()
assert isinstance(kinds, list)