lavuelib.histogramWidget — LaVue 2.76.0 documentation

# Source code for lavuelib.histogramWidget

    # Copyright (C) 2017  DESY, Notkestr. 85, D-22607 Hamburg
    #
    # lavue is an image viewing program for photon science imaging detectors.
    # Its usual application is as a live viewer using hidra as data source.
    #
    # This program is free software; you can redistribute it and/or
    # modify it under the terms of the GNU General Public License
    # as published by the Free Software Foundation in  version 2
    # of the License.
    #
    # This program is distributed in the hope that it will be useful,
    # but WITHOUT ANY WARRANTY; without even the implied warranty of
    # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    # GNU General Public License for more details.
    #
    # You should have received a copy of the GNU General Public License
    # along with this program; if not, write to the Free Software
    # Foundation, Inc., 51 Franklin Street, Fifth Floor,
    # Boston, MA  02110-1301, USA.
    #
    # Authors:
    #     Jan Kotanski <jan.kotanski@desy.de>
    #     Christoph Rosemann <christoph.rosemann@desy.de>
    #

    """ Horizontal HistogramWidget """

    import pyqtgraph as _pg
    from pyqtgraph import QtCore, QtGui
    import numpy as np
    import logging

    from .external.pyqtgraph_0_12 import (
        histogram__init__, histogram_paint, histogram_setHistogramRange,
        # histogram_setLevels, histogram_fillHistogram
    )

    #: ( (:obj:`str`,:obj:`str`,:obj:`str`) )
    #:         pg major version, pg minor verion, pg patch version
    _VMAJOR, _VMINOR, _VPATCH = _pg.__version__.split(".")[:3] \
        if _pg.__version__ else ("0", "9", "0")
    try:
        _NPATCH = int(_VPATCH)
    except Exception:
        _NPATCH = 0
    _PQGVER = int(_VMAJOR) * 1000 + int(_VMINOR) * 100 + _NPATCH

    _pg.graphicsItems.GradientEditorItem.Gradients['reversegrey'] = {
        'ticks': [(0.0, (255, 255, 255, 255)),
                  (1.0, (0, 0, 0, 255)), ], 'mode': 'rgb'}
    _pg.graphicsItems.GradientEditorItem.Gradients['highcontrast'] = {
        'ticks': [(0.0, (0, 0, 0, 255)),
                  (1.0, (255, 255, 0, 255)), ], 'mode': 'rgb'}
    _pg.graphicsItems.GradientEditorItem.Gradients['spectrum'] = {
        'ticks': [(0.0, (255, 0, 255, 255)),
                  (1.0, (255, 0, 0, 255))], 'mode': 'hsv'}
    _pg.graphicsItems.GradientEditorItem.Gradients['spectrumclip'] = {
        'ticks': [(0.0, (255, 0, 255, 255)),
                  (.99, (255, 0, 0, 255)),
                  (1.0, (255, 255, 255, 255))], 'mode': 'hsv'}
    # define two new gradients of choice
    _pg.graphicsItems.GradientEditorItem.Gradients['inverted'] = {
        'ticks': [(0.0, (255, 255, 255, 255)),
                  (1.0, (0, 0, 0, 255)), ], 'mode': 'rgb'}
    _pg.graphicsItems.GradientEditorItem.Gradients['highcontrastclip'] = {
        'ticks': [(0.0, (255, 255, 255, 255)),
                  (0.15, (0, 0, 0, 255)),
                  (0.4, (255, 0, 0, 255)),
                  (0.7, (255, 255, 0, 255)),
                  (0.99, (255, 255, 255, 255)),
                  (1.0, (0, 0, 255, 255)), ], 'mode': 'rgb'}

    __all__ = ['HistogramHLUTWidget']

    logger = logging.getLogger("lavue")

    class HistogramHLUTWidget(_pg.widgets.GraphicsView.GraphicsView):

        """ Horizontal HistogramWidget """

        def __init__(self, parent=None, bins=None, step=None,
                     *args, **kargs):
            """ constructor

            :param parent: parent object
            :type parent: :class:`pyqtgraph.QtCore.QObject`
            :param bins: bins edges algorithm for histogram, default: 'auto'
            :type bins: :obj:`str`
            :param step: data step for calculation of histogram levels,
                         default: 'auto'
            :type step: :obj:`str` or :obj:`int`
            :param args: HistogramHLUTItem parameters list
            :type args: :obj:`list` < :obj:`any`>
            :param kargs:  HistogramHLUTItem parameter dictionary
            :type kargs: :obj:`dict` < :obj:`str`, :obj:`any`>
            """
            background = kargs.pop('background', 'default')

            _pg.widgets.GraphicsView.GraphicsView.__init__(
                self, parent, useOpenGL=False, background=background)
            #: (:class:`HistogramHLUTItem`) histogram item
            self.item = HistogramHLUTItem(bins, step, *args, **kargs)
            self.setCentralItem(self.item)
            self.setSizePolicy(
                QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Expanding)
            self.setMinimumWidth(95)
            self.setMinimumHeight(95)

        def sizeHint(self):
            """ sets size hint
            """
            return QtCore.QSize(115, 200)

        def __getattr__(self, attr):
            """ gets attribute of HistogramHLUTItem
            :param attr: attribute name
            :type attr: :obj:`str`
            :returns: attribute value
            :rtype: :obj:`any`
            """
            return getattr(self.item, attr)

        def setAutoFactor(self, factor):
            """ sets auto level factor

            :param factor: auto level factor of maximal peak
            :type factor: :obj:`float`
            """
            self.item.autolevelfactor = factor

        def setBins(self, bins):
            """ sets bins edges algorithm for histogram

            :param channel: bins edges algorithm for histogram
            :type channel: :obj:`str`
            """
            self.item.setBins(bins)

        def setStep(self, step):
            """ sets image step data for algorithm of histogram

            :param channel: image step data for algorithm of histogram
            :type channel: :obj:`int`
            """
            self.item.setStep(step)

    class GradientEditorItemWS(
            _pg.graphicsItems.GradientEditorItem.GradientEditorItem):

        """ gradient editor item with a signal on loadPreset """

        #: (:class:`pyqtgraph.QtCore.pyqtSignal`) minimum level changed signal
        sigNameChanged = QtCore.pyqtSignal(str)

        def __init__(self, *args, **kargs):
            """ constructor

            :param args: GradientEditorItem parameters list
            :type args: :obj:`list` < :obj:`any`>
            :param kargs:  GradientEditorItem parameter dictionary
            :type kargs: :obj:`dict` < :obj:`str`, :obj:`any`>
            """
            self.__skipupdate = True
            _pg.graphicsItems.GradientEditorItem.GradientEditorItem.__init__(
                self, *args, **kargs)
            self.__skipupdate = False
            #: (:obj:`str`) color gradient name
            self.name = "highcontrast"
            #: (:class:`pyqtgrath.QtGui.QAction`) save gradient action
            self.saveAction = QtGui.QAction('Save ...', self)
            #: (:obj:`pyqtgrath.QtGui.QAction`) remove gradient action
            self.removeAction = QtGui.QAction('Remove', self)

        def addMenuActions(self):
            """ add save/remove actions
            """
            self.menu.addAction(self.saveAction)
            self.menu.addAction(self.removeAction)

        def removeTick(self, tick, finish=True):
            """ removes ticks with hook

            :param tick: ticks to remove
            :type tick:  :obj:`list` < :obj:`any`>
            :param finish: finish remove flag
            :type finish: :obj:`bool`
            """
            if not self.__skipupdate:
                _pg.graphicsItems.GradientEditorItem.GradientEditorItem.\
                    removeTick(
                        self, tick=tick, finish=finish)
            else:
                _pg.graphicsItems.GradientEditorItem.TickSliderItem.\
                    removeTick(self, tick)
                if finish:
                    # hook for a pyqtgraph bug
                    # self.updateGradient()
                    self.sigGradientChangeFinished.emit(self)

        def getCurrentGradient(self):
            """ provides dictionary with the current gradient

            :returns: gradient dictionary with:
               {"mode": <colorMode>,
                "ticks": [
                    (<pos>, (<r>, <g>, <b>, <a>)),
                    (<pos>, (<r>, <g>, <b>, <a>)),
                          ...
                    (<pos>, (<r>, <g>, <b>, <a>)),
                ]
               }
            :rtype: :obj:`dict` < :obj:`str`:,  :obj:`str`: or :obj:`list`:>
            """
            tlist = self.listTicks()
            tdct = {}
            tdct['ticks'] = []
            tdct['mode'] = self.colorMode
            for tck, pos in tlist:
                clr = tck.color
                tdct['ticks'].append(
                    (pos,
                     (clr.red(), clr.green(), clr.blue(), clr.alpha()))
                    )
            return tdct

        def loadPreset(self, name):
            """ loads a predefined gradient and emits sigNameChanged

            :param name: gradient name
            :type name: :obj:`str`
            """
            _pg.graphicsItems.GradientEditorItem.GradientEditorItem.loadPreset(
                self, name)
            self.name = name
            self.sigNameChanged.emit(name)

    class HistogramHLUTItem(_pg.HistogramLUTItem):

        #: (:class:`pyqtgraph.QtCore.pyqtSignal`) automatic levels changed signal
        autoLevelsChanged = QtCore.pyqtSignal(int)  # bool does not work...
        #: (:class:`pyqtgraph.QtCore.pyqtSignal`) minimum level changed signal
        sigNameChanged = QtCore.pyqtSignal(str)
        #: (:class:`pyqtgraph.QtCore.pyqtSignal`) save gradient requested
        saveGradientRequested = QtCore.pyqtSignal()
        #: (:class:`pyqtgraph.QtCore.pyqtSignal`) remove gradient requested
        removeGradientRequested = QtCore.pyqtSignal()

        """ Horizontal HistogramItem """

        def __init__(self, bins=None, step=None, image=None, fillHistogram=True,
                     expertmode=False):
            """ constructor

            :param bins: bins edges algorithm for histogram, default: 'auto'
            :type bins: :obj:`str`
            :param step: data step for calculation of histogram levels,
                         default: 'auto'
            :type step: :obj:`str` or :obj:`int`
            :param image: 2d image
            :type image: :class:`pyqtgraph.ImageItem`
            :param fillHistogram: histogram will be filled in
            :type fillHistogram: :obj:`bool`
            :param expertmode: expert mode flag
            :type expertmode: :obj:`bool`
            """
            if _PQGVER >= 1100:
                self.__init_1100(bins, step, image, fillHistogram, expertmode)
            else:
                self.__init_old(bins, step, image, fillHistogram, expertmode)
            self.vb.enableAutoRange(self.vb.YAxis, 0.99)

        def __init_1100(self, bins=None, step=None, image=None, fillHistogram=True,
                        expertmode=False):
            """ constructor for old pyqtgraph

            :param bins: bins edges algorithm for histogram, default: 'auto'
            :type bins: :obj:`str`
            :param step: data step for calculation of histogram levels,
                         default: 'auto'
            :type step: :obj:`str` or :obj:`int`
            :param image: 2d image
            :type image: :class:`pyqtgraph.ImageItem`
            :param fillHistogram: histogram will be filled in
            :type fillHistogram: :obj:`bool`
            :param expertmode: expert mode flag
            :type expertmode: :obj:`bool`
            """
            if _PQGVER >= 1202:
                _pg.graphicsItems.HistogramLUTItem.HistogramLUTItem.__init__(
                    self, image, fillHistogram, levelMode='mono',
                    gradientPosition='bottom', orientation='horizontal')
            else:
                histogram__init__(
                    self, image, fillHistogram, levelMode='mono',
                    gradientPosition='bottom', orientation='horizontal')

            #: (:obj:`bool`) expert mode
            self.__expertmode = expertmode

            #: (:obj: `bool`) rgb flag
            self.__rgb = False

            # self.vb.setMaximumHeight(15200)

            #: (:obj:`list`) buffer for removed gradients
            self.__oldgradient = []
            self.resetGradient(False)

            self.autolevelfactor = None
            self.__step = step or 'auto'
            self.__bins = bins or 'auto'

        def __init_old(self, bins=None, step=None, image=None, fillHistogram=True,
                       expertmode=False):
            """ constructor for old pyqtgraph

            :param bins: bins edges algorithm for histogram, default: 'auto'
            :type bins: :obj:`str`
            :param step: data step for calculation of histogram levels,
                         default: 'auto'
            :type step: :obj:`str` or :obj:`int`
            :param image: 2d image
            :type image: :class:`pyqtgraph.ImageItem`
            :param fillHistogram: histogram will be filled in
            :type fillHistogram: :obj:`bool`
            :param expertmode: expert mode flag
            :type expertmode: :obj:`bool`
            """
            _pg.graphicsItems.GraphicsWidget.GraphicsWidget.__init__(self)

            #: (:obj:`bool`) expert mode
            self.__expertmode = expertmode

            #: (:class:`numpy.ndarray`) look up table
            self.lut = None
            if _VMAJOR == '0' and int(_VMINOR) < 10 and int(_VPATCH) < 9:
                #: (:class:`weakref.ref` or :class:`pyqtgraph.ImageItem`)
                #: weakref to image item or image item itself  (for < 0.9.8)
                self.imageItem = None
            else:
                self.imageItem = lambda: None

            #: (:obj: `bool`) rgb flag
            self.__rgb = False

            #: (:class:`PyQt5.QtGui.QGraphicsGridLayout`) grid layout
            self.layout = QtGui.QGraphicsGridLayout()
            self.setLayout(self.layout)
            self.layout.setContentsMargins(1, 1, 1, 1)
            self.layout.setSpacing(0)

            #: (:class:`pyqtgraph.graphicsItems.ViewBox.ViewBox`) view box
            self.vb = _pg.graphicsItems.ViewBox.ViewBox(parent=self)
            # self.vb.setMaximumHeight(152)
            self.vb.setMinimumHeight(45)
            self.vb.setMouseEnabled(x=True, y=False)
            # self.vb.setMouseEnabled(x=False, y=True)

            #: (:class:`GradientEditorItemWS`) gradient editor item with a signal
            self.gradient = GradientEditorItemWS()
            if self.__expertmode:
                self.gradient.addMenuActions()
            self.gradient.setOrientation('bottom')
            self.gradient.loadPreset('grey')
            #: (:obj:`list`) buffer for removed gradients
            self.__oldgradient = []
            #: (:class:`pyqtgraph.graphicsItems.LinearRegionItem.LinearRegionItem`)
            #:    linear region item
            self.region = _pg.graphicsItems.LinearRegionItem.LinearRegionItem(
                [0, 1],
                _pg.graphicsItems.LinearRegionItem.LinearRegionItem.Vertical)
            self.region.setZValue(1000)
            self.vb.addItem(self.region)

            #: (:class:`pyqtgraph.graphicsItems.AxisItem.AxisItem`) axis item
            self.axis = _pg.graphicsItems.AxisItem.AxisItem(
                'top', linkView=self.vb, maxTickLength=-10, showValues=False,
                parent=self)

            self.layout.addItem(self.axis, 0, 0)
            self.layout.addItem(self.vb, 1, 0)
            self.layout.addItem(self.gradient, 2, 0)
            # self.range = None

            self.autolevelfactor = None
            self.__step = step or 'auto'
            self.__bins = bins or 'auto'
            self.gradient.setFlag(self.gradient.ItemStacksBehindParent)
            self.vb.setFlag(self.gradient.ItemStacksBehindParent)

            self.gradient.sigGradientChanged.connect(self.gradientChanged)
            self.gradient.sigNameChanged.connect(self._emitSigNameChanged)
            self.gradient.saveAction.triggered.connect(
                self._emitSaveGradientRequested)
            self.gradient.removeAction.triggered.connect(
                self._emitRemoveGradientRequested)
            self.region.sigRegionChanged.connect(self.regionChanging)
            self.region.sigRegionChangeFinished.connect(self.regionChanged)
            self.vb.sigRangeChanged.connect(self.viewRangeChanged)

            self.plot = _pg.graphicsItems.PlotDataItem.PlotDataItem()
            self.plots = [self.plot]
            # self.plot.dataBounds(1, 0.9)
            # self.plot.dataBounds(0, 0.9)
            self.levelMode = 'mono'
            self.fillHistogram(fillHistogram)

            self.vb.addItem(self.plot)
            self.autoHistogramRange()

            if image is not None:
                self.setImageItem(image)
            # self.background = None

        def setRGB(self, rgb):
            """ sets rgb flag

            :param rgb: rgb lag
            :type rgb: :obj:`bool`
            """
            self.__rgb = rgb

        def gradientChanged(self):
            """ gradient changed with rgb
            """
            if self.__rgb:
                self.lut = None
                self.sigLookupTableChanged.emit(self)
            else:
                _pg.HistogramLUTItem.gradientChanged(self)

        def resetGradient(self, signal=True):
            """ resets gradient widget
            """
            self.gradient.sigGradientChanged.disconnect(self.gradientChanged)
            if signal:
                self.gradient.sigNameChanged.disconnect(self._emitSigNameChanged)
                self.gradient.saveAction.triggered.disconnect(
                    self._emitSaveGradientRequested)
                self.gradient.removeAction.triggered.disconnect(
                    self._emitRemoveGradientRequested)
            self.gradient.hide()
            if hasattr(self.gradient, "prepareGeometryChange"):
                self.gradient.prepareGeometryChange()
            self.layout.removeItem(self.gradient)
            self.gradient.close()
            self.__oldgradient.append(self.gradient)
            self.gradient = GradientEditorItemWS()
            if self.__expertmode:
                self.gradient.addMenuActions()
            self.gradient.setOrientation('bottom')
            self.gradient.loadPreset('grey')
            self.layout.addItem(self.gradient, 2, 0)

            self.gradient.sigGradientChanged.connect(self.gradientChanged)
            self.gradient.sigNameChanged.connect(self._emitSigNameChanged)
            self.gradient.saveAction.triggered.connect(
                self._emitSaveGradientRequested)
            self.gradient.removeAction.triggered.connect(
                self._emitRemoveGradientRequested)

        @QtCore.pyqtSlot(str)
        def _emitSigNameChanged(self, name):
            """ emits SigNameChanged

            :param name: gradient name
            :type name: :obj:`str`
            """
            self.sigNameChanged.emit(name)

        @QtCore.pyqtSlot()
        def _emitSaveGradientRequested(self):
            """ emits saveGradientRequested
            """
            self.saveGradientRequested.emit()

        @QtCore.pyqtSlot()
        def _emitRemoveGradientRequested(self):
            """ emits removeGradientRequested
            """
            self.removeGradientRequested.emit()

        def setBins(self, bins):
            """ sets bins edges algorithm for histogram

            :param channel: bins edges algorithm for histogram
            :type channel: :obj:`str`
            """
            self.__bins = bins

        def setStep(self, step):
            """ sets image step data for algorithm of histogram

            :param channel: image step data for algorithm of histogram
            :type channel: :obj:`int`
            """
            try:
                if self.__step:
                    self.__step = int(step)
                else:
                    self.__step = "auto"
            except Exception:
                self.__step = "auto"

        def setGradientByName(self, name):
            """ sets gradient by name

            :param name: gradient name
            :type name: :obj:`str`
            """
            try:
                self.gradient.loadPreset(str(name))
            except Exception:
                self.gradient.loadPreset("highcontrast")

        def paint(self, p, *args):
            """ paints the histogram item

            :param p: QPainter painter
            :type p: :class:`PyQt5.QtGui.QPainter`
            :param args: paint argument
            :type args: :obj:`list` < :obj:`any`>
            """
            if _PQGVER >= 1202:
                self.__paint_1202(p, *args)
            elif _PQGVER >= 1100:
                histogram_paint(self, p, *args)
            else:
                self.__paint_old(p, *args)

        def __paint_1202(self, p, *args):
            _pg.graphicsItems.HistogramLUTItem.HistogramLUTItem.paint(
                self, p, *args)

        def __paint_old(self, p, *args):
            """ paints the histogram item

            :param p: QPainter painter
            :type p: :class:`PyQt5.QtGui.QPainter`
            :param args: paint argument
            :type args: :obj:`list` < :obj:`any`>
            """

            pen = self.region.lines[0].pen
            rgn = self.getLevels()
            p1 = self.vb.mapFromViewToItem(
                self, _pg.Point(rgn[0], self.vb.viewRect().center().y()))
            p2 = self.vb.mapFromViewToItem(
                self, _pg.Point(rgn[1], self.vb.viewRect().center().y()))
            gradRect = self.gradient.mapRectToParent(
                self.gradient.gradRect.rect())
            for pen in [_pg.functions.mkPen('k', width=3), pen]:
                p.setPen(pen)
                p.drawLine(p1, gradRect.topLeft())
                p.drawLine(p2, gradRect.topRight())
                p.drawLine(gradRect.topLeft(), gradRect.bottomLeft())
                p.drawLine(gradRect.topRight(), gradRect.bottomRight())

        def setHistogramRange(self, mn, mx, padding=0.1):
            """sets the Y range on the histogram plot. This disables auto-scaling.

            :param mn: minimum range level
            :type mn: :obj:`float`
            :param mx: maximum range level
            :type mx: :obj:`float`
            :param padding: histogram padding
            :type padding: :obj:`float`
            """
            if _PQGVER >= 1202:
                self.__setHistogramRange_1202(self, mn, mx, padding)
            if _PQGVER >= 1100:
                histogram_setHistogramRange(self, mn, mx, padding)
            else:
                self.__setHistogramRange_old(self, mn, mx, padding)

        def __setHistogramRange_old(self, mn, mx, padding=0.1):
            """sets the Y range on the histogram plot. This disables auto-scaling.

            :param mn: minimum range level
            :type mn: :obj:`float`
            :param mx: maximum range level
            :type mx: :obj:`float`
            :param padding: histogram padding
            :type padding: :obj:`float`
            """
            self.vb.enableAutoRange(self.vb.XAxis, False)
            self.vb.setYRange(mn, mx, padding)

        def __setHistogramRange_1202(self, mn, mx, padding=0.1):
            """sets the Y range on the histogram plot. This disables auto-scaling.

            :param mn: minimum range level
            :type mn: :obj:`float`
            :param mx: maximum range level
            :type mx: :obj:`float`
            :param padding: histogram padding
            :type padding: :obj:`float`
            """
            _pg.graphicsItems.HistogramLUTItem.HistogramLUTItem.setHistogramRange(
                self, mn, mx, padding)

        def __imageItem(self):
            """ provides imageItem independent of the pyqtgraph version

            :returns: image item
            :rtype: :class:`pyqtgraph.ImageItem`

            """
            if _VMAJOR == '0' and int(_VMINOR) < 10 and int(_VPATCH) < 9:
                #: (:class:`weakref.ref` or :class:`pyqtgraph.ImageItem`)
                #: weakref to image item or image item itself  (for < 0.9.8)
                return self.imageItem
            else:
                return self.imageItem()

        def getFactorRegion(self):
            """ provides mono auto levels calculated from autofactor

            :returns: minlevel, maxlevel
            :rtype: (float, float)

            """
            hx = None
            hy = None
            if self.autolevelfactor is not None:
                try:
                    hx, hy = self.__imageItem().getHistogram(
                        step=self.__step, bins=self.__bins)
                except Exception as e:
                    logger.warning(str(e))
                    # print(str(e))
                if hy is not None and hx is not None and hx.any() and hy.any():
                    if abs(hx[0]) < 1.e-3 or abs(hx[0]+2.) < 1.e-3:
                        hx = hx[1:]
                        hy = hy[1:]
                    if hx.any() and hy.any():
                        hmax = max(hy)
                        hmin = self.autolevelfactor*hmax/100.
                        # mn, mx = self.__imageItem().levels[:2]
                        indexes = np.where(hy >= hmin)
                        ind1 = indexes[0][0]
                        ind2 = indexes[-1][-1]
                        return hx[ind1], hx[ind2]
            return None, None

        def getChannelFactorRegion(self, ch=None):
            """ provides mono auto levels calculated from autofactor

            :param ch: histogram channels
            :type ch:  :obj:
            :returns: minlevel, maxlevel for channels
            :rtype: :obj:`list` < (float, float) >

            """
            channels = []
            if ch is None:
                channels = self.__imageItem().getHistogram(perChannel=True)
            if ch[0] is None:
                return
            autofactor = False
            if self.autolevelfactor is not None:
                for i in range(1, 5):
                    chs = None
                    if len(ch) >= i:
                        h = ch[i-1]
                        hx = h[0]
                        hy = h[1]
                        if hy is not None and hx is not None and \
                           hx.any() and hy.any():
                            if abs(hx[0]) < 1.e-3 or abs(hx[0]+2.) < 1.e-3:
                                hhx = hx[1:]
                                hhy = hy[1:]
                            else:
                                hhx = hx
                                hhy = hy
                            if hhx.any() and hhy.any():
                                hmax = max(hhy)
                                hmin = self.autolevelfactor * hmax / 100.
                                # mn, mx = self.__imageItem().levels[:2]
                                indexes = np.where(hhy >= hmin)
                                ind1 = indexes[0][0]
                                ind2 = indexes[-1][-1]
                                chs = (hhx[ind1], hhx[ind2])
                                autofactor = True
                    channels.append(chs)
            if autofactor:
                return channels

        def imageChanged(self, autoLevel=False, autoRange=False):
            """ overload imageChange method

            :param autoLevel: auto level flag
            :type autoLevel: :obj:`bool`
            :param autoRange: auto range flag
            :type autoRange: :obj:`bool`
            """
            if self.levelMode == 'mono':
                for plt in self.plots[1:]:
                    plt.setVisible(False)
                self.plots[0].setVisible(True)
                hx1, hx2 = self.getFactorRegion()
                if hx1 is not None:
                    self.region.setRegion([hx1, hx2])
                    _pg.graphicsItems.HistogramLUTItem.HistogramLUTItem.\
                        imageChanged(
                            self, autoLevel=False, autoRange=autoRange)
                    return
                try:
                    # _pg.graphicsItems.HistogramLUTItem.HistogramLUTItem.\
                    #     imageChanged(
                    #         self, autoLevel=autoLevel, autoRange=autoRange)
                    h = self.__imageItem().getHistogram(
                        step=self.__step, bins=self.__bins)
                    if h[0] is None:
                        return
                    self.plot.setData(*h)
                    if autoLevel:
                        mn = h[0][0]
                        mx = h[0][-1]
                        self.region.setRegion([mn, mx])
                except Exception as e:
                    logger.warning(str(e))
                    # print(str(e))
            else:
                # plot one histogram for each channel
                self.plots[0].setVisible(False)
                ch = self.__imageItem().getHistogram(perChannel=True)
                if ch[0] is None:
                    return
                for i in range(1, 5):
                    if len(ch) >= i:
                        h = ch[i-1]
                        self.plots[i].setVisible(True)
                        self.plots[i].setData(*h)
                    else:
                        # hide channels not present in image data
                        self.plots[i].setVisible(False)
                autofactor = False
                channels = self.getChannelFactorRegion(ch)
                if channels is not None:
                    for i, hxx in enumerate(channels):
                        if hxx is not None:
                            self.regions[i + 1].setRegion([hxx[0], hxx[1]])
                    autofactor = True
                if not autofactor and autoLevel:
                    for i in range(1, 5):
                        if len(ch) >= i:
                            h = ch[i-1]
                            mn = h[0][0]
                            mx = h[0][-1]
                            self.regions[i].setRegion([mn, mx])
                if autofactor:
                    _pg.graphicsItems.HistogramLUTItem.HistogramLUTItem.\
                        imageChanged(
                            self, autoLevel=False, autoRange=autoRange)

                # make sure we are displaying the correct number of channels
                self._showRegions()

        def switchLevelMode(self, mode):
            """ switch rgba mode

            :param mode: rgba mode i.e. 'mono' or 'rgba'
            :type mode: :obj:`str`
            """
            if mode == self.levelMode or mode not in {'mono', 'rgba'}:
                return

            self.levelMode = mode
            self._showRegions()

            if mode == 'mono':
                levels = self.region.getRegion()
                self.setLevels(*levels)
            else:
                if hasattr(self, "regions"):
                    levels = [self.regions[i].getRegion() for i in range(1, 5)]
                else:
                    oldLevels = self.region.getRegion()
                    levels = [oldLevels] * 4
                self.setLevels(rgba=levels)
            self.__imageItem().setLevels(self.getLevels())
            self.imageChanged()
            self.update()