Skip to content

Commit

Permalink
Merge branch 'main' into BUG-channel-annot-merge
Browse files Browse the repository at this point in the history
  • Loading branch information
nmarkowitz authored Jul 24, 2024
2 parents fb76b85 + f602590 commit aa5ea49
Show file tree
Hide file tree
Showing 3 changed files with 146 additions and 31 deletions.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.5.2
rev: v0.5.4
hooks:
- id: ruff
args: ["--fix"]
Expand Down
161 changes: 131 additions & 30 deletions mne_qt_browser/_pg_figure.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,37 @@ def _get_channel_scaling(widget, ch_type):
return inv_norm


def _calc_chan_type_to_physical(widget, ch_type, units="mm"):
"""Convert data to physical units."""
# Get the ViewBox and its height in pixels
vb = widget.mne.viewbox
height_px = vb.geometry().height()

# Get the view range in data units (here we write V for logical simplicity and
# dimensional analysis but it works for any underlying data unit)
view_range = vb.viewRange()
height_V = view_range[1][1] - view_range[1][0]

# Calculate the pixel-to-data ratio
if height_V == 0:
return 0

# Get the screen DPI
px_per_in = QApplication.primaryScreen().logicalDotsPerInch()

# Convert to inches
height_in = height_px / px_per_in

# Convert pixels to inches
in_per_V = height_in / height_V

# Convert inches to millimeters (or something else, but using mm in the name for
# simplicity)
mm_per_in = dict(mm=25.4, cm=2.54, inch=1.0)[units]
mm_per_V = in_per_V * mm_per_in
return _get_channel_scaling(widget, ch_type) / mm_per_V


def propagate_to_children(method): # noqa: D103
@functools.wraps(method)
def wrapper(*args, **kwargs):
Expand Down Expand Up @@ -1658,7 +1689,9 @@ def __init__(self, mne, ch_type):
QGraphicsLineItem.__init__(self)

self.setZValue(1)
self.setPen(self.mne.mkPen(color="#AA3377", width=5))
pen = self.mne.mkPen(color="#AA3377", width=5)
pen.setCapStyle(Qt.FlatCap)
self.setPen(pen)
self.update_y_position()

def _set_position(self, x, y):
Expand Down Expand Up @@ -1809,34 +1842,83 @@ def __init__(self, main, title="Settings", **kwargs):
self.scroll_sensitivity_slider.setValue(self.mne.scroll_sensitivity)
layout.addRow("horizontal scroll sensitivity", self.scroll_sensitivity_slider)

# Add subgroup box to show channel type scalings
layout.addItem(QSpacerItem(10, 10, QSizePolicy.Minimum, QSizePolicy.Expanding))
ch_scaling_box = QGroupBox("Channel Type Scalings")
ch_scaling_box.setStyleSheet("QGroupBox { font-size: 12pt; }")
ch_scaling_layout = QFormLayout()
self.ch_scaling_spinboxes = {}

# Get all unique channel types and allow scaling
# Get all unique channel types
ordered_types = self.mne.ch_types[self.mne.ch_order]
unique_type_idxs = np.unique(ordered_types, return_index=True)[1]
ch_types_ordered = [ordered_types[idx] for idx in sorted(unique_type_idxs)]
for ch in ch_types_ordered:
if ch in self.mne.unit_scalings.keys():
ch_spinbox = QDoubleSpinBox()
ch_spinbox.setMinimumWidth(100)
ch_spinbox.setRange(0, float("inf"))
ch_spinbox.setDecimals(1)
ch_spinbox.setStepType(QAbstractSpinBox.AdaptiveDecimalStepType)
inv_norm = _get_channel_scaling(self, ch)
ch_spinbox.setValue(inv_norm)
ch_spinbox.valueChanged.connect(
_methpartial(self._update_spinbox_values, ch_type=ch)
)
self.ch_scaling_spinboxes[ch] = ch_spinbox
ch_scaling_layout.addRow(f"{ch} ({self.mne.units[ch]})", ch_spinbox)
ch_types_ordered = [
ordered_types[idx]
for idx in sorted(unique_type_idxs)
if ordered_types[idx] in self.mne.unit_scalings
]

# Grid layout for channel spinboxes and settings
ch_grid_layout = QGridLayout()

