Skip to content
4 changes: 4 additions & 0 deletions hexrdgui/calibration/polar_plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,10 @@ def write_image(self, filename='polar_image.npz'):
data[f'border_mask_{name}'] = mask.get_masked_arrays(
self.type,
)
elif mask.highlight:
data[f'highlight_mask_{name}'] = mask.get_masked_arrays(
self.type,
)

keep_detectors = HexrdConfig().azimuthal_lineout_detectors
if (
Expand Down
77 changes: 74 additions & 3 deletions hexrdgui/image_canvas.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from matplotlib.backends.backend_qtagg import FigureCanvas
from matplotlib.figure import Figure
from matplotlib.lines import Line2D
from matplotlib.patches import Circle
from matplotlib.patches import Circle, Polygon
from matplotlib.ticker import AutoLocator, AutoMinorLocator, FuncFormatter

import matplotlib as mpl
Expand Down Expand Up @@ -122,6 +122,10 @@ def setup_connections(self):
HexrdConfig().oscillation_stage_changed.connect(
self.oscillation_stage_changed)
MaskManager().polar_masks_changed.connect(self.polar_masks_changed)
# Update mask highlights without re-running expensive mask logic
MaskManager().mask_highlights_changed.connect(
self.mask_highlights_changed
)
HexrdConfig().overlay_renamed.connect(self.overlay_renamed)
HexrdConfig().azimuthal_options_modified.connect(
self.update_azimuthal_integral_plot)
Expand Down Expand Up @@ -302,8 +306,10 @@ def load_images(self, image_names):

self.raw_view_images_dict = computed_images_dict
self.clear_mask_boundaries()
self.clear_mask_highlights()
for name, axis in self.raw_axes.items():
self.draw_mask_boundaries(axis, name)
self.highlight_masks(axis, name)

# This will call self.draw_idle()
self.show_saturation()
Expand Down Expand Up @@ -394,6 +400,10 @@ def blit_artists(self):
def overlay_artists(self):
return self.blit_artists.setdefault('overlays', {})

@property
def mask_highlight_artists(self):
return self.blit_artists.setdefault('mask_highlights', {})

def remove_all_overlay_artists(self):
self.blit_manager.remove_artists('overlays')
self.blit_manager.artists['overlays'] = {}
Expand Down Expand Up @@ -1238,6 +1248,7 @@ def render_polar(self):
self.axes_images[0].set_data(img)

self.update_mask_boundaries(self.axis)
self.update_mask_highlights(self.axis)

# Get the "tth" vector
angular_grid = self.iviewer.angular_grid
Expand Down Expand Up @@ -1365,6 +1376,7 @@ def finish_show_stereo(self, iviewer):
self.figure.tight_layout()

self.update_mask_boundaries(self.axis)
self.update_mask_highlights(self.axis)

self.draw_stereo_border()
self.update_auto_picked_data()
Expand Down Expand Up @@ -1566,6 +1578,19 @@ def is_stereo_from_polar(self):
self.iviewer.project_from_polar
)

def mask_highlights_changed(self):
if not self.iviewer:
return

if self.mode == ViewType.raw:
self.clear_mask_highlights()
for det_name, ax in self.raw_axes.items():
self.highlight_masks(ax, det_name)
return

if self.mode in (ViewType.polar, ViewType.stereo):
self.update_mask_highlights(self.axis)

def polar_masks_changed(self):
skip = (
not self.iviewer or
Expand All @@ -1576,6 +1601,7 @@ def polar_masks_changed(self):
return

self.update_mask_boundaries(self.axis)
self.update_mask_highlights(self.axis)
self.iviewer.reapply_masks()
img = self.scaled_display_images[0]
self.axes_images[0].set_data(img)
Expand Down Expand Up @@ -2182,11 +2208,23 @@ def clear_mask_boundaries(self):

self._mask_boundary_artists.clear()

def draw_mask_boundaries(self, axis, det=None):
def update_mask_highlights(self, axis):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need a new signal/slot mechanism that, when the user's selection changes, it only triggers highlight artist updates (and doesn't retrigger all the polar mask logic).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would make highlight selections much faster.

# Update is a clear followed by a draw
self.clear_mask_highlights()
self.highlight_masks(axis)

def clear_mask_highlights(self):
self.remove_all_mask_highlight_artists()

