Skip to content

Commit

Permalink
Merge pull request #125 from coganlab/mri_explicit
Browse files Browse the repository at this point in the history
Mri explicit
  • Loading branch information
Aaronearlerichardson authored Dec 3, 2024
2 parents 75a6b70 + 398c2cd commit 43757ce
Show file tree
Hide file tree
Showing 19 changed files with 636 additions and 161 deletions.
22 changes: 15 additions & 7 deletions .github/workflows/Package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,26 @@ jobs:
with:
python-version: '3.10'
cache: 'pip'
# Publish (moved to end of document later)
- name: Build source distribution
- name: Setup pip
run: |
python -m pip install --upgrade pip
python -m pip install build --user
python -m pip install cibuildwheel
- name: Build source distribution
run: |
python -m build --sdist
- name: Store the distribution packages
- name: Build wheel
run: |
python -m cibuildwheel --output-dir dist/
env:
CIBW_BUILD: cp310-*
- name: Store dist & wheel
uses: actions/upload-artifact@v3
with:
name: python-package-distributions
name: python-package-dist-wheel
path: dist/


publish-to-testpypi:
name: Publish Python 🐍 distribution 📦 to TestPyPI
needs:
Expand All @@ -42,10 +50,10 @@ jobs:
id-token: write # IMPORTANT: mandatory for trusted publishing

