Skip to content

Commit

Permalink
Merge branch 'main' into seg
Browse files Browse the repository at this point in the history
  • Loading branch information
alexrockhill authored Dec 4, 2023
2 parents bbc4572 + 95c8a30 commit fee40a5
Show file tree
Hide file tree
Showing 8 changed files with 495 additions and 36 deletions.
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ jobs:
- run:
name: Get Python running
command: |
pip install --upgrade PyQt6 sphinx-gallery pydata-sphinx-theme numpydoc scikit-learn nilearn autoreject git+https://github.com/pyvista/pyvista@main memory_profiler sphinxcontrib.bibtex sphinxcontrib.youtube
pip install --upgrade PyQt6 "PyQt6-Qt6!=6.6.1" sphinx-gallery pydata-sphinx-theme numpydoc scikit-learn nilearn mne-bids autoreject git+https://github.com/pyvista/pyvista@main memory_profiler sphinxcontrib.bibtex sphinxcontrib.youtube
pip install -ve ./mne-python .
- run:
name: Check Qt
Expand Down
13 changes: 6 additions & 7 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,19 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
python-version: ["3.11"] # Newest supported version (dipy not out for 3.11 yet!)
os: [ubuntu-latest, windows-latest, macos-latest]
python-version: ["3.11"]
mne-version: [mne-main]
qt: [PyQt6]
include:
# macOS (can be moved above once Dipy releases 3.11 wheels)
- os: macos-latest
python-version: "3.10"
mne-version: mne-main # TODO: Set back to mne-stable once 1.4 is out (we need its pytest fixtures)
mne-version: mne-stable
qt: PyQt6
# Old (and PyQt5)
- os: ubuntu-latest
python-version: "3.8"
mne-version: mne-main # TODO: Set back to mne-stable once 1.4 is out (we need its pytest fixtures)
python-version: "3.9"
mne-version: mne-stable
qt: PyQt5
# PySide2
- os: ubuntu-latest
Expand Down Expand Up @@ -63,7 +62,7 @@ jobs:
if: "matrix.mne-version == 'mne-main'"
run: git clone --single-branch --branch main https://github.com/mne-tools/mne-python.git
- run: pip install -ve ./mne-python
- run: pip install -v ${{ matrix.qt }}
- run: pip install -v ${{ matrix.qt }} "PyQt6-Qt6!=6.6.1"
- run: pip install -ve .[tests]
- run: mne sys_info
- run: |
Expand Down
72 changes: 72 additions & 0 deletions examples/ieeg_locate.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import mne
import mne_gui_addons as mne_gui
from mne.datasets import fetch_fsaverage
import mne_bids

# paths to mne datasets: sample sEEG and FreeSurfer's fsaverage subject,
# which is in MNI space
Expand Down Expand Up @@ -384,6 +385,38 @@ def plot_overlay(image, compare, title, thresh=None):
)
# The `raw` object is modified to contain the channel locations

# %%
# In a real case, we would not already have contact locations. We would have
# to click through the slices to find spots of hyperintensity and associate
# those with channel names from the recording. If we have surgical plans for
# where these contacts were targeted, we can try to do this contact detection
# and association automatically. Here, we will fake the surgical plans using
# the locations that have already been found but, in practice, you would enter
# these manually from the planning documentation.

montage = raw.get_montage()
montage.apply_trans(subj_trans) # convert to surface RAS
# convert to scanner RAS
mne_bids.convert_montage_to_ras(
montage, subject="sample_seeg", subjects_dir=misc_path / "seeg"
)

raw.set_montage(None) # clear already found montage
# fake surgical plans from already-found contact locations
targets = {
"".join([letter for letter in ch if not letter.isdigit()]): pos
for ch, pos in montage.get_positions()["ch_pos"].items()
if [letter for letter in ch if letter.isdigit()] == ["1"]
}
mne_gui.locate_ieeg(
raw.info,
subj_trans,
CT_aligned,
subject="sample_seeg",
subjects_dir=misc_path / "seeg",
targets=targets,
)

# %%
# Let's do a quick sidebar and show what this looks like for ECoG as well.

Expand Down Expand Up @@ -415,6 +448,45 @@ def plot_overlay(image, compare, title, thresh=None):
subjects_dir=misc_path / "ecog",
)

# %%
# Similarly for ECoG, we can try to automatically detect contact locations.
# In the case of ECoG, there can often be a lot of contacts so this can be
# a big time saver.

montage = raw_ecog.get_montage()
montage.apply_trans(subj_trans_ecog) # convert to surface RAS
# convert to scanner RAS
mne_bids.convert_montage_to_ras(
montage, subject="sample_ecog", subjects_dir=misc_path / "ecog"
)

raw_ecog.set_montage(None) # clear already found montage
# fake surgical plans from already-found contact locations
targets = dict()
ch_pos = montage.get_positions()["ch_pos"]
for elec in set(
[
"".join([letter for letter in ch if not letter.isdigit()])
for ch in raw_ecog.ch_names
]
):
names = [ch for ch in raw_ecog.ch_names if ch.replace(elec, "").isdigit()]
ch1 = [name for name in names if name.replace(elec, "") == "1"][0]
ch2 = [name for name in names if name.replace(elec, "") == "2"][0]
targets[elec] = (
ch_pos[ch1],
ch_pos[ch2],
) # use second channel so grid counts the right way

mne_gui.locate_ieeg(
raw_ecog.info,
subj_trans_ecog,
CT_aligned_ecog,
subject="sample_ecog",
subjects_dir=misc_path / "ecog",
targets=targets,
)

# %%
# For ECoG, we typically want to account for "brain shift" or shrinking of the
# brain away from the skull/dura due to changes in pressure during the
Expand Down
8 changes: 8 additions & 0 deletions mne_gui_addons/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ def locate_ieeg(
subject=None,
subjects_dir=None,
groups=None,
targets=None,
show=True,
block=False,
verbose=None,
Expand All @@ -51,6 +52,12 @@ def locate_ieeg(
like ``LAMY`` precedes a numeric index like ``7``. If the channels
are formatted improperly, group plotting will work incorrectly.
Group assignments can be adjusted in the GUI.
targets : dict | None
An optional dictionary with group (electrode/grid) names as keys
and a list containing 1) the planned target location for the first
electrode contact and 2) the next contact or planned entry point
(for sEEG). Including only the target location is also an option.
If not provided, automatic contact locations will not be found.
show : bool
Show the GUI if True.
block : bool
Expand All @@ -74,6 +81,7 @@ def locate_ieeg(
subject=subject,
subjects_dir=subjects_dir,
groups=groups,
targets=targets,
show=show,
verbose=verbose,
)
Expand Down
6 changes: 4 additions & 2 deletions mne_gui_addons/_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -489,9 +489,11 @@ def _configure_status_bar(self, hbox=None):
def _update_camera(self, render=False):
"""Update the camera position."""
self._renderer.set_camera(
focalpoint=tuple(self._ras),
reset_camera=False,
focalpoint=tuple(apply_trans(self._scan_ras_mri_t, self._ras)),
distance="auto",
)
if render:
self._renderer._update()

def _on_scroll(self, event):
"""Process mouse scroll wheel event to zoom."""
Expand Down
Loading

0 comments on commit fee40a5

Please sign in to comment.