def get_mask_verts(self, visible_attr, det=None):
# Create an instrument once that we will re-use
instr = create_view_hedm_instrument()
all_verts = []
for name in MaskManager().visible_boundaries:
options = {
'boundaries': MaskManager().visible_boundaries,
'highlights': MaskManager().visible_highlights
}
for name in options[visible_attr]:
mask = MaskManager().masks[name]
verts = None
if self.mode == ViewType.raw:
Expand Down Expand Up @@ -2251,6 +2289,10 @@ def draw_mask_boundaries(self, axis, det=None):
[np.vstack((x, (np.nan, np.nan))) for x in verts]
))

return all_verts

def draw_mask_boundaries(self, axis, det=None):
all_verts = self.get_mask_verts('boundaries', det)
if not all_verts:
return

Expand All @@ -2264,6 +2306,35 @@ def draw_mask_boundaries(self, axis, det=None):
**kwargs,
)

def highlight_masks(self, axis, det=None):
all_verts = self.get_mask_verts('highlights', det)
if not all_verts:
return

kwargs = {
'facecolor': MaskManager().highlight_color,
'alpha': MaskManager().highlight_opacity,
'edgecolor': 'none',
'fill': True,
}

highlight_artists = self.mask_highlight_artists.setdefault(det or 'default', [])

for vert in all_verts:
polygon = Polygon(vert, **kwargs)
polygon.set_animated(True)
axis.add_patch(polygon)
highlight_artists.append(polygon)

self.blit_manager.update()

def remove_all_mask_highlight_artists(self):
self.blit_manager.remove_artists('mask_highlights')
self.blit_manager.artists['mask_highlights'] = {}

def remove_mask_highlight_artists(self, key):
self.blit_manager.remove_artists('mask_highlights', key)


class PolarXAxisTickLocator(AutoLocator):
"""Subclass the tick locator so we can modify its behavior
Expand Down
2 changes: 1 addition & 1 deletion hexrdgui/image_tab_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,7 @@ def on_motion_notify_event(self, event):
for mask in MaskManager().masks.values():
if (
mask.type == MaskType.threshold or
(not mask.visible and not mask.show_border)
(not mask.visible and not mask.show_border and not mask.highlight)
):
continue

Expand Down
42 changes: 35 additions & 7 deletions hexrdgui/masking/mask_border_style_picker.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,24 @@

class MaskBorderStylePicker(QObject):

def __init__(self, original_color, original_style, original_width, parent=None):
def __init__(
self,
original_color,
original_style,
original_width,
original_highlight,
original_highlight_opacity,
parent=None
):
super().__init__(parent)

loader = UiLoader()
self.ui = loader.load_file('mask_border_style_picker.ui', parent)
self.original_color = original_color
self.original_style = original_style
self.original_width = original_width
self.original_highlight = original_highlight
self.original_highlight_opacity = original_highlight_opacity

self.reset_ui()
self.setup_connections()
Expand All @@ -24,14 +34,18 @@ def reset_ui(self):
self.ui.border_color.setStyleSheet('QPushButton {background-color: %s}' % self.original_color)
self.ui.border_style.setCurrentText(self.original_style)
self.ui.border_size.setValue(self.original_width)
self.ui.highlight_color.setText(self.original_highlight)
self.ui.highlight_color.setStyleSheet('QPushButton {background-color: %s}' % self.original_highlight)
self.ui.opacity.setValue(self.original_highlight_opacity)

def exec(self):
self.ui.adjustSize()
return self.ui.exec()

def setup_connections(self):
self.ui.border_color.clicked.connect(self.pick_color)
self.ui.border_color.clicked.connect(lambda: self.pick_color('border'))
self.ui.button_box.rejected.connect(self.reject)
self.ui.highlight_color.clicked.connect(lambda: self.pick_color('highlight'))

@property
def color(self):
Expand All @@ -45,15 +59,29 @@ def style(self):
def width(self):
return self.ui.border_size.value()

def pick_color(self):
dialog = QColorDialog(QColor(self.original_color), self.ui)
@property
def highlight(self):
return self.ui.highlight_color.text()

@property
def opacity(self):
return self.ui.opacity.value()

def pick_color(self, type):
options = {
'border': self.original_color,
'highlight': self.original_highlight,
'border_ui': self.ui.border_color,
'highlight_ui': self.ui.highlight_color
}
dialog = QColorDialog(QColor(options[type]), self.ui)
if dialog.exec():
color = dialog.selectedColor().name()
self.ui.border_color.setText(color)
self.ui.border_color.setStyleSheet('QPushButton {background-color: %s}' % color)
options[f'{type}_ui'].setText(color)
options[f'{type}_ui'].setStyleSheet('QPushButton {background-color: %s}' % color)

def reject(self):
self.reset_ui()

def result(self):
return self.color, self.style, self.width
return self.color, self.style, self.width, self.highlight, self.opacity
Loading
Loading