summaryrefslogtreecommitdiff
path: root/silx/gui/plot/tools/test
diff options
context:
space:
mode:
Diffstat (limited to 'silx/gui/plot/tools/test')
-rw-r--r--silx/gui/plot/tools/test/__init__.py2
-rw-r--r--silx/gui/plot/tools/test/testProfile.py673
-rw-r--r--silx/gui/plot/tools/test/testROI.py191
-rw-r--r--silx/gui/plot/tools/test/testScatterProfileToolBar.py137
4 files changed, 915 insertions, 88 deletions
diff --git a/silx/gui/plot/tools/test/__init__.py b/silx/gui/plot/tools/test/__init__.py
index 9cede27..1429545 100644
--- a/silx/gui/plot/tools/test/__init__.py
+++ b/silx/gui/plot/tools/test/__init__.py
@@ -33,6 +33,7 @@ from . import testROI
from . import testTools
from . import testScatterProfileToolBar
from . import testCurveLegendsWidget
+from . import testProfile
def suite():
@@ -42,6 +43,7 @@ def suite():
testTools.suite(),
testScatterProfileToolBar.suite(),
testCurveLegendsWidget.suite(),
+ testProfile.suite(),
])
return test_suite
diff --git a/silx/gui/plot/tools/test/testProfile.py b/silx/gui/plot/tools/test/testProfile.py
new file mode 100644
index 0000000..444cfe0
--- /dev/null
+++ b/silx/gui/plot/tools/test/testProfile.py
@@ -0,0 +1,673 @@
+# coding: utf-8
+# /*##########################################################################
+#
+# Copyright (c) 2018 European Synchrotron Radiation Facility
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+# ###########################################################################*/
+__authors__ = ["T. Vincent"]
+__license__ = "MIT"
+__date__ = "28/06/2018"
+
+
+import unittest
+import contextlib
+import numpy
+import logging
+
+from silx.gui import qt
+from silx.utils import deprecation
+from silx.utils import testutils
+
+from silx.gui.utils.testutils import TestCaseQt
+from silx.utils.testutils import ParametricTestCase
+from silx.gui.plot import PlotWindow, Plot1D, Plot2D, Profile
+from silx.gui.plot.StackView import StackView
+from silx.gui.plot.tools.profile import rois
+from silx.gui.plot.tools.profile import editors
+from silx.gui.plot.items import roi as roi_items
+from silx.gui.plot.tools.profile import manager
+from silx.gui import plot as silx_plot
+
+_logger = logging.getLogger(__name__)
+
+
+class TestRois(TestCaseQt):
+
+ def test_init(self):
+ """Check that the constructor is not called twice"""
+ roi = rois.ProfileImageVerticalLineROI()
+ if qt.BINDING not in ["PySide", "PySide2"]:
+ # the profile ROI + the shape
+ self.assertEqual(roi.receivers(roi.sigRegionChanged), 2)
+
+
+class TestInteractions(TestCaseQt):
+
+ @contextlib.contextmanager
+ def defaultPlot(self):
+ try:
+ widget = silx_plot.PlotWidget()
+ widget.show()
+ self.qWaitForWindowExposed(widget)
+ yield widget
+ finally:
+ widget.close()
+ widget = None
+ self.qWait()
+
+ @contextlib.contextmanager
+ def imagePlot(self):
+ try:
+ widget = silx_plot.Plot2D()
+ image = numpy.arange(10 * 10).reshape(10, -1)
+ widget.addImage(image)
+ widget.show()
+ self.qWaitForWindowExposed(widget)
+ yield widget
+ finally:
+ widget.close()
+ widget = None
+ self.qWait()
+
+ @contextlib.contextmanager
+ def scatterPlot(self):
+ try:
+ widget = silx_plot.ScatterView()
+
+ nbX, nbY = 7, 5
+ yy = numpy.atleast_2d(numpy.ones(nbY)).T
+ xx = numpy.atleast_2d(numpy.ones(nbX))
+ positionX = numpy.linspace(10, 50, nbX) * yy
+ positionX = positionX.reshape(nbX * nbY)
+ positionY = numpy.atleast_2d(numpy.linspace(20, 60, nbY)).T * xx
+ positionY = positionY.reshape(nbX * nbY)
+ values = numpy.arange(nbX * nbY)
+
+ widget.setData(positionX, positionY, values)
+ widget.resetZoom()
+ widget.show()
+ self.qWaitForWindowExposed(widget)
+ yield widget.getPlotWidget()
+ finally:
+ widget.close()
+ widget = None
+ self.qWait()
+
+ @contextlib.contextmanager
+ def stackPlot(self):
+ try:
+ widget = silx_plot.StackView()
+ image = numpy.arange(10 * 10).reshape(10, -1)
+ cube = numpy.array([image, image, image])
+ widget.setStack(cube)
+ widget.resetZoom()
+ widget.show()
+ self.qWaitForWindowExposed(widget)
+ yield widget.getPlotWidget()
+ finally:
+ widget.close()
+ widget = None
+ self.qWait()
+
+ def waitPendingOperations(self, proflie):
+ for _ in range(10):
+ if not proflie.hasPendingOperations():
+ return
+ self.qWait(100)
+ _logger.error("The profile manager still have pending operations")
+
+ def genericRoiTest(self, plot, roiClass):
+ profileManager = manager.ProfileManager(plot, plot)
+ profileManager.setItemType(image=True, scatter=True)
+
+ try:
+ action = profileManager.createProfileAction(roiClass, plot)
+ action.triggered[bool].emit(True)
+ widget = plot.getWidgetHandle()
+
+ # Do the mouse interaction
+ pos1 = widget.width() * 0.4, widget.height() * 0.4
+ self.mouseMove(widget, pos=pos1)
+ self.mouseClick(widget, qt.Qt.LeftButton, pos=pos1)
+
+ if issubclass(roiClass, roi_items.LineROI):
+ pos2 = widget.width() * 0.6, widget.height() * 0.6
+ self.mouseMove(widget, pos=pos2)
+ self.mouseClick(widget, qt.Qt.LeftButton, pos=pos2)
+
+ self.waitPendingOperations(profileManager)
+
+ # Test that something was computed
+ if issubclass(roiClass, rois._ProfileCrossROI):
+ self.assertEqual(profileManager._computedProfiles, 2)
+ elif issubclass(roiClass, roi_items.LineROI):
+ self.assertGreaterEqual(profileManager._computedProfiles, 1)
+ else:
+ self.assertEqual(profileManager._computedProfiles, 1)
+
+ # Test the created ROIs
+ profileRois = profileManager.getRoiManager().getRois()
+ if issubclass(roiClass, rois._ProfileCrossROI):
+ self.assertEqual(len(profileRois), 3)
+ else:
+ self.assertEqual(len(profileRois), 1)
+ # The first one should be the expected one
+ roi = profileRois[0]
+
+ # Test that something was displayed
+ if issubclass(roiClass, rois._ProfileCrossROI):
+ profiles = roi._getLines()
+ window = profiles[0].getProfileWindow()
+ self.assertIsNotNone(window)
+ window = profiles[1].getProfileWindow()
+ self.assertIsNotNone(window)
+ else:
+ window = roi.getProfileWindow()
+ self.assertIsNotNone(window)
+ finally:
+ profileManager.clearProfile()
+
+ def testImageActions(self):
+ roiClasses = [
+ rois.ProfileImageHorizontalLineROI,
+ rois.ProfileImageVerticalLineROI,
+ rois.ProfileImageLineROI,
+ rois.ProfileImageCrossROI,
+ ]
+ with self.imagePlot() as plot:
+ for roiClass in roiClasses:
+ with self.subTest(roiClass=roiClass):
+ self.genericRoiTest(plot, roiClass)
+
+ def testScatterActions(self):
+ roiClasses = [
+ rois.ProfileScatterHorizontalLineROI,
+ rois.ProfileScatterVerticalLineROI,
+ rois.ProfileScatterLineROI,
+ rois.ProfileScatterCrossROI,
+ rois.ProfileScatterHorizontalSliceROI,
+ rois.ProfileScatterVerticalSliceROI,
+ rois.ProfileScatterCrossSliceROI,
+ ]
+ with self.scatterPlot() as plot:
+ for roiClass in roiClasses:
+ with self.subTest(roiClass=roiClass):
+ self.genericRoiTest(plot, roiClass)
+
+ def testStackActions(self):
+ roiClasses = [
+ rois.ProfileImageStackHorizontalLineROI,
+ rois.ProfileImageStackVerticalLineROI,
+ rois.ProfileImageStackLineROI,
+ rois.ProfileImageStackCrossROI,
+ ]
+ with self.stackPlot() as plot:
+ for roiClass in roiClasses:
+ with self.subTest(roiClass=roiClass):
+ self.genericRoiTest(plot, roiClass)
+
+ def genericEditorTest(self, plot, roi, editor):
+ if isinstance(editor, editors._NoProfileRoiEditor):
+ pass
+ elif isinstance(editor, editors._DefaultImageStackProfileRoiEditor):
+ # GUI to ROI
+ editor._lineWidth.setValue(2)
+ self.assertEqual(roi.getProfileLineWidth(), 2)
+ editor._methodsButton.setMethod("sum")
+ self.assertEqual(roi.getProfileMethod(), "sum")
+ editor._profileDim.setDimension(1)
+ self.assertEqual(roi.getProfileType(), "1D")
+ # ROI to GUI
+ roi.setProfileLineWidth(3)
+ self.assertEqual(editor._lineWidth.value(), 3)
+ roi.setProfileMethod("mean")
+ self.assertEqual(editor._methodsButton.getMethod(), "mean")
+ roi.setProfileType("2D")
+ self.assertEqual(editor._profileDim.getDimension(), 2)
+ elif isinstance(editor, editors._DefaultImageProfileRoiEditor):
+ # GUI to ROI
+ editor._lineWidth.setValue(2)
+ self.assertEqual(roi.getProfileLineWidth(), 2)
+ editor._methodsButton.setMethod("sum")
+ self.assertEqual(roi.getProfileMethod(), "sum")
+ # ROI to GUI
+ roi.setProfileLineWidth(3)
+ self.assertEqual(editor._lineWidth.value(), 3)
+ roi.setProfileMethod("mean")
+ self.assertEqual(editor._methodsButton.getMethod(), "mean")
+ elif isinstance(editor, editors._DefaultScatterProfileRoiEditor):
+ # GUI to ROI
+ editor._nPoints.setValue(100)
+ self.assertEqual(roi.getNPoints(), 100)
+ # ROI to GUI
+ roi.setNPoints(200)
+ self.assertEqual(editor._nPoints.value(), 200)
+ else:
+ assert False
+
+ def testEditors(self):
+ roiClasses = [
+ (rois.ProfileImageHorizontalLineROI, editors._DefaultImageProfileRoiEditor),
+ (rois.ProfileImageVerticalLineROI, editors._DefaultImageProfileRoiEditor),
+ (rois.ProfileImageLineROI, editors._DefaultImageProfileRoiEditor),
+ (rois.ProfileImageCrossROI, editors._DefaultImageProfileRoiEditor),
+ (rois.ProfileScatterHorizontalLineROI, editors._DefaultScatterProfileRoiEditor),
+ (rois.ProfileScatterVerticalLineROI, editors._DefaultScatterProfileRoiEditor),
+ (rois.ProfileScatterLineROI, editors._DefaultScatterProfileRoiEditor),
+ (rois.ProfileScatterCrossROI, editors._DefaultScatterProfileRoiEditor),
+ (rois.ProfileScatterHorizontalSliceROI, editors._NoProfileRoiEditor),
+ (rois.ProfileScatterVerticalSliceROI, editors._NoProfileRoiEditor),
+ (rois.ProfileScatterCrossSliceROI, editors._NoProfileRoiEditor),
+ (rois.ProfileImageStackHorizontalLineROI, editors._DefaultImageStackProfileRoiEditor),
+ (rois.ProfileImageStackVerticalLineROI, editors._DefaultImageStackProfileRoiEditor),
+ (rois.ProfileImageStackLineROI, editors._DefaultImageStackProfileRoiEditor),
+ (rois.ProfileImageStackCrossROI, editors._DefaultImageStackProfileRoiEditor),
+ ]
+ with self.defaultPlot() as plot:
+ profileManager = manager.ProfileManager(plot, plot)
+ editorAction = profileManager.createEditorAction(parent=plot)
+ for roiClass, editorClass in roiClasses:
+ with self.subTest(roiClass=roiClass):
+ roi = roiClass()
+ roi._setProfileManager(profileManager)
+ try:
+ # Force widget creation
+ menu = qt.QMenu(plot)
+ menu.addAction(editorAction)
+ widgets = editorAction.createdWidgets()
+ self.assertGreater(len(widgets), 0)
+
+ editorAction.setProfileRoi(roi)
+ editorWidget = editorAction._getEditor(widgets[0])
+ self.assertIsInstance(editorWidget, editorClass)
+ self.genericEditorTest(plot, roi, editorWidget)
+ finally:
+ editorAction.setProfileRoi(None)
+ menu.deleteLater()
+ menu = None
+ self.qapp.processEvents()
+
+
+class TestProfileToolBar(TestCaseQt, ParametricTestCase):
+ """Tests for ProfileToolBar widget."""
+
+ def setUp(self):
+ super(TestProfileToolBar, self).setUp()
+ self.plot = PlotWindow()
+ self.toolBar = Profile.ProfileToolBar(plot=self.plot)
+ self.plot.addToolBar(self.toolBar)
+
+ self.plot.show()
+ self.qWaitForWindowExposed(self.plot)
+
+ self.mouseMove(self.plot) # Move to center
+ self.qapp.processEvents()
+ deprecation.FORCE = True
+
+ def tearDown(self):
+ deprecation.FORCE = False
+ self.qapp.processEvents()
+ profileManager = self.toolBar.getProfileManager()
+ profileManager.clearProfile()
+ profileManager = None
+ self.plot.setAttribute(qt.Qt.WA_DeleteOnClose)
+ self.plot.close()
+ del self.plot
+ del self.toolBar
+
+ super(TestProfileToolBar, self).tearDown()
+
+ def testAlignedProfile(self):
+ """Test horizontal and vertical profile, without and with image"""
+ # Use Plot backend widget to submit mouse events
+ widget = self.plot.getWidgetHandle()
+ for method in ('sum', 'mean'):
+ with self.subTest(method=method):
+ # 2 positions to use for mouse events
+ pos1 = widget.width() * 0.4, widget.height() * 0.4
+ pos2 = widget.width() * 0.6, widget.height() * 0.6
+
+ for action in (self.toolBar.hLineAction, self.toolBar.vLineAction):
+ with self.subTest(mode=action.text()):
+ # Trigger tool button for mode
+ action.trigger()
+ # Without image
+ self.mouseMove(widget, pos=pos1)
+ self.mouseClick(widget, qt.Qt.LeftButton, pos=pos1)
+
+ # with image
+ self.plot.addImage(
+ numpy.arange(100 * 100).reshape(100, -1))
+ self.mousePress(widget, qt.Qt.LeftButton, pos=pos1)
+ self.mouseMove(widget, pos=pos2)
+ self.mouseRelease(widget, qt.Qt.LeftButton, pos=pos2)
+
+ self.mouseMove(widget)
+ self.mouseClick(widget, qt.Qt.LeftButton)
+
+ manager = self.toolBar.getProfileManager()
+ for _ in range(20):
+ self.qWait(200)
+ if not manager.hasPendingOperations():
+ break
+
+ @testutils.test_logging(deprecation.depreclog.name, warning=4)
+ def testDiagonalProfile(self):
+ """Test diagonal profile, without and with image"""
+ # Use Plot backend widget to submit mouse events
+ widget = self.plot.getWidgetHandle()
+
+ for method in ('sum', 'mean'):
+ for image in (False, True):
+ with self.subTest(method=method, image=image):
+ # 2 positions to use for mouse events
+ pos1 = widget.width() * 0.4, widget.height() * 0.4
+ pos2 = widget.width() * 0.6, widget.height() * 0.6
+
+ if image:
+ self.plot.addImage(
+ numpy.arange(100 * 100).reshape(100, -1))
+
+ # Trigger tool button for diagonal profile mode
+ self.toolBar.lineAction.trigger()
+
+ # draw profile line
+ widget.setFocus(qt.Qt.OtherFocusReason)
+ self.mouseMove(widget, pos=pos1)
+ self.qWait(100)
+ self.mousePress(widget, qt.Qt.LeftButton, pos=pos1)
+ self.qWait(100)
+ self.mouseMove(widget, pos=pos2)
+ self.qWait(100)
+ self.mouseRelease(widget, qt.Qt.LeftButton, pos=pos2)
+ self.qWait(100)
+
+ manager = self.toolBar.getProfileManager()
+
+ for _ in range(20):
+ self.qWait(200)
+ if not manager.hasPendingOperations():
+ break
+
+ roi = manager.getCurrentRoi()
+ self.assertIsNotNone(roi)
+ roi.setProfileLineWidth(3)
+ roi.setProfileMethod(method)
+
+ for _ in range(20):
+ self.qWait(200)
+ if not manager.hasPendingOperations():
+ break
+
+ if image is True:
+ curveItem = self.toolBar.getProfilePlot().getAllCurves()[0]
+ if method == 'sum':
+ self.assertTrue(curveItem.getData()[1].max() > 10000)
+ elif method == 'mean':
+ self.assertTrue(curveItem.getData()[1].max() < 10000)
+
+ # Remove the ROI so the profile window is also removed
+ roiManager = manager.getRoiManager()
+ roiManager.removeRoi(roi)
+ self.qWait(100)
+
+
+class TestDeprecatedProfileToolBar(TestCaseQt):
+ """Tests old features of the ProfileToolBar widget."""
+
+ def setUp(self):
+ self.plot = None
+ super(TestDeprecatedProfileToolBar, self).setUp()
+
+ def tearDown(self):
+ if self.plot is not None:
+ self.plot.setAttribute(qt.Qt.WA_DeleteOnClose)
+ self.plot.close()
+ self.plot = None
+ self.qWait()
+
+ super(TestDeprecatedProfileToolBar, self).tearDown()
+
+ @testutils.test_logging(deprecation.depreclog.name, warning=2)
+ def testCustomProfileWindow(self):
+ from silx.gui.plot import ProfileMainWindow
+
+ self.plot = PlotWindow()
+ profileWindow = ProfileMainWindow.ProfileMainWindow(self.plot)
+ toolBar = Profile.ProfileToolBar(parent=self.plot,
+ plot=self.plot,
+ profileWindow=profileWindow)
+
+ self.plot.show()
+ self.qWaitForWindowExposed(self.plot)
+ profileWindow.show()
+ self.qWaitForWindowExposed(profileWindow)
+ self.qapp.processEvents()
+
+ self.plot.addImage(numpy.arange(10 * 10).reshape(10, -1))
+ profile = rois.ProfileImageHorizontalLineROI()
+ profile.setPosition(5)
+ toolBar.getProfileManager().getRoiManager().addRoi(profile)
+ toolBar.getProfileManager().getRoiManager().setCurrentRoi(profile)
+
+ for _ in range(20):
+ self.qWait(200)
+ if not toolBar.getProfileManager().hasPendingOperations():
+ break
+
+ # There is a displayed profile
+ self.assertIsNotNone(profileWindow.getProfile())
+ self.assertIs(toolBar.getProfileMainWindow(), profileWindow)
+
+ # There is nothing anymore but the window is still there
+ toolBar.getProfileManager().clearProfile()
+ self.qapp.processEvents()
+ self.assertIsNone(profileWindow.getProfile())
+
+
+class TestProfile3DToolBar(TestCaseQt):
+ """Tests for Profile3DToolBar widget.
+ """
+ def setUp(self):
+ super(TestProfile3DToolBar, self).setUp()
+ self.plot = StackView()
+ self.plot.show()
+ self.qWaitForWindowExposed(self.plot)
+
+ self.plot.setStack(numpy.array([
+ [[0, 1, 2], [3, 4, 5]],
+ [[6, 7, 8], [9, 10, 11]],
+ [[12, 13, 14], [15, 16, 17]]
+ ]))
+ deprecation.FORCE = True
+
+ def tearDown(self):
+ deprecation.FORCE = False
+ profileManager = self.plot.getProfileToolbar().getProfileManager()
+ profileManager.clearProfile()
+ profileManager = None
+ self.plot.setAttribute(qt.Qt.WA_DeleteOnClose)
+ self.plot.close()
+ self.plot = None
+
+ super(TestProfile3DToolBar, self).tearDown()
+
+ @testutils.test_logging(deprecation.depreclog.name, warning=2)
+ def testMethodProfile2D(self):
+ """Test that the profile can have a different method if we want to
+ compute then in 1D or in 2D"""
+
+ toolBar = self.plot.getProfileToolbar()
+
+ toolBar.vLineAction.trigger()
+ plot2D = self.plot.getPlotWidget().getWidgetHandle()
+ pos1 = plot2D.width() * 0.5, plot2D.height() * 0.5
+ self.mouseClick(plot2D, qt.Qt.LeftButton, pos=pos1)
+
+ manager = toolBar.getProfileManager()
+ roi = manager.getCurrentRoi()
+ roi.setProfileMethod("mean")
+ roi.setProfileType("2D")
+ roi.setProfileLineWidth(3)
+
+ for _ in range(20):
+ self.qWait(200)
+ if not manager.hasPendingOperations():
+ break
+
+ # check 2D 'mean' profile
+ profilePlot = toolBar.getProfilePlot()
+ data = profilePlot.getAllImages()[0].getData()
+ expected = numpy.array([[1, 4], [7, 10], [13, 16]])
+ numpy.testing.assert_almost_equal(data, expected)
+
+ @testutils.test_logging(deprecation.depreclog.name, warning=2)
+ def testMethodSumLine(self):
+ """Simple interaction test to make sure the sum is correctly computed
+ """
+ toolBar = self.plot.getProfileToolbar()
+
+ toolBar.lineAction.trigger()
+ plot2D = self.plot.getPlotWidget().getWidgetHandle()
+ pos1 = plot2D.width() * 0.5, plot2D.height() * 0.2
+ pos2 = plot2D.width() * 0.5, plot2D.height() * 0.8
+
+ self.mouseMove(plot2D, pos=pos1)
+ self.mousePress(plot2D, qt.Qt.LeftButton, pos=pos1)
+ self.mouseMove(plot2D, pos=pos2)
+ self.mouseRelease(plot2D, qt.Qt.LeftButton, pos=pos2)
+
+ manager = toolBar.getProfileManager()
+ roi = manager.getCurrentRoi()
+ roi.setProfileMethod("sum")
+ roi.setProfileType("2D")
+ roi.setProfileLineWidth(3)
+
+ for _ in range(20):
+ self.qWait(200)
+ if not manager.hasPendingOperations():
+ break
+
+ # check 2D 'sum' profile
+ profilePlot = toolBar.getProfilePlot()
+ data = profilePlot.getAllImages()[0].getData()
+ expected = numpy.array([[3, 12], [21, 30], [39, 48]])
+ numpy.testing.assert_almost_equal(data, expected)
+
+
+class TestGetProfilePlot(TestCaseQt):
+
+ def setUp(self):
+ self.plot = None
+ super(TestGetProfilePlot, self).setUp()
+
+ def tearDown(self):
+ if self.plot is not None:
+ manager = self.plot.getProfileToolbar().getProfileManager()
+ manager.clearProfile()
+ manager = None
+ self.plot.setAttribute(qt.Qt.WA_DeleteOnClose)
+ self.plot.close()
+ self.plot = None
+
+ super(TestGetProfilePlot, self).tearDown()
+
+ def testProfile1D(self):
+ self.plot = Plot2D()
+ self.plot.show()
+ self.qWaitForWindowExposed(self.plot)
+ self.plot.addImage([[0, 1], [2, 3]])
+
+ toolBar = self.plot.getProfileToolbar()
+
+ manager = toolBar.getProfileManager()
+ roiManager = manager.getRoiManager()
+
+ roi = rois.ProfileImageHorizontalLineROI()
+ roi.setPosition(0.5)
+ roiManager.addRoi(roi)
+ roiManager.setCurrentRoi(roi)
+
+ for _ in range(20):
+ self.qWait(200)
+ if not manager.hasPendingOperations():
+ break
+
+ profileWindow = roi.getProfileWindow()
+ self.assertIsInstance(roi.getProfileWindow(), qt.QMainWindow)
+ self.assertIsInstance(profileWindow.getCurrentPlotWidget(), Plot1D)
+
+ def testProfile2D(self):
+ """Test that the profile plot associated to a stack view is either a
+ Plot1D or a plot 2D instance."""
+ self.plot = StackView()
+ self.plot.show()
+ self.qWaitForWindowExposed(self.plot)
+
+ self.plot.setStack(numpy.array([[[0, 1], [2, 3]],
+ [[4, 5], [6, 7]]]))
+
+ toolBar = self.plot.getProfileToolbar()
+
+ manager = toolBar.getProfileManager()
+ roiManager = manager.getRoiManager()
+
+ roi = rois.ProfileImageStackHorizontalLineROI()
+ roi.setPosition(0.5)
+ roi.setProfileType("2D")
+ roiManager.addRoi(roi)
+ roiManager.setCurrentRoi(roi)
+
+ for _ in range(20):
+ self.qWait(200)
+ if not manager.hasPendingOperations():
+ break
+
+ profileWindow = roi.getProfileWindow()
+ self.assertIsInstance(roi.getProfileWindow(), qt.QMainWindow)
+ self.assertIsInstance(profileWindow.getCurrentPlotWidget(), Plot2D)
+
+ roi.setProfileType("1D")
+
+ for _ in range(20):
+ self.qWait(200)
+ if not manager.hasPendingOperations():
+ break
+
+ profileWindow = roi.getProfileWindow()
+ self.assertIsInstance(roi.getProfileWindow(), qt.QMainWindow)
+ self.assertIsInstance(profileWindow.getCurrentPlotWidget(), Plot1D)
+
+
+def suite():
+ test_suite = unittest.TestSuite()
+ loadTests = unittest.defaultTestLoader.loadTestsFromTestCase
+ test_suite.addTest(loadTests(TestRois))
+ test_suite.addTest(loadTests(TestInteractions))
+ test_suite.addTest(loadTests(TestProfileToolBar))
+ test_suite.addTest(loadTests(TestGetProfilePlot))
+ test_suite.addTest(loadTests(TestProfile3DToolBar))
+ test_suite.addTest(loadTests(TestDeprecatedProfileToolBar))
+ return test_suite
+
+
+if __name__ == '__main__':
+ unittest.main(defaultTest='suite')
diff --git a/silx/gui/plot/tools/test/testROI.py b/silx/gui/plot/tools/test/testROI.py
index 8aec1d9..33a0000 100644
--- a/silx/gui/plot/tools/test/testROI.py
+++ b/silx/gui/plot/tools/test/testROI.py
@@ -1,7 +1,7 @@
# coding: utf-8
# /*##########################################################################
#
-# Copyright (c) 2018 European Synchrotron Radiation Facility
+# Copyright (c) 2018-2020 European Synchrotron Radiation Facility
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -60,7 +60,7 @@ class TestRoiItems(TestCaseQt):
def testPoint_geometry(self):
point = numpy.array([1, 2])
- item = roi_items.VerticalLineROI()
+ item = roi_items.PointROI()
item.setPosition(point)
numpy.testing.assert_allclose(item.getPosition(), point)
@@ -108,6 +108,43 @@ class TestRoiItems(TestCaseQt):
numpy.testing.assert_allclose(item.getCenter(), expectedCenter)
numpy.testing.assert_allclose(item.getSize(), size)
+ def testCircle_geometry(self):
+ center = numpy.array([0, 0])
+ radius = 10.
+ item = roi_items.CircleROI()
+ item.setGeometry(center=center, radius=radius)
+ numpy.testing.assert_allclose(item.getCenter(), center)
+ numpy.testing.assert_allclose(item.getRadius(), radius)
+
+ def testCircle_setCenter(self):
+ center = numpy.array([0, 0])
+ radius = 10.
+ item = roi_items.CircleROI()
+ item.setGeometry(center=center, radius=radius)
+ newCenter = numpy.array([-10, 0])
+ item.setCenter(newCenter)
+ numpy.testing.assert_allclose(item.getCenter(), newCenter)
+ numpy.testing.assert_allclose(item.getRadius(), radius)
+
+ def testCircle_setRadius(self):
+ center = numpy.array([0, 0])
+ radius = 10.
+ item = roi_items.CircleROI()
+ item.setGeometry(center=center, radius=radius)
+ newRadius = 5.1
+ item.setRadius(newRadius)
+ numpy.testing.assert_allclose(item.getCenter(), center)
+ numpy.testing.assert_allclose(item.getRadius(), newRadius)
+
+ def testRectangle_isIn(self):
+ origin = numpy.array([0, 0])
+ size = numpy.array([10, 20])
+ item = roi_items.RectangleROI()
+ item.setGeometry(origin=origin, size=size)
+ self.assertTrue(item.contains(position=(0, 0)))
+ self.assertTrue(item.contains(position=(2, 14)))
+ self.assertFalse(item.contains(position=(14, 12)))
+
def testPolygon_emptyGeometry(self):
points = numpy.empty((0, 2))
item = roi_items.PolygonROI()
@@ -120,6 +157,17 @@ class TestRoiItems(TestCaseQt):
item.setPoints(points)
numpy.testing.assert_allclose(item.getPoints(), points)
+ def testPolygon_isIn(self):
+ points = numpy.array([[0, 0], [0, 10], [5, 10]])
+ item = roi_items.PolygonROI()
+ item.setPoints(points)
+ self.assertTrue(item.contains((0, 0)))
+ self.assertFalse(item.contains((6, 2)))
+ self.assertFalse(item.contains((-2, 5)))
+ self.assertFalse(item.contains((2, -1)))
+ self.assertFalse(item.contains((8, 1)))
+ self.assertTrue(item.contains((1, 8)))
+
def testArc_getToSetGeometry(self):
"""Test that we can use getGeometry as input to setGeometry"""
item = roi_items.ArcROI()
@@ -147,7 +195,7 @@ class TestRoiItems(TestCaseQt):
self.assertAlmostEqual(item.getInnerRadius(), innerRadius)
self.assertAlmostEqual(item.getOuterRadius(), outerRadius)
self.assertAlmostEqual(item.getStartAngle(), item.getEndAngle() - numpy.pi * 2.0)
- self.assertAlmostEqual(item.isClosed(), True)
+ self.assertTrue(item.isClosed())
def testArc_special_donut(self):
item = roi_items.ArcROI()
@@ -158,7 +206,7 @@ class TestRoiItems(TestCaseQt):
self.assertAlmostEqual(item.getInnerRadius(), innerRadius)
self.assertAlmostEqual(item.getOuterRadius(), outerRadius)
self.assertAlmostEqual(item.getStartAngle(), item.getEndAngle() - numpy.pi * 2.0)
- self.assertAlmostEqual(item.isClosed(), True)
+ self.assertTrue(item.isClosed())
def testArc_clockwiseGeometry(self):
"""Test that we can use getGeometry as input to setGeometry"""
@@ -186,6 +234,15 @@ class TestRoiItems(TestCaseQt):
self.assertAlmostEqual(item.getEndAngle(), endAngle)
self.assertAlmostEqual(item.isClosed(), False)
+ def testHRange_geometry(self):
+ item = roi_items.HorizontalRangeROI()
+ vmin = 1
+ vmax = 3
+ item.setRange(vmin, vmax)
+ self.assertAlmostEqual(item.getMin(), vmin)
+ self.assertAlmostEqual(item.getMax(), vmax)
+ self.assertAlmostEqual(item.getCenter(), 2)
+
class TestRegionOfInterestManager(TestCaseQt, ParametricTestCase):
"""Tests for RegionOfInterestManager class"""
@@ -229,6 +286,9 @@ class TestRegionOfInterestManager(TestCaseQt, ParametricTestCase):
(roi_items.VerticalLineROI,
numpy.array((((10., 20.), (10., 30.)),
((30., 40.), (30., 50.))))),
+ (roi_items.HorizontalLineROI,
+ numpy.array((((10., 20.), (10., 30.)),
+ ((30., 40.), (30., 50.))))),
)
for roiClass, points in tests:
@@ -246,7 +306,9 @@ class TestRegionOfInterestManager(TestCaseQt, ParametricTestCase):
manager.sigRoiChanged.connect(changedListener)
# Add a point
- manager.createRoi(roiClass, points[0])
+ r = roiClass()
+ r.setFirstShapePoints(points[0])
+ manager.addRoi(r)
self.qapp.processEvents()
self.assertTrue(len(manager.getRois()), 1)
self.assertEqual(changedListener.callCount(), 1)
@@ -257,9 +319,13 @@ class TestRegionOfInterestManager(TestCaseQt, ParametricTestCase):
self.assertEqual(changedListener.callCount(), 2)
# Add two point
- manager.createRoi(roiClass, points[0])
+ r = roiClass()
+ r.setFirstShapePoints(points[0])
+ manager.addRoi(r)
self.qapp.processEvents()
- manager.createRoi(roiClass, points[1])
+ r = roiClass()
+ r.setFirstShapePoints(points[1])
+ manager.addRoi(r)
self.qapp.processEvents()
self.assertTrue(len(manager.getRois()), 2)
self.assertEqual(changedListener.callCount(), 4)
@@ -273,9 +339,13 @@ class TestRegionOfInterestManager(TestCaseQt, ParametricTestCase):
changedListener.clear()
# Add two point
- manager.createRoi(roiClass, points[0])
+ r = roiClass()
+ r.setFirstShapePoints(points[0])
+ manager.addRoi(r)
self.qapp.processEvents()
- manager.createRoi(roiClass, points[1])
+ r = roiClass()
+ r.setFirstShapePoints(points[1])
+ manager.addRoi(r)
self.qapp.processEvents()
self.assertTrue(len(manager.getRois()), 2)
self.assertEqual(changedListener.callCount(), 2)
@@ -356,6 +426,10 @@ class TestRegionOfInterestManager(TestCaseQt, ParametricTestCase):
innerRadius, outerRadius, startAngle, endAngle = 1, 100, numpy.pi * 0.5, numpy.pi
item.setGeometry(center, innerRadius, outerRadius, startAngle, endAngle)
rois.append(item)
+ # Horizontal Range
+ item = roi_items.HorizontalRangeROI()
+ item.setRange(-1, 3)
+ rois.append(item)
manager = roi.RegionOfInterestManager(self.plot)
self.roiTableWidget.setRegionOfInterestManager(manager)
@@ -370,6 +444,25 @@ class TestRegionOfInterestManager(TestCaseQt, ParametricTestCase):
manager.removeRoi(item)
self.qapp.processEvents()
+ def testSelectionProxy(self):
+ item1 = roi_items.PointROI()
+ item1.setSelectable(True)
+ item2 = roi_items.PointROI()
+ item2.setSelectable(True)
+ item1.setFocusProxy(item2)
+ manager = roi.RegionOfInterestManager(self.plot)
+ manager.setCurrentRoi(item1)
+ self.assertIs(manager.getCurrentRoi(), item2)
+
+ def testRemovedSelection(self):
+ item1 = roi_items.PointROI()
+ item1.setSelectable(True)
+ manager = roi.RegionOfInterestManager(self.plot)
+ manager.addRoi(item1)
+ manager.setCurrentRoi(item1)
+ manager.removeRoi(item1)
+ self.assertIs(manager.getCurrentRoi(), None)
+
def testMaxROI(self):
"""Test Max ROI"""
origin1 = numpy.array([1., 10.])
@@ -443,6 +536,86 @@ class TestRegionOfInterestManager(TestCaseQt, ParametricTestCase):
manager.clear()
+ def testLineInteraction(self):
+ """This test make sure that a ROI based on handles can be edited with
+ the mouse."""
+ xlimit = self.plot.getXAxis().getLimits()
+ ylimit = self.plot.getYAxis().getLimits()
+ points = numpy.array([xlimit, ylimit]).T
+ center = numpy.mean(points, axis=0)
+
+ # Create the line
+ manager = roi.RegionOfInterestManager(self.plot)
+ item = roi_items.LineROI()
+ item.setEndPoints(points[0], points[1])
+ item.setEditable(True)
+ manager.addRoi(item)
+ self.qapp.processEvents()
+
+ # Drag the center
+ widget = self.plot.getWidgetHandle()
+ mx, my = self.plot.dataToPixel(*center)
+ self.mouseMove(widget, pos=(mx, my))
+ self.mousePress(widget, qt.Qt.LeftButton, pos=(mx, my))
+ self.mouseMove(widget, pos=(mx, my+50))
+ self.mouseRelease(widget, qt.Qt.LeftButton, pos=(mx, my))
+
+ result = numpy.array(item.getEndPoints())
+ # x location is still the same
+ numpy.testing.assert_allclose(points[:, 0], result[:, 0], atol=0.5)
+ # size is still the same
+ numpy.testing.assert_allclose(points[1] - points[0],
+ result[1] - result[0], atol=0.5)
+ # But Y is not the same
+ self.assertNotEqual(points[0, 1], result[0, 1])
+ self.assertNotEqual(points[1, 1], result[1, 1])
+ item = None
+ manager.clear()
+ self.qapp.processEvents()
+
+ def testPlotWhenCleared(self):
+ """PlotWidget.clear should clean up the available ROIs"""
+ manager = roi.RegionOfInterestManager(self.plot)
+ item = roi_items.LineROI()
+ item.setEndPoints((0, 0), (1, 1))
+ item.setEditable(True)
+ manager.addRoi(item)
+ self.qWait()
+ try:
+ # Make sure the test setup is fine
+ self.assertNotEqual(len(manager.getRois()), 0)
+ self.assertNotEqual(len(self.plot.getItems()), 0)
+
+ # Call clear and test the expected state
+ self.plot.clear()
+ self.assertEqual(len(manager.getRois()), 0)
+ self.assertEqual(len(self.plot.getItems()), 0)
+ finally:
+ # Clean up
+ manager.clear()
+
+ def testPlotWhenRoiRemoved(self):
+ """Make sure there is no remaining items in the plot when a ROI is removed"""
+ manager = roi.RegionOfInterestManager(self.plot)
+ item = roi_items.LineROI()
+ item.setEndPoints((0, 0), (1, 1))
+ item.setEditable(True)
+ manager.addRoi(item)
+ self.qWait()
+ try:
+ # Make sure the test setup is fine
+ self.assertNotEqual(len(manager.getRois()), 0)
+ self.assertNotEqual(len(self.plot.getItems()), 0)
+
+ # Call clear and test the expected state
+ manager.removeRoi(item)
+ self.assertEqual(len(manager.getRois()), 0)
+ self.assertEqual(len(self.plot.getItems()), 0)
+ finally:
+ # Clean up
+ manager.clear()
+
+
def suite():
test_suite = unittest.TestSuite()
diff --git a/silx/gui/plot/tools/test/testScatterProfileToolBar.py b/silx/gui/plot/tools/test/testScatterProfileToolBar.py
index 714746a..b9f4885 100644
--- a/silx/gui/plot/tools/test/testScatterProfileToolBar.py
+++ b/silx/gui/plot/tools/test/testScatterProfileToolBar.py
@@ -34,8 +34,9 @@ from silx.gui import qt
from silx.utils.testutils import ParametricTestCase
from silx.gui.utils.testutils import TestCaseQt
from silx.gui.plot import PlotWindow
-from silx.gui.plot.tools import profile
-import silx.gui.plot.items.roi as roi_items
+from silx.gui.plot.tools.profile import manager
+from silx.gui.plot.tools.profile import core
+from silx.gui.plot.tools.profile import rois
class TestScatterProfileToolBar(TestCaseQt, ParametricTestCase):
@@ -45,40 +46,25 @@ class TestScatterProfileToolBar(TestCaseQt, ParametricTestCase):
super(TestScatterProfileToolBar, self).setUp()
self.plot = PlotWindow()
- self.profile = profile.ScatterProfileToolBar(plot=self.plot)
-
- self.plot.addToolBar(self.profile)
+ self.manager = manager.ProfileManager(plot=self.plot)
+ self.manager.setItemType(scatter=True)
+ self.manager.setActiveItemTracking(True)
self.plot.show()
self.qWaitForWindowExposed(self.plot)
def tearDown(self):
- del self.profile
+ del self.manager
self.qapp.processEvents()
self.plot.setAttribute(qt.Qt.WA_DeleteOnClose)
self.plot.close()
del self.plot
super(TestScatterProfileToolBar, self).tearDown()
- def testNoProfile(self):
- """Test ScatterProfileToolBar without profile"""
- self.assertEqual(self.profile.getPlotWidget(), self.plot)
-
- # Add a scatter plot
- self.plot.addScatter(
- x=(0., 1., 1., 0.), y=(0., 0., 1., 1.), value=(0., 1., 2., 3.))
- self.plot.resetZoom(dataMargins=(.1, .1, .1, .1))
- self.qapp.processEvents()
-
- # Check that there is no profile
- self.assertIsNone(self.profile.getProfileValues())
- self.assertIsNone(self.profile.getProfilePoints())
-
def testHorizontalProfile(self):
"""Test ScatterProfileToolBar horizontal profile"""
- nPoints = 8
- self.profile.setNPoints(nPoints)
- self.assertEqual(self.profile.getNPoints(), nPoints)
+
+ roiManager = self.manager.getRoiManager()
# Add a scatter plot
self.plot.addScatter(
@@ -86,46 +72,39 @@ class TestScatterProfileToolBar(TestCaseQt, ParametricTestCase):
self.plot.resetZoom(dataMargins=(.1, .1, .1, .1))
self.qapp.processEvents()
- # Activate Horizontal profile
- hlineAction = self.profile.actions()[0]
- hlineAction.trigger()
- self.qapp.processEvents()
-
# Set a ROI profile
- roi = roi_items.HorizontalLineROI()
+ roi = rois.ProfileScatterHorizontalLineROI()
roi.setPosition(0.5)
- self.profile._getRoiManager().addRoi(roi)
+ roi.setNPoints(8)
+ roiManager.addRoi(roi)
# Wait for async interpolator init
for _ in range(20):
self.qWait(200)
- if not self.profile.hasPendingOperations():
+ if not self.manager.hasPendingOperations():
break
self.qapp.processEvents()
- self.assertIsNotNone(self.profile.getProfileValues())
- points = self.profile.getProfilePoints()
- self.assertEqual(len(points), nPoints)
+ window = roi.getProfileWindow()
+ self.assertIsNotNone(window)
+ data = window.getProfile()
+ self.assertIsInstance(data, core.CurveProfileData)
+ self.assertEqual(len(data.coords), 8)
# Check that profile has same limits than Plot
xLimits = self.plot.getXAxis().getLimits()
- self.assertEqual(points[0, 0], xLimits[0])
- self.assertEqual(points[-1, 0], xLimits[1])
+ self.assertEqual(data.coords[0], xLimits[0])
+ self.assertEqual(data.coords[-1], xLimits[1])
# Clear the profile
- clearAction = self.profile.actions()[-1]
- clearAction.trigger()
+ self.manager.clearProfile()
self.qapp.processEvents()
-
- self.assertIsNone(self.profile.getProfileValues())
- self.assertIsNone(self.profile.getProfilePoints())
- self.assertEqual(self.profile.getProfileTitle(), '')
+ self.assertIsNone(roi.getProfileWindow())
def testVerticalProfile(self):
"""Test ScatterProfileToolBar vertical profile"""
- nPoints = 8
- self.profile.setNPoints(nPoints)
- self.assertEqual(self.profile.getNPoints(), nPoints)
+
+ roiManager = self.manager.getRoiManager()
# Add a scatter plot
self.plot.addScatter(
@@ -133,55 +112,52 @@ class TestScatterProfileToolBar(TestCaseQt, ParametricTestCase):
self.plot.resetZoom(dataMargins=(.1, .1, .1, .1))
self.qapp.processEvents()
- # Activate vertical profile
- vlineAction = self.profile.actions()[1]
- vlineAction.trigger()
- self.qapp.processEvents()
-
# Set a ROI profile
- roi = roi_items.VerticalLineROI()
+ roi = rois.ProfileScatterVerticalLineROI()
roi.setPosition(0.5)
- self.profile._getRoiManager().addRoi(roi)
+ roi.setNPoints(8)
+ roiManager.addRoi(roi)
# Wait for async interpolator init
for _ in range(10):
self.qWait(200)
- if not self.profile.hasPendingOperations():
+ if not self.manager.hasPendingOperations():
break
- self.assertIsNotNone(self.profile.getProfileValues())
- points = self.profile.getProfilePoints()
- self.assertEqual(len(points), nPoints)
+ window = roi.getProfileWindow()
+ self.assertIsNotNone(window)
+ data = window.getProfile()
+ self.assertIsInstance(data, core.CurveProfileData)
+ self.assertEqual(len(data.coords), 8)
# Check that profile has same limits than Plot
yLimits = self.plot.getYAxis().getLimits()
- self.assertEqual(points[0, 1], yLimits[0])
- self.assertEqual(points[-1, 1], yLimits[1])
+ self.assertEqual(data.coords[0], yLimits[0])
+ self.assertEqual(data.coords[-1], yLimits[1])
# Check that profile limits are updated when changing limits
self.plot.getYAxis().setLimits(yLimits[0] + 1, yLimits[1] + 10)
- self.qapp.processEvents()
+
+ # Wait for async interpolator init
+ for _ in range(10):
+ self.qWait(200)
+ if not self.manager.hasPendingOperations():
+ break
+
yLimits = self.plot.getYAxis().getLimits()
- points = self.profile.getProfilePoints()
- self.assertEqual(points[0, 1], yLimits[0])
- self.assertEqual(points[-1, 1], yLimits[1])
+ data = window.getProfile()
+ self.assertEqual(data.coords[0], yLimits[0])
+ self.assertEqual(data.coords[-1], yLimits[1])
- # Clear the plot
- self.plot.clear()
+ # Clear the profile
+ self.manager.clearProfile()
self.qapp.processEvents()
- self.assertIsNone(self.profile.getProfileValues())
- self.assertIsNone(self.profile.getProfilePoints())
+ self.assertIsNone(roi.getProfileWindow())
def testLineProfile(self):
"""Test ScatterProfileToolBar line profile"""
- nPoints = 8
- self.profile.setNPoints(nPoints)
- self.assertEqual(self.profile.getNPoints(), nPoints)
- # Activate line profile
- lineAction = self.profile.actions()[2]
- lineAction.trigger()
- self.qapp.processEvents()
+ roiManager = self.manager.getRoiManager()
# Add a scatter plot
self.plot.addScatter(
@@ -190,19 +166,22 @@ class TestScatterProfileToolBar(TestCaseQt, ParametricTestCase):
self.qapp.processEvents()
# Set a ROI profile
- roi = roi_items.LineROI()
+ roi = rois.ProfileScatterLineROI()
roi.setEndPoints(numpy.array([0., 0.]), numpy.array([1., 1.]))
- self.profile._getRoiManager().addRoi(roi)
+ roi.setNPoints(8)
+ roiManager.addRoi(roi)
# Wait for async interpolator init
for _ in range(10):
self.qWait(200)
- if not self.profile.hasPendingOperations():
+ if not self.manager.hasPendingOperations():
break
- self.assertIsNotNone(self.profile.getProfileValues())
- points = self.profile.getProfilePoints()
- self.assertEqual(len(points), nPoints)
+ window = roi.getProfileWindow()
+ self.assertIsNotNone(window)
+ data = window.getProfile()
+ self.assertIsInstance(data, core.CurveProfileData)
+ self.assertEqual(len(data.coords), 8)
def suite():