Skip to content

Commit

Permalink
feat(interactive.kspace): add circle ROI to ktool
Browse files Browse the repository at this point in the history
Added a button to the visualization tab which creates a circle ROI. The position and radius can be edited by right-clicking on the roi.
  • Loading branch information
kmnhan committed Nov 4, 2024
1 parent c037de1 commit 304e1a5
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 9 deletions.
121 changes: 115 additions & 6 deletions src/erlab/interactive/kspace.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Interactive momentum conversion tool."""

from __future__ import annotations

__all__ = ["ktool"]

import os
Expand All @@ -11,7 +13,7 @@
import pyqtgraph as pg
import varname
import xarray as xr
from qtpy import QtGui, QtWidgets, uic
from qtpy import QtCore, QtGui, QtWidgets, uic

import erlab.analysis
from erlab.interactive.colors import (
Expand All @@ -24,6 +26,102 @@
from erlab.plotting.bz import get_bz_edge


class _CircleROIControlWidget(QtWidgets.QWidget):
def __init__(self, roi: _MovableCircleROI) -> None:
super().__init__()
self.setGeometry(QtCore.QRect(0, 640, 242, 182))
self._roi = roi

Check warning on line 33 in src/erlab/interactive/kspace.py

View check run for this annotation

Codecov / codecov/patch

src/erlab/interactive/kspace.py#L31-L33

Added lines #L31 - L33 were not covered by tests

layout = QtWidgets.QFormLayout(self)
self.setLayout(layout)

Check warning on line 36 in src/erlab/interactive/kspace.py

View check run for this annotation

Codecov / codecov/patch

src/erlab/interactive/kspace.py#L35-L36

Added lines #L35 - L36 were not covered by tests

self.x_spin = pg.SpinBox(dec=True, compactHeight=False)
self.y_spin = pg.SpinBox(dec=True, compactHeight=False)
self.r_spin = pg.SpinBox(dec=True, compactHeight=False)
self.x_spin.sigValueChanged.connect(self.update_roi)
self.y_spin.sigValueChanged.connect(self.update_roi)
self.r_spin.sigValueChanged.connect(self.update_roi)

Check warning on line 43 in src/erlab/interactive/kspace.py

View check run for this annotation

Codecov / codecov/patch

src/erlab/interactive/kspace.py#L38-L43

Added lines #L38 - L43 were not covered by tests

layout.addRow("X", self.x_spin)
layout.addRow("Y", self.y_spin)
layout.addRow("Radius", self.r_spin)

Check warning on line 47 in src/erlab/interactive/kspace.py

View check run for this annotation

Codecov / codecov/patch

src/erlab/interactive/kspace.py#L45-L47

Added lines #L45 - L47 were not covered by tests

self._roi.sigRegionChanged.connect(self.update_spins)

Check warning on line 49 in src/erlab/interactive/kspace.py

View check run for this annotation

Codecov / codecov/patch

src/erlab/interactive/kspace.py#L49

Added line #L49 was not covered by tests

@QtCore.Slot()
def update_roi(self) -> None:
self._roi.blockSignals(True)
self._roi.set_position(

Check warning on line 54 in src/erlab/interactive/kspace.py

View check run for this annotation

Codecov / codecov/patch

src/erlab/interactive/kspace.py#L53-L54

Added lines #L53 - L54 were not covered by tests
(self.x_spin.value(), self.y_spin.value()), self.r_spin.value()
)
self._roi.blockSignals(False)

Check warning on line 57 in src/erlab/interactive/kspace.py

View check run for this annotation

Codecov / codecov/patch

src/erlab/interactive/kspace.py#L57

Added line #L57 was not covered by tests

@QtCore.Slot()
def update_spins(self) -> None:
x, y, r = self._roi.get_position()
self.x_spin.blockSignals(True)
self.y_spin.blockSignals(True)
self.r_spin.blockSignals(True)
self.x_spin.setValue(x)
self.y_spin.setValue(y)
self.r_spin.setValue(r)
self.x_spin.blockSignals(False)
self.y_spin.blockSignals(False)
self.r_spin.blockSignals(False)

Check warning on line 70 in src/erlab/interactive/kspace.py

View check run for this annotation

Codecov / codecov/patch

src/erlab/interactive/kspace.py#L61-L70

Added lines #L61 - L70 were not covered by tests

def setVisible(self, visible: bool) -> None:
super().setVisible(visible)
if visible:
self.update_spins()

Check warning on line 75 in src/erlab/interactive/kspace.py

View check run for this annotation

Codecov / codecov/patch

src/erlab/interactive/kspace.py#L73-L75

Added lines #L73 - L75 were not covered by tests


class _MovableCircleROI(pg.CircleROI):
"""Circle ROI with a menu to control position and radius."""

def __init__(self, pos, size=None, radius=None, **args):
args.setdefault("removable", True)
super().__init__(pos, size, radius, **args)

def getMenu(self):
if self.menu is None:
self.menu = QtWidgets.QMenu()
self.menu.setTitle("ROI")
if self.removable:
remAct = QtGui.QAction("Remove Circle", self.menu)
remAct.triggered.connect(self.removeClicked)
self.menu.addAction(remAct)
self.menu.remAct = remAct
self._pos_menu = self.menu.addMenu("Edit Circle")
ctrlAct = QtWidgets.QWidgetAction(self._pos_menu)
ctrlAct.setDefaultWidget(_CircleROIControlWidget(self))
self._pos_menu.addAction(ctrlAct)

Check warning on line 97 in src/erlab/interactive/kspace.py

View check run for this annotation

Codecov / codecov/patch

src/erlab/interactive/kspace.py#L86-L97

Added lines #L86 - L97 were not covered by tests

return self.menu

Check warning on line 99 in src/erlab/interactive/kspace.py

View check run for this annotation

Codecov / codecov/patch

src/erlab/interactive/kspace.py#L99

Added line #L99 was not covered by tests

def radius(self) -> float:
"""Radius of the circle."""
return float(self.size()[0] / 2)

Check warning on line 103 in src/erlab/interactive/kspace.py

View check run for this annotation

Codecov / codecov/patch

src/erlab/interactive/kspace.py#L103

Added line #L103 was not covered by tests

def center(self) -> tuple[float, float]:
"""Center of the circle."""
x, y = self.pos()
r = self.radius()
return x + r, y + r

Check warning on line 109 in src/erlab/interactive/kspace.py

View check run for this annotation

Codecov / codecov/patch

src/erlab/interactive/kspace.py#L107-L109

Added lines #L107 - L109 were not covered by tests

def get_position(self) -> tuple[float, float, float]:
"""Return the center and radius of the circle."""
return (*self.center(), self.radius())

Check warning on line 113 in src/erlab/interactive/kspace.py

View check run for this annotation

Codecov / codecov/patch

src/erlab/interactive/kspace.py#L113

Added line #L113 was not covered by tests

def set_position(self, center, radius: float | None = None) -> None:
"""Set the center and radius of the circle."""
if radius is None:
radius = self.radius()

Check warning on line 118 in src/erlab/interactive/kspace.py

View check run for this annotation

Codecov / codecov/patch

src/erlab/interactive/kspace.py#L117-L118

Added lines #L117 - L118 were not covered by tests
else:
diameter = 2 * radius
self.setSize((diameter, diameter), update=False)
self.setPos(center[0] - radius, center[1] - radius)

Check warning on line 122 in src/erlab/interactive/kspace.py

View check run for this annotation

Codecov / codecov/patch

src/erlab/interactive/kspace.py#L120-L122

Added lines #L120 - L122 were not covered by tests


class KspaceToolGUI(
*uic.loadUiType(os.path.join(os.path.dirname(__file__), "ktool.ui")) # type: ignore[misc]
):
Expand All @@ -44,16 +142,12 @@ def __init__(self) -> None:
plot.addItem(self.images[i])
plot.showGrid(x=True, y=True, alpha=0.5)

roi = pg.CircleROI([-0.3, -0.3], radius=0.3)
self.plotitems[1].addItem(roi)

# Set up colormap controls
self.cmap_combo.setDefaultCmap("terrain")
self.cmap_combo.setDefaultCmap("ColdWarm")
self.cmap_combo.textActivated.connect(self.update_cmap)
self.gamma_widget.setValue(0.5)
self.gamma_widget.valueChanged.connect(self.update_cmap)
self.invert_check.stateChanged.connect(self.update_cmap)
self.invert_check.setChecked(True)
self.contrast_check.stateChanged.connect(self.update_cmap)
self.update_cmap()

Expand All @@ -75,6 +169,21 @@ def __init__(self) -> None:
lambda: self.plotitems[0].setVisible(self.angle_plot_check.isChecked())
)

self._roi_list: list[_MovableCircleROI] = []
self.add_circle_btn.clicked.connect(self._add_circle)

@QtCore.Slot()
def _add_circle(self) -> None:
roi = _MovableCircleROI([-0.3, -0.3], radius=0.3, removable=True)
self.plotitems[1].addItem(roi)
self._roi_list.append(roi)

def _remove_roi():
self.plotitems[1].removeItem(roi)
self._roi_list.remove(roi)

Check warning on line 183 in src/erlab/interactive/kspace.py

View check run for this annotation

Codecov / codecov/patch

src/erlab/interactive/kspace.py#L182-L183

Added lines #L182 - L183 were not covered by tests

roi.sigRemoveRequested.connect(_remove_roi)

def update_cmap(self) -> None:
name = self.cmap_combo.currentText()
if name == self.cmap_combo.LOAD_ALL_TEXT:
Expand Down
7 changes: 7 additions & 0 deletions src/erlab/interactive/ktool.ui
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,13 @@
</layout>
</widget>
</item>
<item>
<widget class="QPushButton" name="add_circle_btn">
<property name="text">
<string>Add Circle ROI</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
Expand Down
4 changes: 1 addition & 3 deletions tests/interactive/test_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ def test_goldtool(qtbot, gold):
with qtbot.waitExposed(win):
win.show()
win.activateWindow()
win.raise_()
win.params_edge.widgets["# CPU"].setValue(1)
win.params_edge.widgets["Fast"].setChecked(True)

Expand Down Expand Up @@ -45,7 +44,6 @@ def test_dtool(qtbot):
with qtbot.waitExposed(win):
win.show()
win.activateWindow()
win.raise_()

win.tab_widget.setCurrentIndex(0)
win.interp_group.setChecked(False)
Expand Down Expand Up @@ -77,7 +75,6 @@ def test_ktool(qtbot, anglemap):
with qtbot.waitExposed(win):
win.show()
win.activateWindow()
win.raise_()

win._offset_spins["delta"].setValue(30.0)
win._offset_spins["xi"].setValue(20.0)
Expand All @@ -88,6 +85,7 @@ def test_ktool(qtbot, anglemap):
== """anglemap.kspace.offsets = {"delta": 30.0, "xi": 20.0, "beta": 10.0}
anglemap_kconv = anglemap.kspace.convert()"""
)
win.add_circle_btn.click()


def test_curvefittingtool(qtbot):
Expand Down

0 comments on commit 304e1a5

Please sign in to comment.