class RegionOfInterestWidget(_ConditionWidget): def __init__(self, parent=None): _ConditionWidget.__init__(self, RegionOfInterest, parent) def _init_ui(self): # Widgets validator = QIntValidator() validator.setBottom(0) self._txt_start_channel = QLineEdit() self._txt_start_channel.setValidator(validator) self._txt_start_channel.setAccessibleName('Start channel') self._txt_end_channel = QLineEdit() self._txt_end_channel.setValidator(validator) self._txt_end_channel.setAccessibleName("End channel") # Layouts layout = _ConditionWidget._init_ui(self) layout.addRow('<i>Start channel</i>', self._txt_start_channel) layout.addRow('<i>End channel</i>', self._txt_end_channel) # Signals self._txt_start_channel.textEdited.connect(self.edited) self._txt_end_channel.textEdited.connect(self.edited) return layout def _create_parameter(self): return self.CLASS(0, 1) def parameter(self, parameter=None): parameter = _ConditionWidget.parameter(self, parameter) parameter.channels = (int(self._txt_start_channel.text()), int(self._txt_end_channel.text())) return parameter def setParameter(self, condition): _ConditionWidget.setParameter(self, condition) self._txt_start_channel.setText(str(condition.start_channel)) self._txt_end_channel.setText(str(condition.end_channel)) def setReadOnly(self, state): _ConditionWidget.setReadOnly(self, state) self._txt_start_channel.setReadOnly(state) self._txt_end_channel.setReadOnly(state) def isReadOnly(self): return _ConditionWidget.isReadOnly(self) and \ self._txt_start_channel.isReadOnly() and \ self._txt_end_channel.isReadOnly() def hasAcceptableInput(self): return _ConditionWidget.hasAcceptableInput(self) and \ self._txt_start_channel.hasAcceptableInput() and \ self._txt_end_channel.hasAcceptableInput()
class ColorbarWidget(QWidget): colorbarChanged = Signal() # The parent should simply redraw their canvas def __init__(self, parent=None): super(ColorbarWidget, self).__init__(parent) self.setWindowTitle("Colorbar") self.setMaximumWidth(200) self.cmap = QComboBox() self.cmap.addItems(sorted(cm.cmap_d.keys())) self.cmap.currentIndexChanged.connect(self.cmap_index_changed) self.cmin = QLineEdit() self.cmin_value = 0 self.cmin.setMaximumWidth(100) self.cmin.editingFinished.connect(self.clim_changed) self.cmin_layout = QHBoxLayout() self.cmin_layout.addStretch() self.cmin_layout.addWidget(self.cmin) self.cmin_layout.addStretch() self.cmax = QLineEdit() self.cmax_value = 1 self.cmax.setMaximumWidth(100) self.cmax.editingFinished.connect(self.clim_changed) self.cmin.setValidator(QDoubleValidator()) self.cmax.setValidator(QDoubleValidator()) self.cmax_layout = QHBoxLayout() self.cmax_layout.addStretch() self.cmax_layout.addWidget(self.cmax) self.cmax_layout.addStretch() self.norm_layout = QHBoxLayout() self.norm = QComboBox() self.norm.addItems(NORM_OPTS) self.norm.currentIndexChanged.connect(self.norm_changed) self.powerscale = QLineEdit() self.powerscale_value = 2 self.powerscale.setText("2") self.powerscale.setValidator(QDoubleValidator(0.001,100,3)) self.powerscale.setMaximumWidth(50) self.powerscale.editingFinished.connect(self.norm_changed) self.powerscale.hide() self.powerscale_label = QLabel("n=") self.powerscale_label.hide() self.norm_layout.addStretch() self.norm_layout.addWidget(self.norm) self.norm_layout.addStretch() self.norm_layout.addWidget(self.powerscale_label) self.norm_layout.addWidget(self.powerscale) self.autoscale = QCheckBox("Autoscaling") self.autoscale.setChecked(True) self.autoscale.stateChanged.connect(self.update_clim) self.canvas = FigureCanvas(Figure()) if parent: # Set facecolor to match parent self.canvas.figure.set_facecolor(parent.palette().window().color().getRgbF()) self.ax = self.canvas.figure.add_axes([0.4,0.05,0.2,0.9]) # layout self.layout = QVBoxLayout(self) self.layout.addWidget(self.cmap) self.layout.addLayout(self.cmax_layout) self.layout.addWidget(self.canvas, stretch=1) self.layout.addLayout(self.cmin_layout) self.layout.addLayout(self.norm_layout) self.layout.addWidget(self.autoscale) def set_mappable(self, mappable): """ When a new plot is created this method should be called with the new mappable """ self.ax.clear() try: # Use current cmap cmap = self.colorbar.get_cmap() except AttributeError: try: # else use viridis cmap = cm.viridis except AttributeError: # else default cmap = None self.colorbar = Colorbar(ax=self.ax, mappable=mappable) self.cmin_value, self.cmax_value = self.colorbar.get_clim() self.update_clim_text() self.cmap_changed(cmap) self.cmap.setCurrentIndex(sorted(cm.cmap_d.keys()).index(self.colorbar.get_cmap().name)) self.redraw() def cmap_index_changed(self): self.cmap_changed(self.cmap.currentText()) def cmap_changed(self, name): self.colorbar.set_cmap(name) self.colorbar.mappable.set_cmap(name) self.redraw() def norm_changed(self): """ Called when a different normalization is selected """ idx = self.norm.currentIndex() if NORM_OPTS[idx] == 'Power': self.powerscale.show() self.powerscale_label.show() else: self.powerscale.hide() self.powerscale_label.hide() self.colorbar.mappable.set_norm(self.get_norm()) self.set_mappable(self.colorbar.mappable) def get_norm(self): """ This will create a matplotlib.colors.Normalize from selected idx, limits and powerscale """ idx = self.norm.currentIndex() if self.autoscale.isChecked(): cmin = cmax = None else: cmin = self.cmin_value cmax = self.cmax_value if NORM_OPTS[idx] == 'Power': if self.powerscale.hasAcceptableInput(): self.powerscale_value = float(self.powerscale.text()) return PowerNorm(gamma=self.powerscale_value, vmin=cmin, vmax=cmax) elif NORM_OPTS[idx] == "SymmetricLog10": return SymLogNorm(1e-8 if cmin is None else max(1e-8, abs(cmin)*1e-3), vmin=cmin, vmax=cmax) else: return Normalize(vmin=cmin, vmax=cmax) def clim_changed(self): """ Called when either the min or max is changed. Will unset the autoscale. """ self.autoscale.blockSignals(True) self.autoscale.setChecked(False) self.autoscale.blockSignals(False) self.update_clim() def update_clim(self): """ This will update the clim of the plot based on min, max, and autoscale """ if self.autoscale.isChecked(): data = self.colorbar.mappable.get_array() try: try: self.cmin_value = data[~data.mask].min() self.cmax_value = data[~data.mask].max() except AttributeError: self.cmin_value = np.nanmin(data) self.cmax_value = np.nanmax(data) except (ValueError, RuntimeWarning): # all values mask pass self.update_clim_text() else: if self.cmin.hasAcceptableInput(): cmin = float(self.cmin.text()) if cmin < self.cmax_value: self.cmin_value = cmin else: # reset values back self.update_clim_text() if self.cmax.hasAcceptableInput(): cmax = float(self.cmax.text()) if cmax > self.cmin_value: self.cmax_value = cmax else: #reset values back self.update_clim_text() self.colorbar.set_clim(self.cmin_value, self.cmax_value) self.redraw() def update_clim_text(self): """ Update displayed limit values based on stored ones """ self.cmin.setText("{:.4}".format(self.cmin_value)) self.cmax.setText("{:.4}".format(self.cmax_value)) def redraw(self): """ Redraws the colobar and emits signal to cause the parent to redraw """ self.colorbar.update_ticks() self.colorbar.draw_all() self.canvas.draw_idle() self.colorbarChanged.emit()
class ColorbarWidget(QWidget): colorbarChanged = Signal() # The parent should simply redraw their canvas def __init__(self, parent=None): super(ColorbarWidget, self).__init__(parent) self.setWindowTitle("Colorbar") self.setMaximumWidth(200) self.dval = QDoubleValidator() self.cmin = QLineEdit() self.cmin_value = 0 self.cmin.setMaximumWidth(100) self.cmin.editingFinished.connect(self.clim_changed) self.cmin_layout = QHBoxLayout() self.cmin_layout.addStretch() self.cmin_layout.addWidget(self.cmin) self.cmin_layout.addStretch() self.cmax = QLineEdit() self.cmax_value = 1 self.cmax.setMaximumWidth(100) self.cmax.editingFinished.connect(self.clim_changed) self.cmin.setValidator(self.dval) self.cmax.setValidator(self.dval) self.cmax_layout = QHBoxLayout() self.cmax_layout.addStretch() self.cmax_layout.addWidget(self.cmax) self.cmax_layout.addStretch() self.norm_layout = QHBoxLayout() self.norm = QComboBox() self.norm.addItems(NORM_OPTS) self.norm.currentIndexChanged.connect(self.norm_changed) self.powerscale = QLineEdit() self.powerscale_value = 2 self.powerscale.setText("2") self.powerscale.setValidator(QDoubleValidator(0.001,100,3)) self.powerscale.setMaximumWidth(50) self.powerscale.editingFinished.connect(self.norm_changed) self.powerscale.hide() self.powerscale_label = QLabel("n=") self.powerscale_label.hide() self.norm_layout.addStretch() self.norm_layout.addWidget(self.norm) self.norm_layout.addStretch() self.norm_layout.addWidget(self.powerscale_label) self.norm_layout.addWidget(self.powerscale) self.autoscale = QCheckBox("Autoscaling") self.autoscale.setChecked(True) self.autoscale.stateChanged.connect(self.update_clim) self.canvas = FigureCanvas(Figure()) if parent: # Set facecolor to match parent self.canvas.figure.set_facecolor(parent.palette().window().color().getRgbF()) self.ax = self.canvas.figure.add_axes([0.4,0.05,0.2,0.9]) # layout self.layout = QVBoxLayout(self) self.layout.addLayout(self.cmax_layout) self.layout.addWidget(self.canvas, stretch=1) self.layout.addLayout(self.cmin_layout) self.layout.addLayout(self.norm_layout) self.layout.addWidget(self.autoscale) def set_mappable(self, mappable): """ When a new plot is created this method should be called with the new mappable """ self.ax.clear() self.colorbar = Colorbar(ax=self.ax, mappable=mappable) self.cmin_value, self.cmax_value = self.colorbar.get_clim() self.update_clim_text() self.redraw() def norm_changed(self): """ Called when a different normalization is selected """ idx = self.norm.currentIndex() if NORM_OPTS[idx] == 'Power': self.powerscale.show() self.powerscale_label.show() else: self.powerscale.hide() self.powerscale_label.hide() self.colorbar.mappable.set_norm(self.get_norm()) self.colorbarChanged.emit() def get_norm(self): """ This will create a matplotlib.colors.Normalize from selected idx, limits and powerscale """ idx = self.norm.currentIndex() if self.autoscale.isChecked(): cmin = cmax = None else: cmin = self.cmin_value cmax = self.cmax_value if NORM_OPTS[idx] == 'Power': if self.powerscale.hasAcceptableInput(): self.powerscale_value = float(self.powerscale.text()) return PowerNorm(gamma=self.powerscale_value, vmin=cmin, vmax=cmax) elif NORM_OPTS[idx] == "SymmetricLog10": return SymLogNorm(1e-8 if cmin is None else max(1e-8, abs(cmin)*1e-3), vmin=cmin, vmax=cmax) else: return Normalize(vmin=cmin, vmax=cmax) def clim_changed(self): """ Called when either the min or max is changed. Will unset the autoscale. """ self.autoscale.blockSignals(True) self.autoscale.setChecked(False) self.autoscale.blockSignals(False) self.update_clim() def update_clim(self): """ This will update the clim of the plot based on min, max, and autoscale """ if self.autoscale.isChecked(): data = self.colorbar.mappable.get_array() try: try: self.cmin_value = data[~data.mask].min() self.cmax_value = data[~data.mask].max() except AttributeError: self.cmin_value = np.nanmin(data) self.cmax_value = np.nanmax(data) except (ValueError, RuntimeWarning): # all values mask pass self.update_clim_text() else: if self.cmin.hasAcceptableInput(): self.cmin_value = float(self.cmin.text()) if self.cmax.hasAcceptableInput(): self.cmax_value = float(self.cmax.text()) self.colorbar.set_clim(self.cmin_value, self.cmax_value) self.redraw() def update_clim_text(self): """ Update displayed limit values based on stored ones """ self.cmin.setText("{:.4}".format(self.cmin_value)) self.cmax.setText("{:.4}".format(self.cmax_value)) def redraw(self): """ Redraws the colobar and emits signal to cause the parent to redraw """ self.colorbar.update_ticks() self.colorbar.draw_all() self.canvas.draw_idle() self.colorbarChanged.emit()
class LevelEditDialog(QDialog): level_changed = Signal(LogLevel) def __init__(self, parent, level=None, creating_new_level=False, level_names=set()): super().__init__(parent) if level: self.level = level else: self.level = deepcopy(NO_LEVEL) self.creating_new_level = creating_new_level self.level_names = level_names self.setupUi() self.load_level(self.level) self.update_output() def setupUi(self): self.resize(350, 280) self.gridLayout = QGridLayout(self) self.levelNameLabel = QLabel("Level name", self) self.gridLayout.addWidget(self.levelNameLabel, 0, 0) self.levelNameLine = QLineEdit(self) self.gridLayout.addWidget(self.levelNameLine, 1, 0, 1, 0) self.groupBox = QGroupBox("Light mode", self) self.gridLayout.addWidget(self.groupBox, 2, 0) self.groupBoxDark = QGroupBox("Dark mode", self) self.gridLayout.addWidget(self.groupBoxDark, 2, 1) self.formLayout = QFormLayout(self.groupBox) self.groupBox.setLayout(self.formLayout) self.fgColorPreview = QLineEdit(self) self.formLayout.addRow("Foreground", self.fgColorPreview) self.bgColorPreview = QLineEdit(self) self.formLayout.addRow("Background", self.bgColorPreview) self.boldCheckBox = QCheckBox(self.groupBox) self.formLayout.addRow("Bold", self.boldCheckBox) self.italicCheckBox = QCheckBox(self.groupBox) self.formLayout.addRow("Italic", self.italicCheckBox) self.underlineCheckBox = QCheckBox(self.groupBox) self.formLayout.addRow("Underline", self.underlineCheckBox) self.formLayoutDark = QFormLayout(self.groupBoxDark) self.groupBoxDark.setLayout(self.formLayoutDark) self.fgColorPreviewDark = QLineEdit(self) self.formLayoutDark.addRow("Foreground", self.fgColorPreviewDark) self.bgColorPreviewDark = QLineEdit(self) self.formLayoutDark.addRow("Background", self.bgColorPreviewDark) self.boldCheckBoxDark = QCheckBox(self.groupBoxDark) self.formLayoutDark.addRow("Bold", self.boldCheckBoxDark) self.italicCheckBoxDark = QCheckBox(self.groupBox) self.formLayoutDark.addRow("Italic", self.italicCheckBoxDark) self.underlineCheckBoxDark = QCheckBox(self.groupBox) self.formLayoutDark.addRow("Underline", self.underlineCheckBoxDark) self.spacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding) self.gridLayout.addItem(self.spacer, 3, 0, 1, 2) self.previewLabel = QLabel("Preview", self) self.gridLayout.addWidget(self.previewLabel, 4, 0, 1, 2) self.previewLine = QLineEdit(self) self.gridLayout.addWidget(self.previewLine, 5, 0) self.previewLineDark = QLineEdit(self) self.gridLayout.addWidget(self.previewLineDark, 5, 1) buttons = QDialogButtonBox.Reset | QDialogButtonBox.Save | QDialogButtonBox.Cancel self.buttonBox = QDialogButtonBox(buttons, self) self.resetButton = self.buttonBox.button(QDialogButtonBox.Reset) self.gridLayout.addWidget(self.buttonBox, 6, 0, 1, 2) self.setup_widget_attributes() self.setup_widget_connections() def setup_widget_attributes(self): self.fgColorPreview.setReadOnly(True) self.bgColorPreview.setReadOnly(True) self.fgColorPreviewDark.setReadOnly(True) self.bgColorPreviewDark.setReadOnly(True) self.previewLine.setText("Log message") self.previewLineDark.setText("Log message") self.resetButton.setMaximumWidth(60) self.resetButton.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) self.levelNameLine.setText(self.level.levelname) if self.creating_new_level: self.name_validator = LevelNameValidator(self, self.level_names) self.levelNameLine.setValidator(self.name_validator) self.levelNameLine.textChanged.connect(self.level_name_valid) else: self.levelNameLine.setReadOnly(True) def setup_widget_connections(self): self.boldCheckBox.toggled.connect(self.toggle_bold) self.italicCheckBox.toggled.connect(self.toggle_italic) self.underlineCheckBox.toggled.connect(self.toggle_underline) self.boldCheckBoxDark.toggled.connect( partial(self.toggle_bold, dark=True)) self.italicCheckBoxDark.toggled.connect( partial(self.toggle_italic, dark=True)) self.underlineCheckBoxDark.toggled.connect( partial(self.toggle_underline, dark=True)) # couldn't find a way to make this any better self.fgColorPreview.mouseReleaseEvent = partial( self.open_color_dialog, 'fg') self.bgColorPreview.mouseReleaseEvent = partial( self.open_color_dialog, 'bg') self.fgColorPreviewDark.mouseReleaseEvent = partial( self.open_color_dialog, 'fgDark') self.bgColorPreviewDark.mouseReleaseEvent = partial( self.open_color_dialog, 'bgDark') self.buttonBox.accepted.connect(self.accept) self.buttonBox.rejected.connect(self.reject) self.resetButton.clicked.connect(self.reset_level) def set_checkboxes_state(self): self.boldCheckBox.setChecked(self.bold) self.italicCheckBox.setChecked(self.italic) self.underlineCheckBox.setChecked(self.underline) self.boldCheckBoxDark.setChecked(self.boldDark) self.italicCheckBoxDark.setChecked(self.italicDark) self.underlineCheckBoxDark.setChecked(self.underlineDark) def load_level(self, level): self.bg = level.bg self.fg = level.fg self.bgDark = level.bgDark self.fgDark = level.fgDark self.bold = 'bold' in level.styles self.italic = 'italic' in level.styles self.underline = 'underline' in level.styles self.boldDark = 'bold' in level.stylesDark self.italicDark = 'italic' in level.stylesDark self.underlineDark = 'underline' in level.stylesDark def reset_level(self): replacement = DEFAULT_LEVELS.get(self.level.levelname) if not replacement: replacement = NO_LEVEL self.load_level(replacement) self.update_output() def toggle_bold(self, enabled, dark=False): if not dark: self.bold = enabled else: self.boldDark = enabled self.update_output() def toggle_italic(self, enabled, dark=False): if not dark: self.italic = enabled else: self.italicDark = enabled self.update_output() def toggle_underline(self, enabled, dark=False): if not dark: self.underline = enabled else: self.underlineDark = enabled self.update_output() def open_color_dialog(self, attr_name, mouse_event): d = QColorDialog(self) d.setCurrentColor(getattr(self, attr_name)) f = partial(self.set_color, attr_name) d.colorSelected.connect( f) # d.open(f) doesn't pass color for some reason d.open() def set_color(self, attr_name, color): setattr(self, attr_name, color) self.update_output() def accept(self): self.level.styles = set() if self.bold: self.level.styles.add('bold') if self.italic: self.level.styles.add('italic') if self.underline: self.level.styles.add('underline') self.level.stylesDark = set() if self.boldDark: self.level.stylesDark.add('bold') if self.italicDark: self.level.stylesDark.add('italic') if self.underlineDark: self.level.stylesDark.add('underline') self.level.bg = self.bg self.level.fg = self.fg self.level.bgDark = self.bgDark self.level.fgDark = self.fgDark self.level.levelname = self.levelNameLine.text().upper() self.level_changed.emit(self.level) self.done(0) def reject(self): self.done(0) def update_output(self): # Setting the pallette doesn't override the global stylesheet, # which is why I can't just set pallete with needed colors here. self.previewLine.setStyleSheet("""QLineEdit {{ color: {}; background: {} }}""".format(self.fg.name(), self.bg.name())) self.previewLineDark.setStyleSheet("""QLineEdit {{ color: {}; background: {} }}""".format( self.fgDark.name(), self.bgDark.name())) self.bgColorPreview.setStyleSheet( 'QLineEdit {{background: {} }}'.format(self.bg.name())) self.fgColorPreview.setStyleSheet( 'QLineEdit {{background: {} }}'.format(self.fg.name())) self.bgColorPreviewDark.setStyleSheet( 'QLineEdit {{ background: {} }}'.format(self.bgDark.name())) self.fgColorPreviewDark.setStyleSheet( 'QLineEdit {{ background: {} }}'.format(self.fgDark.name())) font = self.previewLine.font() font.setBold(self.bold) font.setItalic(self.italic) font.setUnderline(self.underline) self.previewLine.setFont(font) fontDark = self.previewLineDark.font() fontDark.setBold(self.boldDark) fontDark.setItalic(self.italicDark) fontDark.setUnderline(self.underlineDark) self.previewLineDark.setFont(fontDark) self.set_checkboxes_state() def level_name_valid(self): if self.levelNameLine.hasAcceptableInput(): self.buttonBox.button(QDialogButtonBox.Save).setEnabled(True) else: self.buttonBox.button(QDialogButtonBox.Save).setEnabled(False)
class ColorbarWidget(QWidget): colorbarChanged = Signal() # The parent should simply redraw their canvas scaleNormChanged = Signal() # register additional color maps from file register_customized_colormaps() # create the list cmap_list = sorted( [cmap for cmap in cm.cmap_d.keys() if not cmap.endswith('_r')]) def __init__(self, parent=None, default_norm_scale=None): """ :param default_scale: None uses linear, else either a string or tuple(string, other arguments), e.g. tuple('Power', exponent) """ super(ColorbarWidget, self).__init__(parent) self.setWindowTitle("Colorbar") self.setMaximumWidth(100) self.cmap = QComboBox() self.cmap.addItems(self.cmap_list) self.cmap.currentIndexChanged.connect(self.cmap_index_changed) self.crev = QCheckBox('Reverse') self.crev.stateChanged.connect(self.crev_checked_changed) self.cmin = QLineEdit() self.cmin_value = 0 self.cmin.setMaximumWidth(100) self.cmin.editingFinished.connect(self.clim_changed) self.cmin_layout = QHBoxLayout() self.cmin_layout.addStretch() self.cmin_layout.addWidget(self.cmin) self.cmin_layout.addStretch() self.linear_validator = QDoubleValidator(parent=self) self.log_validator = QDoubleValidator(MIN_LOG_VALUE, sys.float_info.max, 3, self) self.cmax = QLineEdit() self.cmax_value = 1 self.cmax.setMaximumWidth(100) self.cmax.editingFinished.connect(self.clim_changed) self.cmax_layout = QHBoxLayout() self.cmax_layout.addStretch() self.cmax_layout.addWidget(self.cmax) self.cmax_layout.addStretch() norm_scale = 'Linear' powerscale_value = 2 if default_norm_scale in NORM_OPTS: norm_scale = default_norm_scale if isinstance(default_norm_scale, tuple) and default_norm_scale[0] in NORM_OPTS: norm_scale = default_norm_scale[0] if norm_scale == 'Power': powerscale_value = float(default_norm_scale[1]) self.norm_layout = QHBoxLayout() self.norm = QComboBox() self.norm.addItems(NORM_OPTS) self.norm.setCurrentText(norm_scale) self.norm.currentIndexChanged.connect(self.norm_changed) self.update_clim_validator() self.powerscale = QLineEdit() self.powerscale_value = powerscale_value self.powerscale.setText(str(powerscale_value)) self.powerscale.setValidator(QDoubleValidator(0.001, 100, 3)) self.powerscale.setMaximumWidth(50) self.powerscale.editingFinished.connect(self.norm_changed) self.powerscale_label = QLabel("n=") if norm_scale != 'Power': self.powerscale.hide() self.powerscale_label.hide() self.norm_layout.addStretch() self.norm_layout.addWidget(self.norm) self.norm_layout.addStretch() self.norm_layout.addWidget(self.powerscale_label) self.norm_layout.addWidget(self.powerscale) self.autoscale = QCheckBox("Autoscaling") self.autoscale.setChecked(True) self.autoscale.stateChanged.connect(self.update_clim) self.canvas = FigureCanvas(Figure()) if parent: # Set facecolor to match parent self.canvas.figure.set_facecolor( parent.palette().window().color().getRgbF()) self.ax = self.canvas.figure.add_axes([0.0, 0.02, 0.2, 0.97]) # layout self.layout = QVBoxLayout(self) self.layout.setContentsMargins(0, 0, 0, 0) self.layout.setSpacing(2) self.layout.addWidget(self.cmap) self.layout.addWidget(self.crev) self.layout.addLayout(self.cmax_layout) self.layout.addWidget(self.canvas, stretch=1) self.layout.addLayout(self.cmin_layout) self.layout.addLayout(self.norm_layout) self.layout.addWidget(self.autoscale) def set_mappable(self, mappable): """ When a new plot is created this method should be called with the new mappable """ # sanity check the mappable mappable = self._validate_mappable(mappable) self.ax.clear() try: # Use current cmap cmap = get_current_cmap(self.colorbar) except AttributeError: # else use default cmap = ConfigService.getString("plots.images.Colormap") self.colorbar = Colorbar(ax=self.ax, mappable=mappable) self.cmin_value, self.cmax_value = mappable.get_clim() self.update_clim_text() self.cmap_changed(cmap, False) mappable_cmap = get_current_cmap(mappable) if mappable_cmap.name.endswith('_r'): self.crev.setChecked(True) else: self.crev.setChecked(False) self.cmap.setCurrentIndex( self.cmap_list.index(mappable_cmap.name.replace('_r', ''))) self.redraw() def cmap_index_changed(self): self.cmap_changed(self.cmap.currentText(), self.crev.isChecked()) def crev_checked_changed(self): self.cmap_changed(self.cmap.currentText(), self.crev.isChecked()) def cmap_changed(self, name, rev): if rev: name += '_r' self.colorbar.mappable.set_cmap(name) if mpl_version_info() >= (3, 1): self.colorbar.update_normal(self.colorbar.mappable) else: self.colorbar.set_cmap(name) self.redraw() def mappable_changed(self): """ Updates the colormap and min/max values of the colorbar when the plot changes via settings. """ mappable_cmap = get_current_cmap(self.colorbar.mappable) low, high = self.colorbar.mappable.get_clim() self.cmin_value = low self.cmax_value = high self.update_clim_text() self.cmap.setCurrentIndex( sorted(cm.cmap_d.keys()).index(mappable_cmap.name)) self.redraw() def norm_changed(self): """ Called when a different normalization is selected """ idx = self.norm.currentIndex() if NORM_OPTS[idx] == 'Power': self.powerscale.show() self.powerscale_label.show() else: self.powerscale.hide() self.powerscale_label.hide() self.colorbar.mappable.set_norm(self.get_norm()) self.set_mappable(self.colorbar.mappable) self.update_clim_validator() self.scaleNormChanged.emit() def get_norm(self): """ This will create a matplotlib.colors.Normalize from selected idx, limits and powerscale """ idx = self.norm.currentIndex() if self.autoscale.isChecked(): cmin = cmax = None else: cmin = self.cmin_value cmax = self.cmax_value if NORM_OPTS[idx] == 'Power': if self.powerscale.hasAcceptableInput(): self.powerscale_value = float(self.powerscale.text()) return PowerNorm(gamma=self.powerscale_value, vmin=cmin, vmax=cmax) elif NORM_OPTS[idx] == "SymmetricLog10": return SymLogNorm( 1e-8 if cmin is None else max(1e-8, abs(cmin) * 1e-3), vmin=cmin, vmax=cmax) elif NORM_OPTS[idx] == "Log": cmin = MIN_LOG_VALUE if cmin is not None and cmin <= 0 else cmin return LogNorm(vmin=cmin, vmax=cmax) else: return Normalize(vmin=cmin, vmax=cmax) def get_colorbar_scale(self): norm = self.colorbar.norm scale = 'linear' kwargs = {} if isinstance(norm, SymLogNorm): scale = 'symlog' elif isinstance(norm, LogNorm): scale = 'log' elif isinstance(norm, PowerNorm): scale = 'function' kwargs = { 'functions': (lambda x: np.power(x, norm.gamma), lambda x: np.power(x, 1 / norm.gamma)) } return scale, kwargs def clim_changed(self): """ Called when either the min or max is changed. Will unset the autoscale. """ self.autoscale.blockSignals(True) self.autoscale.setChecked(False) self.autoscale.blockSignals(False) self.update_clim() def update_clim(self): """ This will update the clim of the plot based on min, max, and autoscale """ if self.autoscale.isChecked(): self._autoscale_clim() else: self._manual_clim() self.colorbar.mappable.set_clim(self.cmin_value, self.cmax_value) self.redraw() def update_clim_text(self): """ Update displayed limit values based on stored ones """ self.cmin.setText("{:.4}".format(self.cmin_value)) self.cmax.setText("{:.4}".format(self.cmax_value)) def redraw(self): """ Redraws the colobar and emits signal to cause the parent to redraw """ self.colorbar.update_ticks() self.colorbar.draw_all() self.canvas.draw_idle() self.colorbarChanged.emit() def update_clim_validator(self): if NORM_OPTS[self.norm.currentIndex()] == "Log": self.cmin.setValidator(self.log_validator) self.cmax.setValidator(self.log_validator) else: self.cmin.setValidator(self.linear_validator) self.cmax.setValidator(self.linear_validator) def _autoscale_clim(self): """Update stored colorbar limits The new limits are found from the colobar data """ data = self.colorbar.mappable.get_array() norm = NORM_OPTS[self.norm.currentIndex()] try: try: masked_data = data[~data.mask] # use the smallest positive value as vmin when using log scale, # matplotlib will take care of the data skipping part. masked_data = masked_data[ data > 0] if norm == "Log" else masked_data self.cmin_value = masked_data.min() self.cmax_value = masked_data.max() except (AttributeError, IndexError): data = data[np.nonzero(data)] if norm == "Log" else data self.cmin_value = np.nanmin(data) self.cmax_value = np.nanmax(data) except (ValueError, RuntimeWarning): # all values mask pass self.update_clim_text() def _manual_clim(self): """Update stored colorbar limits The new limits are found from user input""" if self.cmin.hasAcceptableInput(): cmin = float(self.cmin.text()) if cmin < self.cmax_value: self.cmin_value = cmin else: # reset values back self.update_clim_text() if self.cmax.hasAcceptableInput(): cmax = float(self.cmax.text()) if cmax > self.cmin_value: self.cmax_value = cmax else: # reset values back self.update_clim_text() def _create_linear_normalize_object(self): if self.autoscale.isChecked(): cmin = cmax = None else: cmin = self.cmin_value cmax = self.cmax_value return Normalize(vmin=cmin, vmax=cmax) def _validate_mappable(self, mappable): """Disable the Log option if no positive value can be found from given data (image)""" index = NORM_OPTS.index("Log") if mappable.get_array() is not None: if np.all(mappable.get_array() <= 0): self.norm.model().item(index, 0).setEnabled(False) self.norm.setItemData( index, "Log scale is disabled for non-positive data", Qt.ToolTipRole) if isinstance(mappable.norm, LogNorm): mappable.norm = self._create_linear_normalize_object() self.norm.blockSignals(True) self.norm.setCurrentIndex(0) self.norm.blockSignals(False) else: if not self.norm.model().item(index, 0).isEnabled(): self.norm.model().item(index, 0).setEnabled(True) self.norm.setItemData(index, "", Qt.ToolTipRole) return mappable