Skip to content

Commit

Permalink
Add basic support for montages without raw, montages are ensured on d…
Browse files Browse the repository at this point in the history
…emand for evokeds etc.
  • Loading branch information
teekuningas committed Mar 29, 2024
1 parent 438ed54 commit f3d48d5
Show file tree
Hide file tree
Showing 11 changed files with 77 additions and 28 deletions.
4 changes: 2 additions & 2 deletions meggie/actions/evoked_plot/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ def handler(self, subject, params):
else:
chs = list(get_channels_by_type(info).keys())
if "eeg" in chs:
plot_evoked_topo(evoked, ch_type="eeg")
plot_evoked_topo(subject, evoked, ch_type="eeg")
if "grad" in chs or "mag" in chs:
plot_evoked_topo(evoked, ch_type="meg")
plot_evoked_topo(subject, evoked, ch_type="meg")
except Exception as exc:
exc_messagebox(self.window, exc)
6 changes: 5 additions & 1 deletion meggie/actions/evoked_plot/controller/evoked.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from meggie.utilities.plotting import color_cycle
from meggie.utilities.plotting import create_channel_average_plot
from meggie.utilities.channels import average_to_channel_groups
from meggie.utilities.channels import ensure_montage
from meggie.utilities.plotting import set_figure_title

from meggie.utilities.units import get_unit
Expand Down Expand Up @@ -66,7 +67,7 @@ def plot_fun(ax_idx, ax):
plt.show()


def plot_evoked_topo(evoked, ch_type):
def plot_evoked_topo(subject, evoked, ch_type):
"""Plots evoked time courses arranged as a topography"""
evokeds = []
labels = []
Expand All @@ -88,6 +89,8 @@ def plot_evoked_topo(evoked, ch_type):

evok = evok.copy().drop_channels(dropped_names)

ensure_montage(subject, evok, ch_type)

evokeds.append(evok)
labels.append(key)

Expand All @@ -100,6 +103,7 @@ def plot_evoked_topo(evoked, ch_type):
]

fig, axes = plt.subplots()

mne.viz.plot_evoked_topo(evokeds, color=colors, legend=False, show=False, axes=axes)
axes.legend(handles=lines, loc="upper right")
title = "{0}_{1}".format(evoked.name, ch_type)
Expand Down
2 changes: 1 addition & 1 deletion meggie/actions/raw_measurement_info/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def run(self):
rereferenced = subject.rereferenced

try:
eeg_montage_set = is_montage_set(raw, "eeg")
eeg_montage_set = is_montage_set(raw.info, "eeg")
except Exception:
eeg_montage_set = False

Expand Down
8 changes: 5 additions & 3 deletions meggie/actions/raw_montage/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ def handler(self, subject, params):
@threaded
def montage_fun():
""" """
raw = subject.get_raw()

head_size = params["head_size"]

if params["custom"] is True:
Expand All @@ -38,7 +36,11 @@ def montage_fun():
montage_name, head_size=head_size
)

raw.set_montage(montage)
if subject.has_raw:
raw = subject.get_raw()
raw.set_montage(montage)

subject.save_montage(montage)
subject.save()

montage_fun(do_meanwhile=self.window.update_ui)
Expand Down
2 changes: 1 addition & 1 deletion meggie/actions/raw_montage/configuration.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
"name": "Montage",
"entry": "Montage",
"description": "Apply a montage to the EEG dataset, enabling the creation of topographical plots.",
"tags": ["eeg", "raw"]
"tags": ["eeg"]
}
3 changes: 3 additions & 0 deletions meggie/actions/spectrum_plot/controller/spectrum.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from meggie.utilities.channels import average_to_channel_groups
from meggie.utilities.channels import iterate_topography
from meggie.utilities.channels import filter_info
from meggie.utilities.channels import ensure_montage
from meggie.utilities.plotting import set_figure_title
from meggie.utilities.units import get_power_unit

Expand Down Expand Up @@ -88,6 +89,8 @@ def plot_spectrum_topo(subject, name, log_transformed=True, ch_type="meg"):

info = filter_info(info, picked_channels)

ensure_montage(subject, info, ch_type)

colors = color_cycle(len(data))

def individual_plot(ax, info_idx, names_idx):
Expand Down
3 changes: 3 additions & 0 deletions meggie/actions/tfr_plot/controller/tfr.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from meggie.utilities.plotting import create_channel_average_plot
from meggie.utilities.channels import average_to_channel_groups
from meggie.utilities.channels import ensure_montage
from meggie.utilities.plotting import set_figure_title


