diff --git a/hexrd/ui/brightness_contrast_editor.py b/hexrd/ui/brightness_contrast_editor.py index a3e1e10db..1cd4be863 100644 --- a/hexrd/ui/brightness_contrast_editor.py +++ b/hexrd/ui/brightness_contrast_editor.py @@ -2,10 +2,14 @@ from scipy.interpolate import interp1d from PySide2.QtCore import QObject, Signal +from PySide2.QtWidgets import ( + QDialog, QDialogButtonBox, QMessageBox, QVBoxLayout +) from matplotlib.backends.backend_qt5agg import FigureCanvas from matplotlib.figure import Figure +from hexrd.ui.range_widget import RangeWidget from hexrd.ui.ui_loader import UiLoader from hexrd.ui.utils import block_signals, reversed_enumerate @@ -28,6 +32,8 @@ def __init__(self, parent=None): self._ui_min, self._ui_max = self._data_range self._data = None self.histogram = None + self.histogram_artist = None + self.line_artist = None self.default_auto_threshold = 5000 self.current_auto_threshold = self.default_auto_threshold @@ -36,7 +42,6 @@ def __init__(self, parent=None): self.ui = loader.load_file('brightness_contrast_editor.ui', parent) self.setup_plot() - self.setup_line() self.ui.minimum.setMaximum(NUM_INCREMENTS) self.ui.maximum.setMaximum(NUM_INCREMENTS) @@ -51,6 +56,7 @@ def setup_connections(self): self.ui.brightness.valueChanged.connect(self.brightness_edited) self.ui.contrast.valueChanged.connect(self.contrast_edited) + self.ui.set_data_range.pressed.connect(self.select_data_range) self.ui.reset.pressed.connect(self.reset_pressed) self.ui.auto_button.pressed.connect(self.auto_pressed) @@ -61,6 +67,8 @@ def data_range(self): @data_range.setter def data_range(self, v): self._data_range = v + self.clip_ui_range() + self.ensure_min_max_space('max') self.update_gui() @property @@ -70,7 +78,7 @@ def data(self): @data.setter def data(self, v): self._data = v - self.update_data_range() + self.reset_data_range() @property def data_list(self): @@ -83,15 +91,18 @@ def data_list(self): else: return [self.data] - def update_data_range(self): + @property + def data_bounds(self): if self.data is None: - self.data_range = (0, 1) - return + return (0, 1) data = self.data_list mins = [x.min() for x in data] maxes = [x.max() for x in data] - self.data_range = (min(mins), max(maxes)) + return (min(mins), max(maxes)) + + def reset_data_range(self): + self.data_range = self.data_bounds def update_gui(self): self.update_brightness() @@ -289,7 +300,15 @@ def setup_plot(self): self.ui.plot_layout.addWidget(self.canvas) + def clear_plot(self): + self.axis.clear() + self.histogram_artist = None + self.line_artist = None + def update_histogram(self): + # Clear the plot so everything will be re-drawn from scratch + self.clear_plot() + data = self.data_list if not data: return @@ -310,8 +329,9 @@ def update_histogram(self): 'bins': HISTOGRAM_NUM_BINS, 'color': 'black', } - self.axis.hist(**kwargs) - self.canvas.draw_idle() + self.histogram_artist = self.axis.hist(**kwargs)[2] + + self.canvas.draw() def update_range_labels(self): labels = (self.ui.min_label, self.ui.max_label) @@ -319,7 +339,7 @@ def update_range_labels(self): for label, text in zip(labels, texts): label.setText(text) - def setup_line(self): + def create_line(self): xs = (self.ui_min, self.ui_max) ys = self.axis.get_ylim() kwargs = { @@ -330,6 +350,9 @@ def setup_line(self): self.line_artist, = self.axis.plot(xs, ys, **kwargs) def update_line(self): + if self.line_artist is None: + self.create_line() + xs = (self.ui_min, self.ui_max) ys = self.axis.get_ylim() @@ -341,14 +364,46 @@ def update_line(self): self.line_artist.set_data(interp(xs), ys) self.canvas.draw_idle() - def clear_plot(self): - self.figure.clear() - @property def max_num_pixels(self): return max(np.prod(x.shape) for x in self.data_list) + def select_data_range(self): + dialog = QDialog(self.ui) + layout = QVBoxLayout() + dialog.setLayout(layout) + + range_widget = RangeWidget(dialog) + range_widget.bounds = self.data_bounds + range_widget.min = self.data_range[0] + range_widget.max = self.data_range[1] + layout.addWidget(range_widget.ui) + + buttons = QDialogButtonBox.Ok | QDialogButtonBox.Cancel + button_box = QDialogButtonBox(buttons, dialog) + button_box.accepted.connect(dialog.accept) + button_box.rejected.connect(dialog.reject) + layout.addWidget(button_box) + + if not dialog.exec_(): + # User canceled + return + + data_range = range_widget.range + if data_range[0] >= data_range[1]: + message = 'Min cannot be greater than or equal to the max' + QMessageBox.critical(self.ui, 'Validation Error', message) + return + + if self.data_range == data_range: + # Nothing changed... + return + + self.data_range = data_range + self.modified() + def reset_pressed(self): + self.reset_data_range() self.reset_auto_threshold() self.reset.emit() @@ -393,14 +448,15 @@ def auto_pressed(self): if h_max < h_min: # Reset the range - self.reset_pressed() - return + self.reset_auto_threshold() + self.ui_range = self.data_range + else: + vmin = hist_start + h_min * bin_size + vmax = hist_start + h_max * bin_size + if vmin == vmax: + vmin, vmax = data_range - vmin = hist_start + h_min * bin_size - vmax = hist_start + h_max * bin_size - if vmin == vmax: - vmin, vmax = data_range + self.ui_range = vmin, vmax - self.ui_range = vmin, vmax self.update_brightness() self.update_contrast() diff --git a/hexrd/ui/color_map_editor.py b/hexrd/ui/color_map_editor.py index 4587d2345..577b085cb 100644 --- a/hexrd/ui/color_map_editor.py +++ b/hexrd/ui/color_map_editor.py @@ -76,6 +76,9 @@ def update_bc_enable_state(self): self.ui.bc_editor_button.setEnabled(has_data) def bc_editor_button_pressed(self): + if self.bc_editor: + self.bc_editor.ui.reject() + bc = self.bc_editor = BrightnessContrastEditor(self.ui) bc.data = self.data bc.edited.connect(self.bc_editor_modified) diff --git a/hexrd/ui/range_widget.py b/hexrd/ui/range_widget.py new file mode 100644 index 000000000..a0f6a1c5d --- /dev/null +++ b/hexrd/ui/range_widget.py @@ -0,0 +1,40 @@ +from hexrd.ui.ui_loader import UiLoader + + +class RangeWidget: + + def __init__(self, parent=None): + loader = UiLoader() + self.ui = loader.load_file('range_widget.ui', parent) + + @property + def min(self): + return self.ui.min.value() + + @min.setter + def min(self, v): + self.ui.min.setValue(v) + + @property + def max(self): + return self.ui.max.value() + + @max.setter + def max(self, v): + self.ui.max.setValue(v) + + @property + def range(self): + return (self.min, self.max) + + @property + def bounds(self): + return (self.ui.min.minimum(), self.ui.max.maximum()) + + @bounds.setter + def bounds(self, v): + self.ui.min.setMinimum(v[0]) + self.ui.max.setMaximum(v[1]) + + self.ui.min.setToolTip(f'Min: {v[0]}') + self.ui.max.setToolTip(f'Max: {v[1]}') diff --git a/hexrd/ui/resources/ui/brightness_contrast_editor.ui b/hexrd/ui/resources/ui/brightness_contrast_editor.ui index 2b2a91ad8..2bf535e7b 100644 --- a/hexrd/ui/resources/ui/brightness_contrast_editor.ui +++ b/hexrd/ui/resources/ui/brightness_contrast_editor.ui @@ -17,76 +17,72 @@ - - - - Minimum: - - - - - - - Brightness: - - - - + Reset - - + + - Auto + Minimum: - - + + + + + Qt::Horizontal - + Maximum: - + + + + + + + Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing + + + + Qt::Horizontal - - + + - Contrast: + Auto - + Qt::Horizontal - - - - - - - Qt::Horizontal + + + + Brightness: @@ -100,18 +96,37 @@ - - + + - + Contrast: - - Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing + + + + + + Qt::Horizontal + + + + + + + Set Data Range + + minimum + maximum + brightness + contrast + auto_button + reset + diff --git a/hexrd/ui/resources/ui/range_widget.ui b/hexrd/ui/resources/ui/range_widget.ui new file mode 100644 index 000000000..59f847c07 --- /dev/null +++ b/hexrd/ui/resources/ui/range_widget.ui @@ -0,0 +1,83 @@ + + + range_widget + + + + 0 + 0 + 235 + 71 + + + + + 0 + 0 + + + + + + + + 0 + 0 + + + + + 100 + 0 + + + + + + + + + 0 + 0 + + + + - + + + Qt::AlignCenter + + + + + + + + 10 + 0 + + + + + 100 + 0 + + + + + + + + + ScientificDoubleSpinBox + QDoubleSpinBox +
scientificspinbox.h
+
+
+ + min + max + + + +