Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
98 commits
Select commit Hold shift + click to select a range
b3e2ed3
Add basic dock
GuillaumeFavelier Feb 11, 2021
485caf0
Move time controller
GuillaumeFavelier Feb 11, 2021
bedc211
Update toggle_interface
GuillaumeFavelier Feb 11, 2021
921e065
Move speed controller
GuillaumeFavelier Feb 11, 2021
4050acd
Rename controller to widget
GuillaumeFavelier Feb 11, 2021
2d5d6c4
Rename
GuillaumeFavelier Feb 11, 2021
8226ed9
Add renderer selector
GuillaumeFavelier Feb 11, 2021
d7258ae
Move smoothing and clim widgets
GuillaumeFavelier Feb 12, 2021
c5ab551
Fix UpdateColorbarScale
GuillaumeFavelier Feb 12, 2021
d2f139a
Fix restore_user_scaling
GuillaumeFavelier Feb 12, 2021
c9f25d9
Improve select_renderer
GuillaumeFavelier Feb 12, 2021
dc98f64
Migrate orientation widget
GuillaumeFavelier Feb 12, 2021
6feefdd
Fix time actor
GuillaumeFavelier Feb 12, 2021
3df2bee
Refactor
GuillaumeFavelier Feb 15, 2021
055cb72
Make the time widget a slider
GuillaumeFavelier Feb 15, 2021
bac0bf5
Fix test_layered_mesh
GuillaumeFavelier Feb 15, 2021
56100c3
Fix test_brain_time_viewer
GuillaumeFavelier Feb 15, 2021
c3fcbb8
Fix _clean
GuillaumeFavelier Feb 15, 2021
fd03c18
Mitigate pain of test_brain_screenshot
GuillaumeFavelier Feb 16, 2021
58bc8eb
Remove cruft
GuillaumeFavelier Feb 16, 2021
560ac60
Fix test_brain_traces
GuillaumeFavelier Feb 16, 2021
8390fe7
Fix test_brain_linkviewer
GuillaumeFavelier Feb 16, 2021
5e387f7
Simplify diff
GuillaumeFavelier Feb 16, 2021
0badea4
Patch notebook for now
GuillaumeFavelier Feb 16, 2021
dcbba5c
Simplify diff
GuillaumeFavelier Feb 16, 2021
342df79
Remove cruft
GuillaumeFavelier Feb 16, 2021
7ab4047
Refactor
GuillaumeFavelier Feb 16, 2021
c0108a0
Use combo_box.currentTextChanged
GuillaumeFavelier Feb 16, 2021
4e601fc
Fix test_notebook
GuillaumeFavelier Feb 16, 2021
da37eef
Refactor notebook
GuillaumeFavelier Feb 22, 2021
bf7e878
Fix widgets
GuillaumeFavelier Feb 22, 2021
bbd209a
Refactor
GuillaumeFavelier Feb 22, 2021
01ba379
Refactor colormap widget
GuillaumeFavelier Feb 22, 2021
3b510de
Refactor
GuillaumeFavelier Feb 22, 2021
9720302
Turn the renderer widget into a QComboBox
GuillaumeFavelier Feb 22, 2021
9b77d4f
Refactor label parameters
GuillaumeFavelier Feb 22, 2021
ccebb51
Add spacer before help button
GuillaumeFavelier Feb 22, 2021
775b24c
Update tests
GuillaumeFavelier Feb 22, 2021
31c65d1
Refactor
GuillaumeFavelier Feb 22, 2021
31b0698
Merge branch 'main' into mnt/interface
GuillaumeFavelier Feb 22, 2021
876b2c5
Move Restore from tool_bar to Colormap group
GuillaumeFavelier Feb 23, 2021
77670d4
Update test
GuillaumeFavelier Feb 23, 2021
7f666d4
Refactor layout management
GuillaumeFavelier Feb 23, 2021
f13381d
Change playback speed default
GuillaumeFavelier Feb 23, 2021
74263b6
Add compact parameter
GuillaumeFavelier Feb 23, 2021
6f866f5
Disband the time group
GuillaumeFavelier Feb 23, 2021
f60c685
Rework alignment
GuillaumeFavelier Feb 23, 2021
d6a8ea0
Tweak UI
GuillaumeFavelier Feb 23, 2021
3338810
Rename
GuillaumeFavelier Feb 23, 2021
659579c
Add time group
GuillaumeFavelier Feb 23, 2021
13a3a43
Update notebook branch
GuillaumeFavelier Feb 23, 2021
2a542c9
Improve playback
GuillaumeFavelier Feb 23, 2021
078f37a
Rename
GuillaumeFavelier Feb 23, 2021
40c0574
Merge branch 'main' into mnt/interface
GuillaumeFavelier Feb 23, 2021
097a410
Fix
GuillaumeFavelier Feb 23, 2021
fd3ed47
Fix
GuillaumeFavelier Feb 24, 2021
ec269e1
Add QFloatSlider
GuillaumeFavelier Feb 24, 2021
2cd038c
Refactor
GuillaumeFavelier Feb 24, 2021
13e90cd
make pydocstyle
GuillaumeFavelier Feb 24, 2021
0780d7f
Improve Qt isolation
GuillaumeFavelier Feb 24, 2021
4df2392
Move labels at the top
GuillaumeFavelier Feb 24, 2021
14a7a39
Fix
GuillaumeFavelier Feb 24, 2021
a0b1116
Refactor
GuillaumeFavelier Feb 24, 2021
ca5760f
Update color limits group
GuillaumeFavelier Feb 24, 2021
4f26de0
Update color limits group
GuillaumeFavelier Feb 24, 2021
6b80049
Fix
GuillaumeFavelier Feb 24, 2021
656bba6
Fix toggle_interface
GuillaumeFavelier Feb 24, 2021
c52a386
Fix
GuillaumeFavelier Feb 24, 2021
0f79841
Fix
GuillaumeFavelier Feb 24, 2021
1983ca8
Fix
GuillaumeFavelier Feb 24, 2021
5d0bd4e
Refactor
GuillaumeFavelier Feb 25, 2021
89b773f
Add _AbstractDock
GuillaumeFavelier Feb 25, 2021
18811bc
Rename
GuillaumeFavelier Feb 25, 2021
f14f986
Rename
GuillaumeFavelier Feb 25, 2021
6a36e0d
Add license
GuillaumeFavelier Feb 25, 2021
b2ea9d0
Refactor
GuillaumeFavelier Feb 25, 2021
e0f7a31
Rename
GuillaumeFavelier Feb 25, 2021
af42bb1
Merge branch 'main' into mnt/interface
GuillaumeFavelier Feb 25, 2021
fb8e1ac
Fix
GuillaumeFavelier Feb 25, 2021
c89d494
Refactor
GuillaumeFavelier Feb 25, 2021
3368e51
Remove cruft
GuillaumeFavelier Feb 25, 2021
6f874b8
Fix
GuillaumeFavelier Feb 25, 2021
661f212
Fix [skip azp][skip github]
GuillaumeFavelier Feb 25, 2021
82fb9ae
Merge branch 'main' into mnt/interface
GuillaumeFavelier Feb 26, 2021
4029cd4
Move builtin
GuillaumeFavelier Feb 26, 2021
4228bd2
Refactor
GuillaumeFavelier Feb 26, 2021
2f5cc87
Fix
GuillaumeFavelier Feb 26, 2021
d47018e
Refactor
GuillaumeFavelier Feb 26, 2021
332152a
MAINT: Refactor
larsoner Feb 26, 2021
2f0e24f
FIX: Missed one
larsoner Feb 26, 2021
1ab7ea0
FIX: Scrollin
larsoner Feb 26, 2021
e5121eb
FIX: Notebook
larsoner Feb 26, 2021
81856dc
Fix
GuillaumeFavelier Mar 1, 2021
a2d3096
Do not use _qt_screenshot
GuillaumeFavelier Mar 1, 2021
8d4d00a
Merge branch 'main' into mnt/interface
GuillaumeFavelier Mar 1, 2021
2df7f6c
Merge remote-tracking branch 'upstream/main' into mnt/interface
larsoner Mar 1, 2021
90237d2
FIX: Restore
larsoner Mar 1, 2021
ca8a555
Fix
GuillaumeFavelier Mar 1, 2021
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 .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ omit =
exclude_lines =
pragma: no cover
if __name__ == .__main__.:
@abstractmethod
@abstractclassmethod
742 changes: 372 additions & 370 deletions mne/viz/_brain/_brain.py