Expand Down Expand Up @@ -139,6 +140,8 @@ def plot_tfr_topo(
# apply baseline here to get better vmin and vmax values
tfr.apply_baseline(baseline=bline, mode=mode)

ensure_montage(subject, tfr, ch_type)

values = tfr.data.flatten()
vmax = np.percentile(np.abs(values), 99)
vmin = -vmax
Expand Down
3 changes: 3 additions & 0 deletions meggie/actions/tfr_plot_tse/controller/tfr.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from meggie.utilities.channels import average_to_channel_groups
from meggie.utilities.channels import iterate_topography
from meggie.utilities.channels import filter_info
from meggie.utilities.channels import ensure_montage
from meggie.utilities.units import get_power_unit


Expand Down Expand Up @@ -75,6 +76,8 @@ def plot_tse_topo(

info = filter_info(info, picked_channels)

ensure_montage(subject, info, ch_type)

ch_names = meggie_tfr.ch_names
colors = color_cycle(len(tses))

Expand Down
4 changes: 2 additions & 2 deletions meggie/experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,15 +84,15 @@ def channel_groups(self):

# if channel groups not found, use defaults..
if not channel_groups.get("eeg"):
if self.active_subject:
if self.active_subject and self.active_subject.has_raw:
raw = self.active_subject.get_raw(preload=False)
try:
channel_groups["eeg"] = get_default_channel_groups(raw, "eeg")
except Exception:
pass

if not channel_groups.get("meg"):
if self.active_subject:
if self.active_subject and self.active_subject.has_raw:
raw = self.active_subject.get_raw(preload=False)
try:
channel_groups["meg"] = get_default_channel_groups(raw, "meg")
Expand Down
45 changes: 37 additions & 8 deletions meggie/subject.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,14 +126,15 @@ def get_raw(self, preload=True, verbose="warning"):

def save(self):
"""Saves the data to the existing path."""
try:
filemanager.save_raw(self._raw, self.raw_path)
except Exception:
raise Exception(
"Could not save the raw file. Please ensure "
"that the entire experiment folder has "
"write permissions."
)
if self.has_raw:
try:
filemanager.save_raw(self._raw, self.raw_path)
except Exception:
raise Exception(
"Could not save the raw file. Please ensure "
"that the entire experiment folder has "
"write permissions."
)

def release_memory(self):
"""Releases data from the memory."""
Expand Down Expand Up @@ -191,3 +192,31 @@ def ensure_folders(self):
"Please ensure that the experiment folder "
"has write permissions everywhere."
)

def save_montage(self, montage):

ch_names = montage.ch_names
with open(os.path.join(self.path, "montage_channels.txt"), "w") as f:
for ch_name in ch_names:
f.write(ch_name + "\n")

montage.save(os.path.join(self.path, "montage.fif"), overwrite=True)

def read_montage(self):
montage_path = os.path.join(self.path, "montage.fif")
channels_path = os.path.join(self.path, "montage_channels.txt")
if os.path.exists(montage_path) and os.path.exists(channels_path):
ch_names = []
with open(channels_path, "r") as f:
for line in f:
ch_names.append(line.strip())

montage = mne.channels.read_dig_fif(montage_path)

mapping = {}
for idx, name in enumerate(montage.ch_names):
mapping[name] = ch_names[idx]

montage.rename_channels(mapping)

return montage
25 changes: 15 additions & 10 deletions meggie/utilities/channels.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@
from mne.channels import _divide_to_regions


def is_montage_set(raw, ch_type):
def is_montage_set(info, ch_type):
"""Checks whether channel locations are found in the info for given channel type.
Parameters
----------
raw : mne.io.Raw
Raw containing info which contains channel information.
info : mne.Info
Info containing channel information.
ch_type : str
Should be 'meg' or 'eeg'.
Expand All @@ -24,18 +24,16 @@ def is_montage_set(raw, ch_type):
"""

if ch_type == "meg":
picks = mne.pick_types(raw.info, meg=True, eeg=False)
picks = mne.pick_types(info, meg=True, eeg=False)
else:
picks = mne.pick_types(raw.info, meg=False, eeg=True)
picks = mne.pick_types(info, meg=False, eeg=True)

ch_names = [
ch_name for idx, ch_name in enumerate(raw.info["ch_names"]) if idx in picks
]
ch_names = [ch_name for idx, ch_name in enumerate(info["ch_names"]) if idx in picks]

if not ch_names:
raise Exception("Data does not contain channels of type " + str(ch_type))

info_filt = filter_info(raw.info, ch_names)
info_filt = filter_info(info, ch_names)

# check if there is no montage set..
ch_norms = []
Expand All @@ -47,6 +45,13 @@ def is_montage_set(raw, ch_type):
return True


def ensure_montage(subject, inst, ch_type):
"""Checks if the subject contains a montage that could be used for inst."""
montage = subject.read_montage()
if montage:
inst.set_montage(montage)


def get_default_channel_groups(raw, ch_type):
"""Returns channels grouped by standard locations
(Left-frontal, Right-occipital, etc.). Grouping is done via
Expand Down Expand Up @@ -80,7 +85,7 @@ def get_default_channel_groups(raw, ch_type):
return {}

# check if there is no montage set..
if not is_montage_set(raw, ch_type):
if not is_montage_set(raw.info, ch_type):
return {}

info_filt = filter_info(raw.info, ch_names)
Expand Down

0 comments on commit f3d48d5

Please sign in to comment.