# Create dropdown to choose units
self.physical_units_cmbx = QComboBox()
self.physical_units_cmbx.addItems(["/ mm", "/ cm", "/ inch"])
self.physical_units_cmbx.currentIndexChanged.connect(
self._update_sensitivity_spinbox_values
)
current_units = self.physical_units_cmbx.currentText().split()[-1]

ch_scaling_box.setLayout(ch_scaling_layout)
layout.addRow(ch_scaling_box)
# Add subgroup box to show channel type scalings
ch_scroll_box = QGroupBox("Channel Configuration")
ch_scroll_box.setStyleSheet("QGroupBox { font-size: 12pt; }")
self.ch_scaling_spinboxes = {}
# self.ch_scaling_spinbox_labels = {}
self.ch_sensitivity_spinboxes = {}
# self.ch_sensitivity_spinbox_labels = {}
self.ch_label_widgets = {}

ch_grid_layout.addWidget(QLabel("Channel Type"), 0, 0)
ch_grid_layout.addWidget(QLabel("Scaling"), 0, 1)
ch_grid_layout.addWidget(QLabel("Sensitivity"), 0, 2)
grid_row = 1
for ch_type in ch_types_ordered:
self.ch_label_widgets[ch_type] = QLabel(
f"{ch_type} ({self.mne.units[ch_type]})"
)

# Make scaling spinbox first
ch_scale_spinbox = QDoubleSpinBox()
ch_scale_spinbox.setMinimumWidth(100)
ch_scale_spinbox.setRange(0, float("inf"))
ch_scale_spinbox.setDecimals(1)
ch_scale_spinbox.setStepType(QAbstractSpinBox.AdaptiveDecimalStepType)
inv_norm = _get_channel_scaling(self, ch_type)
ch_scale_spinbox.setValue(inv_norm)
ch_scale_spinbox.valueChanged.connect(
_methpartial(self._update_scaling_spinbox_values, ch_type=ch_type)
)
self.ch_scaling_spinboxes[ch_type] = ch_scale_spinbox

# Now make sensitivity spinbox
ch_sens_spinbox = QDoubleSpinBox()
ch_sens_spinbox.setMinimumWidth(100)
ch_sens_spinbox.setRange(0, float("inf"))
ch_sens_spinbox.setDecimals(1)
ch_sens_spinbox.setStepType(QAbstractSpinBox.AdaptiveDecimalStepType)
ch_sens_spinbox.setReadOnly(True)
ch_sens_spinbox.setDisabled(True)
ch_sens_spinbox.setValue(
_calc_chan_type_to_physical(self, ch_type, units=current_units)
)
self.ch_sensitivity_spinboxes[ch_type] = ch_sens_spinbox

# Add these to the layout
ch_grid_layout.addWidget(self.ch_label_widgets[ch_type], grid_row, 0)
ch_grid_layout.addWidget(ch_scale_spinbox, grid_row, 1)
ch_grid_layout.addWidget(ch_sens_spinbox, grid_row, 2)
grid_row += 1

ch_grid_layout.addWidget(self.physical_units_cmbx, grid_row, 2)

ch_scroll_box.setLayout(ch_grid_layout)

layout.addRow(ch_scroll_box)

self.setLayout(layout)
self.show()
Expand All @@ -1860,7 +1942,7 @@ def _value_changed(self, new_value, value_name):
def _toggle_antialiasing(self, _):
self.weakmain()._toggle_antialiasing()

