Exemplo n.º 1
0
class TestRangeSlider(TestCaseQt, ParametricTestCase):
    """Tests for TestRangeSlider"""
    def setUp(self):
        self.slider = RangeSlider()
        self.slider.show()
        self.qWaitForWindowExposed(self.slider)

    def tearDown(self):
        self.slider.setAttribute(qt.Qt.WA_DeleteOnClose)
        self.slider.close()
        del self.slider
        self.qapp.processEvents()

    def testRangeValue(self):
        """Test slider range and values"""

        # Play with range
        self.slider.setRange(1, 2)
        self.assertEqual(self.slider.getRange(), (1., 2.))
        self.assertEqual(self.slider.getValues(), (1., 1.))

        self.slider.setMinimum(-1)
        self.assertEqual(self.slider.getRange(), (-1., 2.))
        self.assertEqual(self.slider.getValues(), (1., 1.))

        self.slider.setMaximum(0)
        self.assertEqual(self.slider.getRange(), (-1., 0.))
        self.assertEqual(self.slider.getValues(), (0., 0.))

        # Play with values
        self.slider.setFirstValue(-2.)
        self.assertEqual(self.slider.getValues(), (-1., 0.))

        self.slider.setFirstValue(-0.5)
        self.assertEqual(self.slider.getValues(), (-0.5, 0.))

        self.slider.setSecondValue(2.)
        self.assertEqual(self.slider.getValues(), (-0.5, 0.))

        self.slider.setSecondValue(-0.1)
        self.assertEqual(self.slider.getValues(), (-0.5, -0.1))

    def testStepCount(self):
        """Test related to step count"""
        self.slider.setPositionCount(11)
        self.assertEqual(self.slider.getPositionCount(), 11)
        self.slider.setFirstValue(0.32)
        self.assertEqual(self.slider.getFirstValue(), 0.3)
        self.assertEqual(self.slider.getFirstPosition(), 3)

        self.slider.setPositionCount(3)  # Value is adjusted
        self.assertEqual(self.slider.getValues(), (0.5, 1.))
        self.assertEqual(self.slider.getPositions(), (1, 2))

    def testGroove(self):
        """Test Groove pixmap"""
        profile = list(range(100))

        for cmap in ('jet', colors.Colormap('viridis')):
            with self.subTest(str(cmap)):
                self.slider.setGroovePixmapFromProfile(profile, cmap)
                pixmap = self.slider.getGroovePixmap()
                self.assertIsInstance(pixmap, qt.QPixmap)
                self.assertEqual(pixmap.width(), len(profile))