steps:
- name: Download all the dists
- name: Download all the dists & wheels
uses: actions/download-artifact@v3
with:
name: python-package-distributions
name: python-package-dist-wheel
path: dist/
- name: Publish distribution 📦 to TestPyPI
uses: pypa/gh-action-pypi-publish@release/v1
Expand All @@ -65,7 +73,7 @@ jobs:
- name: Download all the dists
uses: actions/download-artifact@v3
with:
name: python-package-distributions
name: python-package-dist-wheel
path: dist/
- name: Publish package distributions to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
11 changes: 8 additions & 3 deletions .github/workflows/Python-CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ jobs:
- os: ubuntu-latest
python: '3.10'
kind: pip
- os: ubuntu-latest
python: '3.13'
kind: pip
environment: release
steps:
- uses: actions/checkout@v4
Expand All @@ -51,6 +54,7 @@ jobs:
**/requirements.txt
**/pyproject.toml
- if: matrix.os != 'windows-latest'
shell: bash {0}
run: |
python -m pip install --upgrade pip
pip install -e . -v
Expand All @@ -62,16 +66,17 @@ jobs:
- run: ./.github/check_qt_import.sh $MNE_QT_BACKEND
if: ${{ env.MNE_QT_BACKEND != '' }}
- name: List packages
shell: bash {0}
run: |
python -c "import mne; mne.sys_info()"
- name: fetch mne datasets
run: python -c "import mne; mne.datasets.misc.data_path(); mne.datasets.epilepsy_ecog.data_path(); mne.datasets.sample.data_path()"
if: matrix.os == 'ubuntu-latest'
if: matrix.os == 'ubuntu-latest' && matrix.python == '3.10'
- name: Test with pytest
run: |
pip install -r envs/dev/testing-reqs.txt
pytest tests ieeg -v --cov=. --doctest-modules --pycodestyle --cov-report xml
if: matrix.os == 'ubuntu-latest'
if: matrix.os == 'ubuntu-latest' && matrix.python == '3.10'
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v3
if: matrix.os == 'ubuntu-latest'
if: matrix.os == 'ubuntu-latest' && matrix.python == '3.10'
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,9 @@ docs/auto_examples/*
docs/ieeg*
docs/modules.rst
docs/sg_execution_times.rst

# Files compiled and built upon installing ieeg
ieeg.egg-info/*
ieeg/**/*.pyd
ieeg/**/*.c
!ieeg/calc/_fast/ufuncs.c
2 changes: 1 addition & 1 deletion .readthedocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ build:
# golang: "1.19"
jobs:
pre_build:
- pip install --upgrade pip myst_parser sphinx_gallery SQLAlchemy sphinxcontrib-matlabdomain sphinx_rtd_theme sphinx_copybutton memory_profiler cython
- pip install --upgrade pip myst_parser sphinx_gallery traitlets SQLAlchemy sphinxcontrib-matlabdomain sphinx_rtd_theme sphinx_copybutton memory_profiler cython
- sphinx-apidoc -f -o docs/references -e -T -t docs/_templates ieeg
- python -c "import mne; mne.sys_info()"

Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ A repo of current preprocessing pipelines for the [Cogan Lab](https://www.coganl

## Pipeline Functionality

[![Python (3.10) on Windows/Linux](https://github.com/coganlab/IEEG_Pipelines/actions/workflows/Python-CI.yml/badge.svg)](https://github.com/coganlab/IEEG_Pipelines/actions/workflows/Python-CI.yml)
[![Python (3.10 - 3.13) on Windows/Linux/Mac](https://github.com/coganlab/IEEG_Pipelines/actions/workflows/Python-CI.yml/badge.svg)](https://github.com/coganlab/IEEG_Pipelines/actions/workflows/Python-CI.yml)

[![MATLAB latest](https://github.com/coganlab/IEEG_Pipelines/actions/workflows/MATLAB-CI.yml/badge.svg)](https://github.com/coganlab/IEEG_Pipelines/actions/workflows/MATLAB-CI.yml)

Expand All @@ -33,15 +33,15 @@ A repo of current preprocessing pipelines for the [Cogan Lab](https://www.coganl
### Python
Version 3.10 supported
Version 3.10 - 3.13 supported
#### Conda
1. Install Anaconda
2. Create an anaconda environment with python and pip packages installed
```bash
conda create -n <YOUR_NAME> python<3.13 pip
conda create -n <YOUR_NAME> python<3.14 pip
```
3. Activate the environment
Expand Down
2 changes: 1 addition & 1 deletion envs/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
bids>=0.0
mne
mne-bids<=0.14
mne-bids>=0.16
numpy
matplotlib
scipy
Expand Down
23 changes: 18 additions & 5 deletions ieeg/calc/fast.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,18 @@ def mixup(arr: np.ndarray, obs_axis: int, alpha: float = 1.,
[[16. , 17. , 18. , 19. ],
[20. , 21. , 22. , 23. ]]])
"""
if seed is None:
seed = np.random.randint(0, 2**16 - 1)

cmixup(arr, obs_axis, alpha, seed)
if arr.ndim > 3:
for i in range(arr.shape[0]):
mixup(arr[i], obs_axis - 1, alpha, seed)
elif arr.ndim == 1:
raise ValueError("Array must have at least 2 dimensions")
else:
if seed is None:
seed = np.random.randint(0, 2 ** 16 - 1)
if obs_axis == 0:
arr = arr.swapaxes(1, obs_axis)
cmixup(arr, 1, alpha, seed)


def norm(arr: np.ndarray, obs_axis: int = -1) -> None:
Expand Down Expand Up @@ -178,7 +186,12 @@ def mean_diff(group1: np.ndarray, group2: np.ndarray,
>>> mean_diff(group1, group2, axis=1)
array([ 0., 30., 0., 5., 0.])
"""
in1 = np.moveaxis(group1, axis, -1)
in2 = np.moveaxis(group2, axis, -1)
assert group1.ndim == group2.ndim, ("Arrays must have the same number of"
"dimensions")
if group1.ndim > 1 and axis not in (group1.ndim - 1, -1):
in1 = np.moveaxis(group1, axis, -1)
in2 = np.moveaxis(group2, axis, -1)
else:
in1, in2 = group1, group2

return _md(in1, in2)
104 changes: 79 additions & 25 deletions ieeg/calc/mat.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@
from ieeg.calc.fast import concatenate_arrays

import numpy as np
from numpy.matlib import repmat
from numpy.typing import ArrayLike
from numpy.core.numeric import normalize_axis_tuple

import ieeg

Expand Down Expand Up @@ -45,6 +43,56 @@ def iter_nest_dict(d: dict, _lvl: int = 0, _coords=()):
yield _coords + (k,), v


def lcs(*strings: str) -> str:
"""Find the longest common substring in a list of strings.
Parameters
----------
*strings : str
The strings to find the longest common substring of.
Returns
-------
str
The longest common substring in the list of strings.
Examples
--------
>>> lcs('ABAB')
'ABAB'
>>> lcs('ABAB', 'BABA')
'ABA'
>>> lcs('ABAB', 'BABA', 'ABBA')
'AB'
"""
if not strings:
return ""

def _lcs_two_strings(s1, s2):
n, m = len(s1), len(s2)
dp = [[0] * (m + 1) for _ in range(n + 1)]
max_len = 0
end_pos = 0

for i in range(1, n + 1):
for j in range(1, m + 1):
if s1[i - 1] == s2[j - 1]:
dp[i][j] = dp[i - 1][j - 1] + 1
if dp[i][j] > max_len:
max_len = dp[i][j]
end_pos = i

return s1[end_pos - max_len:end_pos]

common_substr = strings[0]
for string in strings[1:]:
common_substr = _lcs_two_strings(common_substr, string)
if not common_substr:
break

return common_substr


class LabeledArray(np.ndarray):
""" A numpy array with labeled dimensions, acting like a dictionary.
Expand Down Expand Up @@ -73,6 +121,7 @@ class LabeledArray(np.ndarray):
Examples
--------
>>> import numpy as np
>>> np.set_printoptions(legacy='1.21')
>>> from ieeg.calc.mat import LabeledArray
>>> arr = np.ones((2, 3, 4), dtype=int)
>>> labels = (('a', 'b'), ('c', 'd', 'e'), ('f', 'g', 'h', 'i'))
Expand Down Expand Up @@ -240,7 +289,7 @@ def swapaxes(self, axis1, axis2):
return LabeledArray(arr, new)

def transpose(self, axes):
axes = normalize_axis_tuple(axes, self.ndim)
axes = np._core.numeric.normalize_axis_tuple(axes, self.ndim)
new_labels = [self.labels[i] for i in axes]
arr_t = super(LabeledArray, self).transpose(axes)
return LabeledArray(arr_t, new_labels)
Expand Down Expand Up @@ -909,7 +958,8 @@ def __matmul__(self, other):
return result

def __add__(self, other):
result = super().__add__(self.delimiter).__add__(other)
result = self.view(np.char.chararray).__add__(
self.delimiter).__add__(other.view(np.char.chararray))
return Labels(result)

def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
Expand Down Expand Up @@ -945,8 +995,8 @@ def decompose(self) -> list['Labels', ...]:
for i, dim in enumerate(self.shape):
for j in range(dim):
row = np.take(self, j, axis=i).flatten().astype(str)
common = _longest_common_substring(tuple(map(
lambda x: tuple(x.split(self.delimiter, )), row)))
splitted = row.split(self.delimiter)
common = functools.reduce(np.intersect1d, splitted)
if len(common) == 0:
common = np.unique(row).tolist()
new_labels[i][j] = self.delimiter.join(common)
Expand Down Expand Up @@ -1039,25 +1089,29 @@ def _make_array_unique(arr: np.ndarray, delimiter: str) -> np.ndarray:
return out


def _longest_common_substring(strings: tuple[tuple[str]]) -> tuple[str]:
matrix = [[] for _ in range(len(strings))]
for i in range(len(strings) - 1):
matrix[i] = _lcs(strings[i], strings[i + 1])
else:
matrix[-1] = [True for _ in range(len(strings[-1]))]
return np.array(strings[0])[np.all(matrix, axis=0)].tolist()

def is_broadcastable(shp1: tuple[int, ...], shp2: tuple[int, ...]):
"""Check if two shapes are broadcastable.
@functools.lru_cache(None)
def _lcs(s1: tuple, s2: tuple) -> list[bool]:
matrix = [False for _ in range(len(s1))]
for i in range(len(s1)):
if s1[i] == s2[i]:
matrix[i] = True
return matrix
Parameters
----------
shp1 : tuple[int, ...]
The first shape.
shp2 : tuple[int, ...]
The second shape.
Returns
-------
bool
def is_broadcastable(shp1: tuple[int, ...], shp2: tuple[int, ...]):
Examples
--------
>>> is_broadcastable((2, 3), (2, 3))
True
>>> is_broadcastable((2, 3), (3, 2))
False
>>> is_broadcastable((2, 3), (2, 1))
True
"""

ndim1 = len(shp1)
ndim2 = len(shp2)
Expand Down Expand Up @@ -1348,13 +1402,12 @@ def get_elbow(data: np.ndarray) -> int:
"""
nPoints = len(data)
allCoord = np.vstack((range(nPoints), data)).T
np.array([range(nPoints), data])
firstPoint = allCoord[0]
lineVec = allCoord[-1] - allCoord[0]
lineVecNorm = lineVec / np.sqrt(np.sum(lineVec ** 2))
vecFromFirst = allCoord - firstPoint
scalarProduct = np.sum(vecFromFirst * repmat(
lineVecNorm, nPoints, 1), axis=1)
scalarProduct = np.sum(vecFromFirst * np.tile(lineVecNorm,
(nPoints, 1)), axis=1)
vecFromFirstParallel = np.outer(scalarProduct, lineVecNorm)
vecToLine = vecFromFirst - vecFromFirstParallel
distToLine = np.sqrt(np.sum(vecToLine ** 2, axis=1))
Expand Down Expand Up @@ -1410,6 +1463,7 @@ def _cat_test():
Examples
--------
>>> import numpy as np
>>> np.set_printoptions(legacy='1.21')
>>> a = np.array([[1, 2, 3]])
>>> b = np.array([[4, 5]])
>>> c = np.array([[6, 7, 8, 9]])
Expand Down
Loading

0 comments on commit 43757ce

Please sign in to comment.