Skip to content

Commit f9824a8

Browse files
authored
Merge pull request #1870 from HEXRD/mask-manager-dialog
Mask manager dialog
2 parents a8a7d47 + d950beb commit f9824a8

File tree

8 files changed

+348
-125
lines changed

8 files changed

+348
-125
lines changed

hexrdgui/calibration/polar_plot.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,10 @@ def write_image(self, filename='polar_image.npz'):
177177
data[f'border_mask_{name}'] = mask.get_masked_arrays(
178178
self.type,
179179
)
180+
elif mask.highlight:
181+
data[f'highlight_mask_{name}'] = mask.get_masked_arrays(
182+
self.type,
183+
)
180184

181185
keep_detectors = HexrdConfig().azimuthal_lineout_detectors
182186
if (

hexrdgui/image_canvas.py

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from matplotlib.backends.backend_qtagg import FigureCanvas
1111
from matplotlib.figure import Figure
1212
from matplotlib.lines import Line2D
13-
from matplotlib.patches import Circle
13+
from matplotlib.patches import Circle, Polygon
1414
from matplotlib.ticker import AutoLocator, AutoMinorLocator, FuncFormatter
1515

1616
import matplotlib as mpl
@@ -122,6 +122,10 @@ def setup_connections(self):
122122
HexrdConfig().oscillation_stage_changed.connect(
123123
self.oscillation_stage_changed)
124124
MaskManager().polar_masks_changed.connect(self.polar_masks_changed)
125+
# Update mask highlights without re-running expensive mask logic
126+
MaskManager().mask_highlights_changed.connect(
127+
self.mask_highlights_changed
128+
)
125129
HexrdConfig().overlay_renamed.connect(self.overlay_renamed)
126130
HexrdConfig().azimuthal_options_modified.connect(
127131
self.update_azimuthal_integral_plot)
@@ -302,8 +306,10 @@ def load_images(self, image_names):
302306

303307
self.raw_view_images_dict = computed_images_dict
304308
self.clear_mask_boundaries()
309+
self.clear_mask_highlights()
305310
for name, axis in self.raw_axes.items():
306311
self.draw_mask_boundaries(axis, name)
312+
self.highlight_masks(axis, name)
307313

308314
# This will call self.draw_idle()
309315
self.show_saturation()
@@ -394,6 +400,10 @@ def blit_artists(self):
394400
def overlay_artists(self):
395401
return self.blit_artists.setdefault('overlays', {})
396402

403+
@property
404+
def mask_highlight_artists(self):
405+
return self.blit_artists.setdefault('mask_highlights', {})
406+
397407
def remove_all_overlay_artists(self):
398408
self.blit_manager.remove_artists('overlays')
399409
self.blit_manager.artists['overlays'] = {}
@@ -1238,6 +1248,7 @@ def render_polar(self):
12381248
self.axes_images[0].set_data(img)
12391249

12401250
self.update_mask_boundaries(self.axis)
1251+
self.update_mask_highlights(self.axis)
12411252

12421253
# Get the "tth" vector
12431254
angular_grid = self.iviewer.angular_grid
@@ -1365,6 +1376,7 @@ def finish_show_stereo(self, iviewer):
13651376
self.figure.tight_layout()
13661377

13671378
self.update_mask_boundaries(self.axis)
1379+
self.update_mask_highlights(self.axis)
13681380

13691381
self.draw_stereo_border()
13701382
self.update_auto_picked_data()
@@ -1566,6 +1578,19 @@ def is_stereo_from_polar(self):
15661578
self.iviewer.project_from_polar
15671579
)
15681580

1581+
def mask_highlights_changed(self):
1582+
if not self.iviewer:
1583+
return
1584+
1585+
if self.mode == ViewType.raw:
1586+
self.clear_mask_highlights()
1587+
for det_name, ax in self.raw_axes.items():
1588+
self.highlight_masks(ax, det_name)
1589+
return
1590+
1591+
if self.mode in (ViewType.polar, ViewType.stereo):
1592+
self.update_mask_highlights(self.axis)
1593+
15691594
def polar_masks_changed(self):
15701595
skip = (
15711596
not self.iviewer or
@@ -1576,6 +1601,7 @@ def polar_masks_changed(self):
15761601
return
15771602

15781603
self.update_mask_boundaries(self.axis)
1604+
self.update_mask_highlights(self.axis)
15791605
self.iviewer.reapply_masks()
15801606
img = self.scaled_display_images[0]
15811607
self.axes_images[0].set_data(img)
@@ -2182,11 +2208,23 @@ def clear_mask_boundaries(self):
21822208

21832209
self._mask_boundary_artists.clear()
21842210

2185-
def draw_mask_boundaries(self, axis, det=None):
2211+
def update_mask_highlights(self, axis):
2212+
# Update is a clear followed by a draw
2213+
self.clear_mask_highlights()
2214+
self.highlight_masks(axis)
2215+
2216+
def clear_mask_highlights(self):
2217+
self.remove_all_mask_highlight_artists()
2218+
2219+
def get_mask_verts(self, visible_attr, det=None):
21862220
# Create an instrument once that we will re-use
21872221
instr = create_view_hedm_instrument()
21882222
all_verts = []
2189-
for name in MaskManager().visible_boundaries:
2223+
options = {
2224+
'boundaries': MaskManager().visible_boundaries,
2225+
'highlights': MaskManager().visible_highlights
2226+
}
2227+
for name in options[visible_attr]:
21902228
mask = MaskManager().masks[name]
21912229
verts = None
21922230
if self.mode == ViewType.raw:
@@ -2251,6 +2289,10 @@ def draw_mask_boundaries(self, axis, det=None):
22512289
[np.vstack((x, (np.nan, np.nan))) for x in verts]
22522290
))
22532291