Exemplo n.º 2
0
class HistogramWidget(qt.QWidget):
    """Widget displaying a histogram and some statistic indicators"""

    _SUPPORTED_ITEM_CLASS = items.ImageBase, items.Scatter

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setWindowTitle('Histogram')

        self.__itemRef = None  # weakref on the item to track

        layout = qt.QVBoxLayout(self)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)

        # Plot
        # Lazy import to avoid circular dependencies
        from silx.gui.plot.PlotWindow import Plot1D
        self.__plot = Plot1D(self)
        layout.addWidget(self.__plot)

        self.__plot.setDataMargins(0.1, 0.1, 0.1, 0.1)
        self.__plot.getXAxis().setLabel("Value")
        self.__plot.getYAxis().setLabel("Count")
        posInfo = self.__plot.getPositionInfoWidget()
        posInfo.setSnappingMode(posInfo.SNAPPING_CURVE)

        # Histogram controls
        controlsWidget = qt.QWidget(self)
        layout.addWidget(controlsWidget)
        controlsLayout = qt.QHBoxLayout(controlsWidget)
        controlsLayout.setContentsMargins(4, 4, 4, 4)

        controlsLayout.addWidget(qt.QLabel("<b>Histogram:<b>"))
        controlsLayout.addWidget(qt.QLabel("N. bins:"))
        self.__nbinsLineEdit = _IntEdit(self)
        self.__nbinsLineEdit.setRange(2, 9999)
        self.__nbinsLineEdit.sigValueChanged.connect(
            self.__updateHistogramFromControls)
        controlsLayout.addWidget(self.__nbinsLineEdit)
        self.__rangeLabel = qt.QLabel("Range:")
        controlsLayout.addWidget(self.__rangeLabel)
        self.__rangeSlider = RangeSlider(parent=self)
        self.__rangeSlider.sigValueChanged.connect(
            self.__updateHistogramFromControls)
        self.__rangeSlider.sigValueChanged.connect(self.__rangeChanged)
        controlsLayout.addWidget(self.__rangeSlider)
        controlsLayout.addStretch(1)

        # Stats display
        statsWidget = qt.QWidget(self)
        layout.addWidget(statsWidget)
        statsLayout = qt.QHBoxLayout(statsWidget)
        statsLayout.setContentsMargins(4, 4, 4, 4)

        self.__statsWidgets = dict(
            (name, _StatWidget(parent=statsWidget, name=name))
            for name in ("min", "max", "mean", "std", "sum"))

        for widget in self.__statsWidgets.values():
            statsLayout.addWidget(widget)
        statsLayout.addStretch(1)

    def getPlotWidget(self):
        """Returns :class:`PlotWidget` use to display the histogram"""
        return self.__plot

    def resetZoom(self):
        """Reset PlotWidget zoom"""
        self.getPlotWidget().resetZoom()

    def reset(self):
        """Clear displayed information"""
        self.getPlotWidget().clear()
        self.setStatistics()

    def getItem(self) -> Optional[items.Item]:
        """Returns item used to display histogram and statistics."""
        return None if self.__itemRef is None else self.__itemRef()

    def setItem(self, item: Optional[items.Item]):
        """Set item from which to display histogram and statistics.

        :param item:
        """
        previous = self.getItem()
        if previous is not None:
            previous.sigItemChanged.disconnect(self.__itemChanged)

        self.__itemRef = None if item is None else weakref.ref(item)
        if item is not None:
            if isinstance(item, self._SUPPORTED_ITEM_CLASS):
                # Only listen signal for supported items
                item.sigItemChanged.connect(self.__itemChanged)
        self._updateFromItem()

    def __itemChanged(self, event):
        """Handle update of the item"""
        if event in (items.ItemChangedType.DATA, items.ItemChangedType.MASK):
            self._updateFromItem()

    def __updateHistogramFromControls(self, *args):
        """Handle udates coming from histogram control widgets"""

        hist = self.getHistogram(copy=False)
        if hist is not None:
            count, edges = hist
            if (len(count) == self.__nbinsLineEdit.getValue() and
                (edges[0], edges[-1]) == self.__rangeSlider.getValues()):
                return  # Nothing has changed

        self._updateFromItem()

    def __rangeChanged(self, first, second):
        """Handle change of histogram range from the range slider"""
        tooltip = "Histogram range:\n[%g, %g]" % (first, second)
        self.__rangeSlider.setToolTip(tooltip)
        self.__rangeLabel.setToolTip(tooltip)

    def _updateFromItem(self):
        """Update histogram and stats from the item"""
        item = self.getItem()

        if item is None:
            self.reset()
            return

        if not isinstance(item, self._SUPPORTED_ITEM_CLASS):
            _logger.error("Unsupported item", item)
            self.reset()
            return

        # Compute histogram and stats
        array = item.getValueData(copy=False)

        if array.size == 0:
            self.reset()
            return

        xmin, xmax = min_max(array, min_positive=False, finite=True)
        if xmin is None or xmax is None:  # All not finite data
            self.reset()
            return
        guessed_nbins = min(1024, int(numpy.sqrt(array.size)))

        # bad hack: get 256 bins in the case we have a B&W
        if numpy.issubdtype(array.dtype, numpy.integer):
            if guessed_nbins > xmax - xmin:
                guessed_nbins = xmax - xmin
        guessed_nbins = max(2, guessed_nbins)

        # Set default nbins
        self.__nbinsLineEdit.setDefaultValue(guessed_nbins, extend_range=True)
        # Set slider range: do not keep the range value, but the relative pos.
        previousPositions = self.__rangeSlider.getPositions()
        if xmin == xmax:  # Enlarge range is none
            if xmin == 0:
                range_ = -0.01, 0.01
            else:
                range_ = sorted((xmin * .99, xmin * 1.01))
        else:
            range_ = xmin, xmax

        self.__rangeSlider.setRange(*range_)
        self.__rangeSlider.setPositions(*previousPositions)

        histogram = Histogramnd(
            array.ravel().astype(numpy.float32),
            n_bins=max(2, self.__nbinsLineEdit.getValue()),
            histo_range=self.__rangeSlider.getValues(),
        )
        if len(histogram.edges) != 1:
            _logger.error("Error while computing the histogram")
            self.reset()
            return

        self.setHistogram(histogram.histo, histogram.edges[0])
        self.resetZoom()
        self.setStatistics(min_=xmin,
                           max_=xmax,
                           mean=numpy.nanmean(array),
                           std=numpy.nanstd(array),
                           sum_=numpy.nansum(array))

    def setHistogram(self, histogram, edges):
        """Set displayed histogram

        :param histogram: Bin values (N)
        :param edges: Bin edges (N+1)
        """
        # Only useful if setHistogram is called directly
        # TODO
        #nbins = len(histogram)
        #if nbins != self.__nbinsLineEdit.getDefaultValue():
        #    self.__nbinsLineEdit.setValue(nbins, extend_range=True)
        #self.__rangeSlider.setValues(edges[0], edges[-1])

        self.getPlotWidget().addHistogram(histogram=histogram,
                                          edges=edges,
                                          legend='histogram',
                                          fill=True,
                                          color='#66aad7',
                                          resetzoom=False)

    def getHistogram(self, copy: bool = True):
        """Returns currently displayed histogram.

        :param copy: True to get a copy,
            False to get internal representation (Do not modify!)
        :return: (histogram, edges) or None
        """
        for item in self.getPlotWidget().getItems():
            if item.getName() == 'histogram':
                return (item.getValueData(copy=copy),
                        item.getBinEdgesData(copy=copy))
        else:
            return None

    def setStatistics(self,
                      min_: Optional[float] = None,
                      max_: Optional[float] = None,
                      mean: Optional[float] = None,
                      std: Optional[float] = None,
                      sum_: Optional[float] = None):
        """Set displayed statistic indicators."""
        self.__statsWidgets['min'].setValue(min_)
        self.__statsWidgets['max'].setValue(max_)
        self.__statsWidgets['mean'].setValue(mean)
        self.__statsWidgets['std'].setValue(std)
        self.__statsWidgets['sum'].setValue(sum_)