Large diffs are not rendered by default.

50 changes: 25 additions & 25 deletions mne/viz/_brain/_linkviewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,30 +26,31 @@ def __init__(self, brains, time=True, camera=False, colorbar=True,

if time:
# link time sliders
self.link_sliders(
self.link_widgets(
name="time",
callback=self.set_time_point,
event_type="always"
signal_type="valueChanged",
)

# link playback speed sliders
self.link_sliders(
self.link_widgets(
name="playback_speed",
callback=self.set_playback_speed,
event_type="always"
signal_type="valueChanged",
)

# link toggle to start/pause playback
for brain in self.brains:
brain.actions["play"].triggered.disconnect()
brain.actions["play"].triggered.connect(
self.toggle_playback)
self.link_widgets(
name="play",
callback=self.toggle_playback,
signal_type="triggered",
actions=True,
)

# link time course canvas
def _time_func(*args, **kwargs):
for brain in self.brains:
brain.callbacks["time"](*args, **kwargs)

for brain in self.brains:
if brain.show_traces:
brain.mpl_canvas.time_func = _time_func
Expand Down Expand Up @@ -95,13 +96,12 @@ def _func_remove(*args, **kwargs):
brain.callbacks["fmin"](fmin)
brain.callbacks["fmid"](fmid)
brain.callbacks["fmax"](fmax)

for slider_name in ('fmin', 'fmid', 'fmax'):
func = getattr(self, "set_" + slider_name)
self.link_sliders(
name=slider_name,
for name in ('fmin', 'fmid', 'fmax'):
func = getattr(self, "set_" + name)
self.link_widgets(
name=name,
callback=func,
event_type="always"
signal_type="valueChanged"
)

def set_fmin(self, value):
Expand All @@ -125,22 +125,22 @@ def set_playback_speed(self, value):
brain.callbacks["playback_speed"](value, update_widget=True)

def toggle_playback(self):
value = self.leader.callbacks["time"].slider_rep.GetValue()
value = self.leader.callbacks["time"].widget.get_value()
# synchronize starting points before playback
self.set_time_point(value)
for brain in self.brains:
brain.toggle_playback()

def link_sliders(self, name, callback, event_type):
from ..backends._pyvista import _update_slider_callback
def link_widgets(self, name, callback, signal_type, actions=False):
for brain in self.brains:
slider = brain.sliders[name]
if slider is not None:
_update_slider_callback(
slider=slider,
callback=callback,
event_type=event_type
)
if actions:
widget = brain._renderer.actions[name]
else:
widget = brain.widgets[name].widget
if widget is not None:
signal = getattr(widget, signal_type)
signal.disconnect()
signal.connect(callback)

def link_cameras(self):
from ..backends._pyvista import _add_camera_callback
Expand Down
206 changes: 94 additions & 112 deletions mne/viz/_brain/callback.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,188 +6,170 @@
import time


class IntSlider(object):
"""Class to set a integer slider."""

def __init__(self, plotter=None, callback=None, first_call=True):
self.plotter = plotter
self.callback = callback
self.slider_rep = None
self.first_call = first_call
self._first_time = True

def __call__(self, value):
"""Round the label of the slider."""
idx = int(round(value))
if self.slider_rep is not None:
self.slider_rep.SetValue(idx)
self.plotter.update()
if not self._first_time or all([self._first_time, self.first_call]):
self.callback(idx)
if self._first_time:
self._first_time = False


class TimeSlider(object):
"""Class to update the time slider."""

def __init__(self, plotter=None, brain=None, callback=None,
first_call=True):
self.plotter = plotter
class Widget(object):
"""Helper class to interface with widgets."""

def __init__(self, widget, notebook=False):
self.widget = widget
self.notebook = notebook

def set_value(self, value):
"""Set the widget value."""
if self.notebook:
self.widget.value = value
else:
if hasattr(self.widget, "setValue"):
self.widget.setValue(value)
elif hasattr(self.widget, "setCurrentText"):
self.widget.setCurrentText(value)
elif hasattr(self.widget, "setText"):
self.widget.setText(value)

def get_value(self):
"""Get the widget value."""
if self.notebook:
return self.widget.value
else:
if hasattr(self.widget, "value"):
return self.widget.value()
elif hasattr(self.widget, "currentText"):
return self.widget.currentText()
elif hasattr(self.widget, "text"):
return self.widget.text()


class TimeCallBack(object):
"""Callback to update the time."""

def __init__(self, brain=None, callback=None):
self.brain = brain
self.callback = callback
self.slider_rep = None
self.first_call = first_call
self._first_time = True
self.time_label = None
self.widget = None
self.label = None
if self.brain is not None and callable(self.brain._data['time_label']):
self.time_label = self.brain._data['time_label']
else:
self.time_label = None

def __call__(self, value, update_widget=False, time_as_index=True):
"""Update the time slider."""
value = float(value)
if not time_as_index:
value = self.brain._to_time_index(value)
if not self._first_time or all([self._first_time, self.first_call]):
self.brain.set_time_point(value)
self.brain.set_time_point(value)
if self.label is not None:
current_time = self.brain._current_time
self.label.set_value(f"{current_time: .3f}")
if self.callback is not None:
self.callback()
current_time = self.brain._current_time
if self.slider_rep is not None:
if self.time_label is not None:
current_time = self.time_label(current_time)
self.slider_rep.SetTitleText(current_time)
if update_widget:
self.slider_rep.SetValue(value)
self.plotter.update()
if self._first_time:
self._first_time = False
if self.widget is not None and update_widget:
self.widget.set_value(int(value))


class UpdateColorbarScale(object):
"""Class to update the values of the colorbar sliders."""

def __init__(self, plotter=None, brain=None):
self.plotter = plotter
def __init__(self, brain=None):
self.brain = brain
self.keys = ('fmin', 'fmid', 'fmax')
self.reps = {key: None for key in self.keys}
self.slider_rep = None
self._first_time = True
self.widget = None
self.widgets = {key: None for key in self.brain.keys}

def __call__(self, value):
"""Update the colorbar sliders."""
if self._first_time:
self._first_time = False
return
self.brain._update_fscale(value)
for key in self.keys:
if self.reps[key] is not None:
self.reps[key].SetValue(self.brain._data[key])
if self.slider_rep is not None:
self.slider_rep.SetValue(1.0)
self.plotter.update()
for key in self.brain.keys:
if self.widgets[key] is not None:
self.widgets[key].set_value(self.brain._data[key])
if self.widget is not None:
self.widget.set_value(1.0)


class BumpColorbarPoints(object):
"""Class that ensure constraints over the colorbar points."""

def __init__(self, plotter=None, brain=None, name=None):
self.plotter = plotter
def __init__(self, brain=None, name=None):
self.brain = brain
self.name = name
self.callback = {
"fmin": lambda fmin: brain.update_lut(fmin=fmin),
"fmid": lambda fmid: brain.update_lut(fmid=fmid),
"fmax": lambda fmax: brain.update_lut(fmax=fmax),
}
self.keys = ('fmin', 'fmid', 'fmax')
self.reps = {key: None for key in self.keys}
self.widgets = {key: None for key in self.brain.keys}
self.last_update = time.time()
self._first_time = True

def __call__(self, value):
"""Update the colorbar sliders."""
if self._first_time:
self._first_time = False
return
vals = {key: self.brain._data[key] for key in self.keys}
if self.name == "fmin" and self.reps["fmin"] is not None:
vals = {key: self.brain._data[key] for key in self.brain.keys}
if self.name == "fmin" and self.widgets["fmin"] is not None:
if vals['fmax'] < value:
vals['fmax'] = value
self.reps['fmax'].SetValue(value)
self.widgets['fmax'].set_value(value)
if vals['fmid'] < value:
vals['fmid'] = value
self.reps['fmid'].SetValue(value)
self.reps['fmin'].SetValue(value)
elif self.name == "fmid" and self.reps['fmid'] is not None:
self.widgets['fmid'].set_value(value)
self.widgets['fmin'].set_value(value)
elif self.name == "fmid" and self.widgets['fmid'] is not None:
if vals['fmin'] > value:
vals['fmin'] = value
self.reps['fmin'].SetValue(value)
self.widgets['fmin'].set_value(value)
if vals['fmax'] < value:
vals['fmax'] = value
self.reps['fmax'].SetValue(value)
self.reps['fmid'].SetValue(value)
elif self.name == "fmax" and self.reps['fmax'] is not None:
self.widgets['fmax'].set_value(value)
self.widgets['fmid'].set_value(value)
elif self.name == "fmax" and self.widgets['fmax'] is not None:
if vals['fmin'] > value:
vals['fmin'] = value
self.reps['fmin'].SetValue(value)
self.widgets['fmin'].set_value(value)
if vals['fmid'] > value:
vals['fmid'] = value
self.reps['fmid'].SetValue(value)
self.reps['fmax'].SetValue(value)
self.widgets['fmid'].set_value(value)
self.widgets['fmax'].set_value(value)
self.brain.widgets[f'entry_{self.name}'].set_value(str(value))
self.brain.update_lut(**vals)
if time.time() > self.last_update + 1. / 60.:
self.callback[self.name](value)
self.last_update = time.time()
self.plotter.update()


class ShowView(object):
"""Class that selects the correct view."""

def __init__(self, plotter=None, brain=None, orientation=None,
row=None, col=None, hemi=None):
self.plotter = plotter
def __init__(self, brain=None, data=None):
self.brain = brain
self.orientation = orientation
self.short_orientation = [s[:3] for s in orientation]
self.row = row
self.col = col
self.hemi = hemi
self.slider_rep = None
self.data = data
self.widget = None

def __call__(self, value, update_widget=False):
"""Update the view."""
self.brain.show_view(value, row=self.row, col=self.col,
hemi=self.hemi)
if update_widget:
if len(value) > 3:
idx = self.orientation.index(value)
else:
idx = self.short_orientation.index(value)
if self.slider_rep is not None:
self.slider_rep.SetValue(idx)
self.slider_rep.SetTitleText(self.orientation[idx])
self.plotter.update()


class SmartSlider(object):
if "renderer" in self.brain.widgets:
idx = self.brain.widgets["renderer"].get_value()
else:
idx = 0
idx = int(idx)
if self.data[idx] is not None:
self.brain.show_view(
value,
row=self.data[idx]['row'],
col=self.data[idx]['col'],
hemi=self.data[idx]['hemi'],
)
if update_widget and self.widget is not None:
self.widget.set_value(value)


class SmartCallBack(object):
"""Class to manage smart slider.

It stores it's own slider representation for efficiency
and uses it when necessary.
"""

def __init__(self, plotter=None, callback=None):
self.plotter = plotter
def __init__(self, callback=None):
self.callback = callback
self.slider_rep = None
self.widget = None

def __call__(self, value, update_widget=False):
"""Update the value."""
self.callback(value)
if update_widget:
if self.slider_rep is not None:
self.slider_rep.SetValue(value)
self.plotter.update()
if self.widget is not None and update_widget:
self.widget.set_value(value)
Loading