Skip to content

Commit

Permalink
Merge pull request #753 from markotoplak/hyper-trace-line
Browse files Browse the repository at this point in the history
[ENH] HyperSpectra: show value traces of selected lines
  • Loading branch information
markotoplak authored Oct 4, 2024
2 parents fce710e + 47d18d9 commit d35394f
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 5 deletions.
2 changes: 1 addition & 1 deletion orangecontrib/spectroscopy/widgets/line_geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ def intersect_curves(x, ys, q1, q2):
:param x: x values of curves (they have to be sorted).
:param ys: y values of multiple curves sharing x values.
:param q1: point of the line segment (x, y)
:param q2: point of the line segnemt (x, y)
:param q2: point of the line segment (x, y)
:return:
"""

Expand Down
87 changes: 83 additions & 4 deletions orangecontrib/spectroscopy/widgets/owhyper.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,10 @@
HelpEventDelegate, selection_modifiers, \
ParameterSetter as SpectraParameterSetter

from orangecontrib.spectroscopy.io.util import VisibleImage
from orangecontrib.spectroscopy.io.util import VisibleImage, build_spec_table
from orangecontrib.spectroscopy.widgets.gui import MovableVline, lineEditDecimalOrNone,\
pixels_to_decimals, float_to_str_decimals
from orangecontrib.spectroscopy.widgets.line_geometry import in_polygon
from orangecontrib.spectroscopy.widgets.line_geometry import in_polygon, intersect_line_segments
from orangecontrib.spectroscopy.widgets.utils import \
SelectionGroupMixin, SelectionOutputsMixin

Expand Down Expand Up @@ -556,6 +556,9 @@ def add_zoom_actions(self, menu):

class ImageSelectionMixin:

def __init__(self):
self.selection_distances = None

def add_selection_actions(self, menu):

select_square = QAction(
Expand All @@ -576,6 +579,15 @@ def add_selection_actions(self, menu):
if menu:
menu.addAction(select_polygon)

line = QAction(
"Trace line", self, triggered=self.plot.vb.set_mode_select,
)
line.setShortcuts([Qt.Key_L])
line.setShortcutContext(Qt.WidgetWithChildrenShortcut)
self.addAction(line)
if menu:
menu.addAction(line)

def select_square(self, p1, p2):
""" Select elements within a square drawn by the user.
A selection needs to contain whole pixels """
Expand All @@ -600,6 +612,37 @@ def select_polygon(self, polygon):
inp *= in_polygon(p, polygon)
self.make_selection(inp)

def select_line(self, p1, p2):
# hijacking existing selection functions to do stuff
p1x, p1y = p1.x(), p1.y()
p2x, p2y = p2.x(), p2.y()
if self.data and self.lsx and self.lsy:
# Pixel selection works so that a pixel is selected
# if the drawn line crosses any of its edges.
# An alternative that would ensure that only one pixel in row/column
# was selected is the Bresenham's line algorithm.
shiftx = _shift(self.lsx)
shifty = _shift(self.lsy)
points_edges = [self.data_points + [[shiftx, shifty]],
self.data_points + [[-shiftx, shifty]],
self.data_points + [[-shiftx, -shifty]],
self.data_points + [[shiftx, -shifty]]]
sel = None
for i in range(4):
res = intersect_line_segments(points_edges[i - 1][:, 0], points_edges[i - 1][:, 1],
points_edges[i][:, 0], points_edges[i][:, 1],
p1x, p1y, p2x, p2y)
if sel is None:
sel = res
else:
sel |= res

distances = np.full(self.data_points.shape[0], np.nan)
distances[sel] = np.linalg.norm(self.data_points[sel] - [[p1x, p1y]], axis=1)

modifiers = (False, False, False) # enforce a new selection
self.make_selection(sel, distances=distances, modifiers=modifiers)


class ImageColorLegend(GraphicsWidget):

Expand Down Expand Up @@ -856,9 +899,10 @@ def refresh_img_selection(self):
self.selection_group[self.data_valid_positions]
self.img.setSelection(selected_px)

def make_selection(self, selected):
def make_selection(self, selected, distances=None, modifiers=None):
"""Add selected indices to the selection."""
add_to_group, add_group, remove = selection_modifiers()
add_to_group, add_group, remove = \
selection_modifiers() if modifiers is None else modifiers
if self.data and self.lsx and self.lsy:
if add_to_group: # both keys - need to test it before add_group
selnum = np.max(self.selection_group)
Expand All @@ -872,6 +916,7 @@ def make_selection(self, selected):
if selected is not None:
self.selection_group[selected] = selnum
self.refresh_img_selection()
self.selection_distances = distances # these are not going to be saved for now
self.prepare_settings_for_saving()
self.selection_changed.emit()

Expand Down Expand Up @@ -1197,6 +1242,14 @@ def __init__(self):
self.curveplot.locked_axes_changed.connect(
lambda locked: self.Information.view_locked(shown=locked))

self.traceplot = CurvePlotHyper(self)
self.traceplot.plot.vb.x_padding = 0.005 # pad view so that lines are not hidden
splitter.addWidget(self.traceplot)
self.traceplot.button.hide()
self.traceplot.hide()
self.imageplot.selection_changed.connect(self.draw_trace)
self.imageplot.image_updated.connect(self.draw_trace)

self.line1 = MovableVline(position=self.lowlim, label="", report=self.curveplot)
self.line1.sigMoved.connect(lambda v: setattr(self, "lowlim", v))
self.line2 = MovableVline(position=self.highlim, label="", report=self.curveplot)
Expand Down Expand Up @@ -1293,6 +1346,32 @@ def output_image_selection(self):
_, selected = self.send_selection(self.data, self.imageplot.selection_group)
self.curveplot.set_data(selected if selected else self.data)

def draw_trace(self):
distances = self.imageplot.selection_distances
sel = self.imageplot.selection_group > 0
self.traceplot.set_data(None)
if distances is not None and self.imageplot.data \
and self.imageplot.lsx and self.imageplot.lsy:
distances = distances[sel]
sortidx = np.argsort(distances)
values = self.imageplot.data_values[sel]
x = distances[sortidx]
y = values[sortidx]

# combine xs with the same value
groups, pos, g_count = np.unique(x,
return_index=True,
return_counts=True)
g_sum = np.add.reduceat(y, pos, axis=0)
g_mean = g_sum / g_count[:, None]
x, y = groups, g_mean

traceplot_data = build_spec_table(x, y.T, None)
self.traceplot.set_data(traceplot_data)
self.traceplot.show()
else:
self.traceplot.hide()

def init_attr_values(self, data):
domain = data.domain if data is not None else None
self.feature_value_model.set_domain(domain)
Expand Down

0 comments on commit d35394f

Please sign in to comment.