Exemplo n.º 3
0
class TestRangeSlider(TestCaseQt, ParametricTestCase):
    """Tests for TestRangeSlider"""

    def setUp(self):
        self.slider = RangeSlider()
        self.slider.show()
        self.qWaitForWindowExposed(self.slider)

    def tearDown(self):
        self.slider.setAttribute(qt.Qt.WA_DeleteOnClose)
        self.slider.close()
        del self.slider
        self.qapp.processEvents()

    def testRangeValue(self):
        """Test slider range and values"""

        # Play with range
        self.slider.setRange(1, 2)
        self.assertEqual(self.slider.getRange(), (1., 2.))
        self.assertEqual(self.slider.getValues(), (1., 1.))

        self.slider.setMinimum(-1)
        self.assertEqual(self.slider.getRange(), (-1., 2.))
        self.assertEqual(self.slider.getValues(), (1., 1.))

        self.slider.setMaximum(0)
        self.assertEqual(self.slider.getRange(), (-1., 0.))
        self.assertEqual(self.slider.getValues(), (0., 0.))

        # Play with values
        self.slider.setFirstValue(-2.)
        self.assertEqual(self.slider.getValues(), (-1., 0.))

        self.slider.setFirstValue(-0.5)
        self.assertEqual(self.slider.getValues(), (-0.5, 0.))

        self.slider.setSecondValue(2.)
        self.assertEqual(self.slider.getValues(), (-0.5, 0.))

        self.slider.setSecondValue(-0.1)
        self.assertEqual(self.slider.getValues(), (-0.5, -0.1))

    def testStepCount(self):
        """Test related to step count"""
        self.slider.setPositionCount(11)
        self.assertEqual(self.slider.getPositionCount(), 11)
        self.slider.setFirstValue(0.32)
        self.assertEqual(self.slider.getFirstValue(), 0.3)
        self.assertEqual(self.slider.getFirstPosition(), 3)

        self.slider.setPositionCount(3)  # Value is adjusted
        self.assertEqual(self.slider.getValues(), (0.5, 1.))
        self.assertEqual(self.slider.getPositions(), (1, 2))

    def testGroove(self):
        """Test Groove pixmap"""
        profile = list(range(100))

        for cmap in ('jet', colors.Colormap('viridis')):
            with self.subTest(str(cmap)):
                self.slider.setGroovePixmapFromProfile(profile, cmap)
                pixmap = self.slider.getGroovePixmap()
                self.assertIsInstance(pixmap, qt.QPixmap)
                self.assertEqual(pixmap.width(), len(profile))