2292+
return all_verts
2293+
2294+
def draw_mask_boundaries(self, axis, det=None):
2295+
all_verts = self.get_mask_verts('boundaries', det)
22542296
if not all_verts:
22552297
return
22562298

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

2309+
def highlight_masks(self, axis, det=None):
2310+
all_verts = self.get_mask_verts('highlights', det)
2311+
if not all_verts:
2312+
return
2313+
2314+
kwargs = {
2315+
'facecolor': MaskManager().highlight_color,
2316+
'alpha': MaskManager().highlight_opacity,
2317+
'edgecolor': 'none',
2318+
'fill': True,
2319+
}
2320+
2321+
highlight_artists = self.mask_highlight_artists.setdefault(det or 'default', [])
2322+
2323+
for vert in all_verts:
2324+
polygon = Polygon(vert, **kwargs)
2325+
polygon.set_animated(True)
2326+
axis.add_patch(polygon)
2327+
highlight_artists.append(polygon)
2328+
2329+
self.blit_manager.update()
2330+
2331+
def remove_all_mask_highlight_artists(self):
2332+
self.blit_manager.remove_artists('mask_highlights')
2333+
self.blit_manager.artists['mask_highlights'] = {}
2334+
2335+
def remove_mask_highlight_artists(self, key):
2336+
self.blit_manager.remove_artists('mask_highlights', key)
2337+
22672338

22682339
class PolarXAxisTickLocator(AutoLocator):
22692340
"""Subclass the tick locator so we can modify its behavior

hexrdgui/image_tab_widget.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -465,7 +465,7 @@ def on_motion_notify_event(self, event):
465465
for mask in MaskManager().masks.values():
466466
if (
467467
mask.type == MaskType.threshold or
468-
(not mask.visible and not mask.show_border)
468+
(not mask.visible and not mask.show_border and not mask.highlight)
469469
):
470470
continue
471471

hexrdgui/masking/mask_border_style_picker.py

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,24 @@
77

88
class MaskBorderStylePicker(QObject):
99

10-
def __init__(self, original_color, original_style, original_width, parent=None):
10+
def __init__(
11+
self,
12+
original_color,
13+
original_style,
14+
original_width,
15+
original_highlight,
16+
original_highlight_opacity,
17+
parent=None
18+
):
1119
super().__init__(parent)
1220

1321
loader = UiLoader()
1422
self.ui = loader.load_file('mask_border_style_picker.ui', parent)
1523
self.original_color = original_color
1624
self.original_style = original_style
1725
self.original_width = original_width
26+
self.original_highlight = original_highlight
27+
self.original_highlight_opacity = original_highlight_opacity
1828

1929
self.reset_ui()
2030
self.setup_connections()
@@ -24,14 +34,18 @@ def reset_ui(self):
2434
self.ui.border_color.setStyleSheet('QPushButton {background-color: %s}' % self.original_color)
2535
self.ui.border_style.setCurrentText(self.original_style)
2636
self.ui.border_size.setValue(self.original_width)
37+
self.ui.highlight_color.setText(self.original_highlight)
38+
self.ui.highlight_color.setStyleSheet('QPushButton {background-color: %s}' % self.original_highlight)
39+
self.ui.opacity.setValue(self.original_highlight_opacity)
2740

2841
def exec(self):
2942
self.ui.adjustSize()
3043
return self.ui.exec()
3144

3245
def setup_connections(self):
33-
self.ui.border_color.clicked.connect(self.pick_color)
46+
self.ui.border_color.clicked.connect(lambda: self.pick_color('border'))
3447
self.ui.button_box.rejected.connect(self.reject)
48+
self.ui.highlight_color.clicked.connect(lambda: self.pick_color('highlight'))
3549

3650
@property
3751
def color(self):
@@ -45,15 +59,29 @@ def style(self):
4559
def width(self):
4660
return self.ui.border_size.value()
4761

48-
def pick_color(self):
49-
dialog = QColorDialog(QColor(self.original_color), self.ui)
62+
@property
63+
def highlight(self):
64+
return self.ui.highlight_color.text()
65+
66+
@property
67+
def opacity(self):
68+
return self.ui.opacity.value()
69+
70+
def pick_color(self, type):
71+
options = {
72+
'border': self.original_color,
73+
'highlight': self.original_highlight,
74+
'border_ui': self.ui.border_color,
75+
'highlight_ui': self.ui.highlight_color
76+
}
77+
dialog = QColorDialog(QColor(options[type]), self.ui)
5078
if dialog.exec():
5179
color = dialog.selectedColor().name()
52-
self.ui.border_color.setText(color)
53-
self.ui.border_color.setStyleSheet('QPushButton {background-color: %s}' % color)
80+
options[f'{type}_ui'].setText(color)
81+
options[f'{type}_ui'].setStyleSheet('QPushButton {background-color: %s}' % color)
5482

5583
def reject(self):
5684
self.reset_ui()
5785

5886
def result(self):
59-
return self.color, self.style, self.width
87+
return self.color, self.style, self.width, self.highlight, self.opacity

0 commit comments

Comments
 (0)