def _update_spinbox_values(self, *args, **kwargs):
def _update_scaling_spinbox_values(self, *args, **kwargs):
"""Update spinbox values. If any args or kwargs do a specific channel update."""
# If new value for a channel given update that channel type and redraw
if len(args) > 0:
Expand All @@ -1870,7 +1952,7 @@ def _update_spinbox_values(self, *args, **kwargs):
# If new_value is 0 then scaling is stuck on 0.
# To get out of 0 set scalings to 1 and then set the new value
if new_value == 0:
self.mne.scalings[ch_type] = 0
self.mne.scalings[ch_type] = 1e-12
else:
self.mne.scalings[ch_type] = 1
self.mne.scalings[ch_type] = new_value / _get_channel_scaling(
Expand All @@ -1885,6 +1967,16 @@ def _update_spinbox_values(self, *args, **kwargs):
for ch_type, spinbox in self.ch_scaling_spinboxes.items():
spinbox.setValue(_get_channel_scaling(self, ch_type))

self._update_sensitivity_spinbox_values()

def _update_sensitivity_spinbox_values(self):
"""Update sensitivity spinbox values."""
current_units = self.physical_units_cmbx.currentText().split()[-1]
for ch_type in self.ch_scaling_spinboxes:
self.ch_sensitivity_spinboxes[ch_type].setValue(
_calc_chan_type_to_physical(self, ch_type, units=current_units)
)


class HelpDialog(_BaseDialog):
"""Shows all keyboard-shortcuts."""
Expand Down Expand Up @@ -3951,7 +4043,7 @@ def _update_scalebar_values(self):

def _update_ch_spinbox_values(self):
if self.mne.fig_settings is not None:
self.mne.fig_settings.update_all_spinboxes()
self.mne.fig_settings._update_scaling_spinbox_values()

def _set_scalebars_visible(self, visible):
for scalebar in self.mne.scalebars.values():
Expand Down Expand Up @@ -3991,7 +4083,7 @@ def scale_all(self, checked=False, *, step):
# Update Scalebars
self._update_scalebar_values()
if self.mne.fig_settings is not None:
self.mne.fig_settings._update_spinbox_values()
self.mne.fig_settings._update_scaling_spinbox_values()

def hscroll(self, step):
"""Scroll horizontally by step."""
Expand Down Expand Up @@ -4111,6 +4203,9 @@ def change_nchan(self, checked=False, *, step):
self.mne.ax_vscroll.update_nchan()
self.mne.plt.setYRange(ymin, ymax, padding=0)

if self.mne.fig_settings is not None:
self.mne.fig_settings._update_sensitivity_spinbox_values()

def _remove_vline(self):
if self.mne.vline is not None:
if self.mne.is_epochs:
Expand Down Expand Up @@ -4800,7 +4895,6 @@ def _set_butterfly(self, butterfly):
self.mne.butterfly = butterfly
self._update_picks()
self._update_data()
self._update_ch_spinbox_values()

if butterfly and self.mne.fig_selection is not None:
self.mne.selection_ypos_dict.clear()
Expand Down Expand Up @@ -4845,6 +4939,8 @@ def _set_butterfly(self, butterfly):

self._draw_traces()

self._update_ch_spinbox_values()

def _toggle_butterfly(self):
if self.mne.instance_type != "ica":
self._set_butterfly(not self.mne.butterfly)
Expand Down Expand Up @@ -5028,7 +5124,7 @@ def _draw_traces(self):
def _get_size(self):
inch_width = self.width() / self.logicalDpiX()
inch_height = self.height() / self.logicalDpiY()

logger.debug(f"Window size: {inch_width:0.1f} x {inch_height:0.1f} inches")
return inch_width, inch_height

def _fake_keypress(self, key, fig=None):
Expand Down Expand Up @@ -5256,6 +5352,11 @@ def closeEvent(self, event):
self.deleteLater()
self._closed = True

def resizeEvent(self, event):
super().resizeEvent(event)
if self.mne.fig_settings is not None:
self.mne.fig_settings._update_sensitivity_spinbox_values()

def _fake_click_on_toolbar_action(self, action_name, wait_after=500):
"""Trigger event associated with action 'action_name' in toolbar."""
for action in self.mne.toolbar.actions():
Expand Down
14 changes: 14 additions & 0 deletions mne_qt_browser/tests/test_pg_specific.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,20 @@ def test_pg_settings_dialog(raw_orig, pg_backend):
)
assert inv_norm == ch_spinbox.value()

ch_scale_spinbox = fig.mne.fig_settings.ch_scaling_spinboxes[ch_type_test]
ch_sens_spinbox = fig.mne.fig_settings.ch_sensitivity_spinboxes[ch_type_test]
scaling_spinbox_value = ch_spinbox.value()
sensitivity_spinbox_value = ch_sens_spinbox.value()
scaling_value = fig.mne.scalings[ch_type_test]
new_scaling_spinbox_value = scaling_spinbox_value * 2
new_expected_sensitivity_spinbox_value = sensitivity_spinbox_value * 2
ch_scale_spinbox.setValue(new_scaling_spinbox_value)
new_scaling_value = fig.mne.scalings[ch_type_test]
assert scaling_value != new_scaling_value
np.testing.assert_allclose(
ch_sens_spinbox.value(), new_expected_sensitivity_spinbox_value, atol=0.1
)


def test_pg_help_dialog(raw_orig, pg_backend):
"""Test Settings Dialog toggle on/off for pyqtgraph-backend."""
Expand Down

0 comments on commit aa5ea49

Please sign in to comment.