Пример #1
0
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()
Пример #2
0
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()
Пример #3
0
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()
Пример #4
0
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)
Пример #5
0
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