Example #1
0
    def testStop(self):
        """Test synchronization after calling stop"""
        sync = SyncAxes([self.plot1.getXAxis(), self.plot2.getXAxis(), self.plot3.getXAxis()])
        sync.stop()

        self.plot1.getXAxis().setLimits(10, 500)
        self.assertEqual(self.plot1.getXAxis().getLimits(), (10, 500))
        self.assertNotEqual(self.plot2.getXAxis().getLimits(), (10, 500))
        self.assertNotEqual(self.plot3.getXAxis().getLimits(), (10, 500))
Example #2
0
    def testAddAxis(self):
        """Test synchronization after construction"""
        sync = SyncAxes([self.plot1.getXAxis(), self.plot2.getXAxis()])
        sync.addAxis(self.plot3.getXAxis())

        self.plot1.getXAxis().setLimits(10, 500)
        self.assertEqual(self.plot1.getXAxis().getLimits(), (10, 500))
        self.assertEqual(self.plot2.getXAxis().getLimits(), (10, 500))
        self.assertEqual(self.plot3.getXAxis().getLimits(), (10, 500))
Example #3
0
    def testStop(self):
        """Test synchronization after calling stop"""
        sync = SyncAxes([self.plot1.getXAxis(), self.plot2.getXAxis(), self.plot3.getXAxis()])
        sync.stop()

        self.plot1.getXAxis().setLimits(10, 500)
        self.assertEqual(self.plot1.getXAxis().getLimits(), (10, 500))
        self.assertNotEqual(self.plot2.getXAxis().getLimits(), (10, 500))
        self.assertNotEqual(self.plot3.getXAxis().getLimits(), (10, 500))
Example #4
0
 def testDoubleStop(self):
     """Test double stop"""
     sync = SyncAxes([
         self.plot1.getXAxis(),
         self.plot2.getXAxis(),
         self.plot3.getXAxis()
     ])
     sync.stop()
     self.assertRaises(RuntimeError, sync.stop)
Example #5
0
    def testRemoveAxis(self):
        """Test synchronization after construction"""
        sync = SyncAxes([self.plot1.getXAxis(), self.plot2.getXAxis(), self.plot3.getXAxis()])
        sync.removeAxis(self.plot3.getXAxis())

        self.plot1.getXAxis().setLimits(10, 500)
        self.assertEqual(self.plot1.getXAxis().getLimits(), (10, 500))
        self.assertEqual(self.plot2.getXAxis().getLimits(), (10, 500))
        self.assertNotEqual(self.plot3.getXAxis().getLimits(), (10, 500))
    def __init__(self):
        qt.QMainWindow.__init__(self)
        self.setWindowTitle("Plot with synchronized axes")
        widget = qt.QWidget(self)
        self.setCentralWidget(widget)

        layout = qt.QGridLayout()
        widget.setLayout(layout)

        backend = "gl"
        plots = []

        data = numpy.arange(100 * 100)
        data = (data % 100) / 5.0
        data = numpy.sin(data)
        data.shape = 100, 100

        colormaps = ["gray", "red", "green", "blue"]
        for i in range(2 * 2):
            plot = Plot2D(parent=widget, backend=backend)
            plot.setInteractiveMode('pan')
            plot.setDefaultColormap(Colormap(colormaps[i]))
            noisyData = silx.test.utils.add_gaussian_noise(data, mean=i / 10.0)
            plot.addImage(noisyData)
            plots.append(plot)

        xAxis = [p.getXAxis() for p in plots]
        yAxis = [p.getYAxis() for p in plots]

        self.constraint1 = SyncAxes(xAxis,
                                    syncLimits=False,
                                    syncScale=True,
                                    syncDirection=True,
                                    syncCenter=True,
                                    syncZoom=True)
        self.constraint2 = SyncAxes(yAxis,
                                    syncLimits=False,
                                    syncScale=True,
                                    syncDirection=True,
                                    syncCenter=True,
                                    syncZoom=True)

        for i, plot in enumerate(plots):
            if i % 2 == 0:
                plot.setFixedWidth(400)
            else:
                plot.setFixedWidth(500)
            if i // 2 == 0:
                plot.setFixedHeight(400)
            else:
                plot.setFixedHeight(500)
            layout.addWidget(plot, i // 2, i % 2)
Example #7
0
    def testStopMovingStart(self):
        """Test synchronization after calling stop, moving an axis, then start again"""
        sync = SyncAxes([self.plot1.getXAxis(), self.plot2.getXAxis(), self.plot3.getXAxis()])
        sync.stop()
        self.plot1.getXAxis().setLimits(10, 500)
        self.plot2.getXAxis().setLimits(1, 50)
        self.assertEqual(self.plot1.getXAxis().getLimits(), (10, 500))
        sync.start()

        # The first axis is the reference
        self.assertEqual(self.plot1.getXAxis().getLimits(), (10, 500))
        self.assertEqual(self.plot2.getXAxis().getLimits(), (10, 500))
        self.assertEqual(self.plot3.getXAxis().getLimits(), (10, 500))
Example #8
0
    def __init__(self, parent=None):
        super(IntegrationPlot, self).__init__(parent)

        self.__plot1d, self.__plot2d = self.__createPlots(self)
        self.__statusBar = _StatusBar(self)
        self.__statusBar.setSizeGripEnabled(False)

        layout = qt.QVBoxLayout(self)
        layout.setContentsMargins(1, 1, 1, 1)
        layout.addWidget(self.__plot2d)
        layout.addWidget(self.__plot1d)
        layout.addWidget(self.__statusBar)
        self.__setResult(None)
        self.__processing1d = None
        self.__processing2d = None
        self.__ringItems = {}
        self.__axisOfCurrentView = None
        self.__angleUnderMouse = None
        self.__availableRings = None
        self.__radialUnit = None
        self.__wavelength = None
        self.__directDist = None
        self.__geometry = None
        self.__inverseGeometry = None

        markerModel = CalibrationContext.instance().getCalibrationModel(
        ).markerModel()
        self.__markerManager = MarkerManager(self.__plot2d, markerModel)

        self.__plot2d.getXAxis().sigLimitsChanged.connect(self.__axesChanged)
        self.__plot1d.sigPlotSignal.connect(self.__plot1dSignalReceived)
        self.__plot2d.sigPlotSignal.connect(self.__plot2dSignalReceived)

        self.__plotBackground = SynchronizePlotBackground(self.__plot2d)

        widget = self.__plot1d
        if hasattr(widget, "centralWidget"):
            widget.centralWidget()
        widget.installEventFilter(self)
        widget = self.__plot2d
        if hasattr(widget, "centralWidget"):
            widget.centralWidget()
        widget.installEventFilter(self)

        colormap = CalibrationContext.instance().getRawColormap()
        self.__plot2d.setDefaultColormap(colormap)

        from silx.gui.plot.utils.axis import SyncAxes
        self.__syncAxes = SyncAxes(
            [self.__plot1d.getXAxis(),
             self.__plot2d.getXAxis()])
Example #9
0
 def testDirection(self):
     """Test direction change"""
     _sync = SyncAxes([self.plot1.getYAxis(), self.plot2.getYAxis(), self.plot3.getYAxis()])
     self.plot1.getYAxis().setInverted(True)
     self.assertEqual(self.plot1.getYAxis().isInverted(), True)
     self.assertEqual(self.plot2.getYAxis().isInverted(), True)
     self.assertEqual(self.plot3.getYAxis().isInverted(), True)
Example #10
0
 def testScale(self):
     """Test scale change"""
     _sync = SyncAxes([self.plot1.getXAxis(), self.plot2.getXAxis(), self.plot3.getXAxis()])
     self.plot1.getXAxis().setScale(self.plot1.getXAxis().LOGARITHMIC)
     self.assertEqual(self.plot1.getXAxis().getScale(), self.plot1.getXAxis().LOGARITHMIC)
     self.assertEqual(self.plot2.getXAxis().getScale(), self.plot1.getXAxis().LOGARITHMIC)
     self.assertEqual(self.plot3.getXAxis().getScale(), self.plot1.getXAxis().LOGARITHMIC)
Example #11
0
    def testDestruction(self):
        """Test synchronization when sync object is destroyed"""
        sync = SyncAxes([self.plot1.getXAxis(), self.plot2.getXAxis(), self.plot3.getXAxis()])
        del sync

        self.plot1.getXAxis().setLimits(10, 500)
        self.assertEqual(self.plot1.getXAxis().getLimits(), (10, 500))
        self.assertNotEqual(self.plot2.getXAxis().getLimits(), (10, 500))
        self.assertNotEqual(self.plot3.getXAxis().getLimits(), (10, 500))
Example #12
0
    def testSyncCenter(self):
        """Test direction change"""
        # Not the same scale
        self.plot1.getXAxis().setLimits(0, 200)
        self.plot2.getXAxis().setLimits(0, 20)
        self.plot3.getXAxis().setLimits(0, 2)
        _sync = SyncAxes([self.plot1.getXAxis(), self.plot2.getXAxis(), self.plot3.getXAxis()],
                         syncLimits=False, syncCenter=True)

        self.assertEqual(self.plot1.getXAxis().getLimits(), (0, 200))
        self.assertEqual(self.plot2.getXAxis().getLimits(), (100 - 10, 100 + 10))
        self.assertEqual(self.plot3.getXAxis().getLimits(), (100 - 1, 100 + 1))
Example #13
0
    def testSyncCenterAndZoom(self):
        """Test direction change"""
        # Not the same scale
        self.plot1.getXAxis().setLimits(0, 200)
        self.plot2.getXAxis().setLimits(0, 20)
        self.plot3.getXAxis().setLimits(0, 2)
        _sync = SyncAxes([self.plot1.getXAxis(), self.plot2.getXAxis(), self.plot3.getXAxis()],
                         syncLimits=False, syncCenter=True, syncZoom=True)

        # Supposing all the plots use the same size
        self.assertEqual(self.plot1.getXAxis().getLimits(), (0, 200))
        self.assertEqual(self.plot2.getXAxis().getLimits(), (0, 200))
        self.assertEqual(self.plot3.getXAxis().getLimits(), (0, 200))
Example #14
0
    def testAxisDestruction(self):
        """Test synchronization when an axis disappear"""
        _sync = SyncAxes([self.plot1.getXAxis(), self.plot2.getXAxis(), self.plot3.getXAxis()])

        # Destroy the plot is possible
        import weakref
        plot = weakref.ref(self.plot2)
        self.plot2 = None
        result = self.qWaitForDestroy(plot)
        if not result:
            # We can't test
            self.skipTest("Object not destroyed")

        self.plot1.getXAxis().setLimits(10, 500)
        self.assertEqual(self.plot3.getXAxis().getLimits(), (10, 500))
Example #15
0
    def __init__(self, parent=None):
        super(IntegrationPlot, self).__init__(parent)

        self.__plot1d, self.__plot2d = self.__createPlots(self)
        self.__statusBar = _StatusBar(self)
        self.__statusBar.setSizeGripEnabled(False)

        layout = qt.QVBoxLayout(self)
        layout.setContentsMargins(1, 1, 1, 1)
        layout.addWidget(self.__plot2d)
        layout.addWidget(self.__plot1d)
        layout.addWidget(self.__statusBar)
        self.__setResult(None)
        self.__processing1d = None
        self.__processing2d = None
        self.__ringItems = {}
        self.__axisOfCurrentView = None
        self.__angleUnderMouse = None
        self.__availableRingAngles = None
        self.__radialUnit = None
        self.__wavelength = None
        self.__directDist = None
        self.__geometry = None
        self.__inverseGeometry = None

        markerModel = CalibrationContext.instance().getCalibrationModel().markerModel()
        self.__markerManager = MarkerManager(self.__plot2d, markerModel)

        self.__plot2d.getXAxis().sigLimitsChanged.connect(self.__axesChanged)
        self.__plot1d.sigPlotSignal.connect(self.__plot1dSignalReceived)
        self.__plot2d.sigPlotSignal.connect(self.__plot2dSignalReceived)

        self.__plotBackground = SynchronizePlotBackground(self.__plot2d)

        widget = self.__plot1d
        if hasattr(widget, "centralWidget"):
            widget.centralWidget()
        widget.installEventFilter(self)
        widget = self.__plot2d
        if hasattr(widget, "centralWidget"):
            widget.centralWidget()
        widget.installEventFilter(self)

        colormap = CalibrationContext.instance().getRawColormap()
        self.__plot2d.setDefaultColormap(colormap)

        from silx.gui.plot.utils.axis import SyncAxes
        self.__syncAxes = SyncAxes([self.__plot1d.getXAxis(), self.__plot2d.getXAxis()])
Example #16
0
    def testStopMovingStart(self):
        """Test synchronization after calling stop, moving an axis, then start again"""
        sync = SyncAxes([self.plot1.getXAxis(), self.plot2.getXAxis(), self.plot3.getXAxis()])
        sync.stop()
        self.plot1.getXAxis().setLimits(10, 500)
        self.plot2.getXAxis().setLimits(1, 50)
        self.assertEqual(self.plot1.getXAxis().getLimits(), (10, 500))
        sync.start()

        # The first axis is the reference
        self.assertEqual(self.plot1.getXAxis().getLimits(), (10, 500))
        self.assertEqual(self.plot2.getXAxis().getLimits(), (10, 500))
        self.assertEqual(self.plot3.getXAxis().getLimits(), (10, 500))
Example #17
0
class IntegrationPlot(qt.QFrame):
    def __init__(self, parent=None):
        super(IntegrationPlot, self).__init__(parent)

        self.__plot1d, self.__plot2d = self.__createPlots(self)
        self.__statusBar = _StatusBar(self)
        self.__statusBar.setSizeGripEnabled(False)

        layout = qt.QVBoxLayout(self)
        layout.setContentsMargins(1, 1, 1, 1)
        layout.addWidget(self.__plot2d)
        layout.addWidget(self.__plot1d)
        layout.addWidget(self.__statusBar)
        self.__setResult(None)
        self.__processing1d = None
        self.__processing2d = None
        self.__ringItems = {}
        self.__axisOfCurrentView = None
        self.__angleUnderMouse = None
        self.__availableRings = None
        self.__radialUnit = None
        self.__wavelength = None
        self.__directDist = None
        self.__geometry = None
        self.__inverseGeometry = None

        markerModel = CalibrationContext.instance().getCalibrationModel(
        ).markerModel()
        self.__markerManager = MarkerManager(self.__plot2d, markerModel)

        self.__plot2d.getXAxis().sigLimitsChanged.connect(self.__axesChanged)
        self.__plot1d.sigPlotSignal.connect(self.__plot1dSignalReceived)
        self.__plot2d.sigPlotSignal.connect(self.__plot2dSignalReceived)

        self.__plotBackground = SynchronizePlotBackground(self.__plot2d)

        widget = self.__plot1d
        if hasattr(widget, "centralWidget"):
            widget.centralWidget()
        widget.installEventFilter(self)
        widget = self.__plot2d
        if hasattr(widget, "centralWidget"):
            widget.centralWidget()
        widget.installEventFilter(self)

        colormap = CalibrationContext.instance().getRawColormap()
        self.__plot2d.setDefaultColormap(colormap)

        from silx.gui.plot.utils.axis import SyncAxes
        self.__syncAxes = SyncAxes(
            [self.__plot1d.getXAxis(),
             self.__plot2d.getXAxis()])

    def aboutToClose(self):
        # Avoid double free release problem. See #892
        self.__syncAxes.stop()
        self.__syncAxes = None

    def resetZoom(self):
        self.__plot1d.resetZoom()
        self.__plot2d.resetZoom()

    def hasData(self):
        return self.__result1d is not None

    def eventFilter(self, widget, event):
        if event.type() == qt.QEvent.Leave:
            self.__mouseLeave()
            return True

        if event.type() == qt.QEvent.ToolTip:
            if self.__availableRings is not None:
                pos = widget.mapFromGlobal(event.globalPos())
                coord = widget.pixelToData(pos.x(), pos.y())

                angle = coord[0]
                ringId, angle = self.__getClosestAngle(angle)

                if ringId is not None:
                    message = "%s ring" % stringutil.to_ordinal(ringId + 1)
                    qt.QToolTip.showText(event.globalPos(), message)
                else:
                    qt.QToolTip.hideText()
                    event.ignore()

                return True

        return False

    def __mouseLeave(self):
        self.__statusBar.clearValues()

        if self.__angleUnderMouse is None:
            return
        if self.__angleUnderMouse not in self.__displayedAngles:
            items = self.__ringItems.get(self.__angleUnderMouse, [])
            for item in items:
                item.setVisible(False)
        self.__angleUnderMouse = None

    def __plot1dSignalReceived(self, event):
        """Called when old style signals at emmited from the plot."""
        if event["event"] == "mouseMoved":
            x, y = event["x"], event["y"]
            self.__mouseMoved(x, y)
            self.__updateStatusBar(x, None)

    def __plot2dSignalReceived(self, event):
        """Called when old style signals at emmited from the plot."""
        if event["event"] == "mouseMoved":
            x, y = event["x"], event["y"]
            self.__mouseMoved(x, y)
            self.__updateStatusBar(x, y)

    def __getClosestAngle(self, angle):
        """
        Returns the closest ring index and ring angle
        """
        # TODO: Could be done in log(n) using bisect search
        result = None
        iresult = None
        minDistance = float("inf")
        for ringId, ringAngle in self.__availableRings:
            distance = abs(angle - ringAngle)
            if distance < minDistance:
                minDistance = distance
                result = ringAngle
                iresult = ringId
        return iresult, result

    def dataToChiTth(self, data):
        """Returns chi and 2theta angles in radian from data coordinate"""
        try:
            tthRad = unitutils.tthToRad(data[0],
                                        unit=self.__radialUnit,
                                        wavelength=self.__wavelength,
                                        directDist=self.__directDist)
        except Exception:
            _logger.debug("Backtrace", exc_info=True)
            tthRad = None

        chiDeg = data[1]
        if chiDeg is not None:
            chiRad = numpy.deg2rad(chiDeg)
        else:
            chiRad = None

        return chiRad, tthRad

    def __updateStatusBar(self, x, y):
        chiRad, tthRad = self.dataToChiTth((x, y))

        if y is not None and self.__inverseGeometry is not None:
            pixelY, pixelX = self.__inverseGeometry(x, y, True)
            ax, ay = numpy.array([pixelX]), numpy.array([pixelY])
            tthFromPixel = self.__geometry.tth(ay, ax)[0]
            chiFromPixel = self.__geometry.chi(ay, ax)[0]

            if tthRad is not None:
                error = numpy.sqrt((tthRad - tthFromPixel)**2 +
                                   (chiRad - chiFromPixel)**2)
                if error > 0.05:
                    # The identified pixel is far from the requested chi/tth. Marker ignored.
                    pixelY, pixelX = None, None
        else:
            pixelY, pixelX = None, None

        self.__statusBar.setValues(pixelX, pixelY, chiRad, tthRad)

    def __mouseMoved(self, x, y):
        """Called when mouse move over the plot."""
        if self.__availableRings is None:
            return
        angle = x
        ringId, angle = self.__getClosestAngle(angle)

        if angle == self.__angleUnderMouse:
            return

        if self.__angleUnderMouse not in self.__displayedAngles:
            items = self.__ringItems.get(self.__angleUnderMouse, [])
            for item in items:
                item.setVisible(False)

        self.__angleUnderMouse = angle

        if angle is not None:
            items = self.__getItemsFromAngle(ringId, angle)
            for item in items:
                item.setVisible(True)

    def __axesChanged(self, minValue, maxValue):
        axisOfCurrentView = self.__plot2d.getXAxis().getLimits()
        if self.__axisOfCurrentView == axisOfCurrentView:
            return
        self.__updateRings()

    def __getAvailableAngles(self, minTth, maxTth):
        result = []
        for ringId, angle in self.__availableRings:
            if minTth is None or maxTth is None:
                result.append(ringId, angle)
            if minTth <= angle <= maxTth:
                result.append((ringId, angle))
        return result

    def __updateRings(self):
        if self.__availableRings is None:
            return

        minTth, maxTth = self.__plot2d.getXAxis().getLimits()
        angles = self.__getAvailableAngles(minTth, maxTth)

        if len(angles) < 20:
            step = 1
        elif len(angles) < 100:
            step = 2
        elif len(angles) < 200:
            step = 5
        elif len(angles) < 500:
            step = 10
        elif len(angles) < 1000:
            step = 20
        elif len(angles) < 5000:
            step = 100
        else:
            step = int(len(angles) / 50)

        self.__displayedAngles = set([])

        for items in self.__ringItems.values():
            for item in items:
                item.setVisible(False)

        for angleId in range(0, len(angles), step):
            ringId, ringAngle = angles[angleId]
            self.__displayedAngles.add(ringAngle)
            items = self.__getItemsFromAngle(ringId, ringAngle)
            for item in items:
                item.setVisible(True)

    def __getItemsFromAngle(self, ringId, ringAngle):
        items = self.__ringItems.get(ringAngle, None)
        if items is not None:
            return items

        color = CalibrationContext.instance().getMarkerColor(ringId,
                                                             mode="numpy")
        items = []

        legend = "ring-%i" % (ringId, )

        self.__plot1d.addXMarker(x=ringAngle, color=color, legend=legend)
        item = self.__plot1d._getMarker(legend)
        items.append(item)

        self.__plot2d.addXMarker(x=ringAngle, color=color, legend=legend)
        item = self.__plot2d._getMarker(legend)
        items.append(item)

        self.__ringItems[ringAngle] = items
        return items

    def __syncModeToPlot1d(self, _event):
        modeDict = self.__plot2d.getInteractiveMode()
        mode = modeDict["mode"]
        self.__plot1d.setInteractiveMode(mode)

    def getDefaultColormap(self):
        return self.__plot2d.getDefaultColormap()

    def __createPlots(self, parent):
        margin = 0.02
        plot1d = silx.gui.plot.PlotWidget(parent)
        plot1d.setGraphXLabel("Radial unit")
        plot1d.setGraphYLabel("Intensity")
        plot1d.setGraphGrid(False)
        plot1d.setDataMargins(margin, margin, margin, margin)
        plot2d = silx.gui.plot.PlotWidget(parent)
        plot2d.setGraphXLabel("Radial unit")
        plot2d.setGraphYLabel(r"Azimuthal angle $\chi$ (°)")
        plot2d.sigInteractiveModeChanged.connect(self.__syncModeToPlot1d)
        plot2d.setDataMargins(margin, margin, margin, margin)

        handle = plot2d.getWidgetHandle()
        handle.setContextMenuPolicy(qt.Qt.CustomContextMenu)
        handle.customContextMenuRequested.connect(self.__plot2dContextMenu)

        from silx.gui.plot import tools
        toolBar = tools.InteractiveModeToolBar(parent=self, plot=plot2d)
        plot2d.addToolBar(toolBar)

        toolBar = tools.ImageToolBar(parent=self, plot=plot2d)
        colormapDialog = CalibrationContext.instance().getColormapDialog()
        toolBar.getColormapAction().setColorDialog(colormapDialog)
        previousResetZoomAction = toolBar.getResetZoomAction()
        resetZoomAction = qt.QAction(toolBar)
        resetZoomAction.triggered.connect(self.resetZoom)
        resetZoomAction.setIcon(previousResetZoomAction.icon())
        resetZoomAction.setText(previousResetZoomAction.text())
        resetZoomAction.setToolTip(previousResetZoomAction.toolTip())
        toolBar.insertAction(previousResetZoomAction, resetZoomAction)
        previousResetZoomAction.setVisible(False)
        self.__resetZoomAction = resetZoomAction
        plot2d.addToolBar(toolBar)

        ownToolBar = qt.QToolBar(plot2d)
        from silx.gui.plot import actions
        logAction = actions.control.YAxisLogarithmicAction(parent=ownToolBar,
                                                           plot=plot1d)
        logAction.setToolTip("Logarithmic y-axis intensity when checked")
        ownToolBar.addAction(logAction)
        plot2d.addToolBar(ownToolBar)

        action = qt.QAction(ownToolBar)
        action.setIcon(silx.gui.icons.getQIcon("document-save"))
        action.triggered.connect(self.__saveAsCsv)
        action.setToolTip("Save 1D integration as CSV file")
        self.__saveResult1dAction = action
        ownToolBar.addAction(action)

        return plot1d, plot2d

    def __plot2dContextMenu(self, pos):
        from silx.gui.plot.actions.control import ZoomBackAction
        zoomBackAction = ZoomBackAction(plot=self.__plot2d,
                                        parent=self.__plot2d)

        menu = qt.QMenu(self)

        menu.addAction(zoomBackAction)
        menu.addSeparator()
        menu.addAction(self.__markerManager.createMarkPixelAction(menu, pos))
        menu.addAction(self.__markerManager.createMarkGeometryAction(
            menu, pos))
        action = self.__markerManager.createRemoveClosestMaskerAction(
            menu, pos)
        if action is not None:
            menu.addAction(action)

        handle = self.__plot2d.getWidgetHandle()
        menu.exec_(handle.mapToGlobal(pos))

    def __clearRings(self):
        """Remove of ring item cached on the plots"""
        for items in self.__ringItems.values():
            for item in items:
                self.__plot1d.removeMarker(item.getLegend())
                self.__plot2d.removeMarker(item.getLegend())
        self.__ringItems = {}
        self.__availableRings = []

    def clear(self):
        self.__clearRings()
        try:
            self.__plot1d.remove("result1d", "histogram")
        except Exception:
            pass
        try:
            self.__plot2d.removeImage("integrated_mask")
        except Exception:
            pass
        try:
            self.__plot2d.removeImage("integrated_data")
        except Exception:
            pass

    def setIntegrationProcess(self, integrationProcess):
        """
        :param :class:`~pyFAI.gui.tasks.IntegrationTask.IntegrationProcess` integrationProcess:
            Result of the integration process
        """
        self.__clearRings()

        self.__availableRings = integrationProcess.rings()
        self.__updateRings()

        result1d = integrationProcess.result1d()
        self.__plot1d.addHistogram(legend="result1d",
                                   align="center",
                                   edges=result1d.radial,
                                   color="blue",
                                   histogram=result1d.intensity,
                                   resetzoom=False)
        self.__plot1d.setGraphXLabel(result1d.unit.label)
        self.__setResult(result1d)

        def compute_location(result):
            # Assume that axes are linear
            if result.intensity.shape[1] > 1:
                scaleX = (result.radial[-1] -
                          result.radial[0]) / (result.intensity.shape[1] - 1)
            else:
                scaleX = 1.0
            if result.intensity.shape[0] > 1:
                scaleY = (result.azimuthal[-1] - result.azimuthal[0]) / (
                    result.intensity.shape[0] - 1)
            else:
                scaleY = 1.0
            halfPixel = 0.5 * scaleX, 0.5 * scaleY
            origin = (result.radial[0] - halfPixel[0],
                      result.azimuthal[0] - halfPixel[1])
            return origin, scaleX, scaleY

        resultMask2d = integrationProcess.resultMask2d()
        isMaskDisplayed = resultMask2d is not None

        result2d = integrationProcess.result2d()
        result2d_intensity = result2d.intensity

        # Mask pixels with no data
        result2d_intensity[result2d.count == 0] = float("NaN")

        if isMaskDisplayed:
            maskedColor = CalibrationContext.instance().getMaskedColor()
            transparent = (0.0, 0.0, 0.0, 0.0)
            resultMask2d_rgba = imageutils.maskArrayToRgba(
                resultMask2d.count != 0,
                falseColor=transparent,
                trueColor=maskedColor)
            origin, scaleX, scaleY = compute_location(resultMask2d)
            self.__plot2d.addImage(legend="integrated_mask",
                                   data=resultMask2d_rgba,
                                   origin=origin,
                                   scale=(scaleX, scaleY),
                                   resetzoom=False)
        else:
            try:
                self.__plot2d.removeImage("integrated_mask")
            except Exception:
                pass

        colormap = self.getDefaultColormap()
        origin, scaleX, scaleY = compute_location(result2d)
        self.__plot2d.addImage(legend="integrated_data",
                               data=result2d_intensity,
                               origin=origin,
                               scale=(scaleX, scaleY),
                               colormap=colormap,
                               resetzoom=False)
        self.__plot2d.setGraphXLabel(result2d.unit.label)

        self.__radialUnit = integrationProcess.radialUnit()
        self.__wavelength = integrationProcess.wavelength()
        self.__directDist = integrationProcess.directDist()
        self.__geometry = integrationProcess.geometry()
        self.__inverseGeometry = InvertGeometry(
            self.__geometry.array_from_unit(typ="center",
                                            unit=self.__radialUnit,
                                            scale=True),
            numpy.rad2deg(self.__geometry.chiArray()))

        self.__markerManager.updateProjection(self.__geometry,
                                              self.__radialUnit,
                                              self.__wavelength,
                                              self.__directDist)

        resetZoomPolicy = integrationProcess.resetZoomPolicy()
        if resetZoomPolicy is None:
            # Default behaviour
            self.resetZoom()
        elif resetZoomPolicy is False:
            pass
        else:
            raise ValueError("Reset zoom policy not implemented")

    def __setResult(self, result1d):
        self.__result1d = result1d
        self.__saveResult1dAction.setEnabled(result1d is not None)

    def __saveAsCsv(self):
        if self.__result1d is None:
            return
        dialog = createSaveDialog(self,
                                  "Save 1D integration as CSV file",
                                  csv=True)
        result = dialog.exec_()
        if not result:
            return
        filename = dialog.selectedFiles()[0]
        silx.io.save1D(filename,
                       x=self.__result1d.radial,
                       y=self.__result1d.intensity,
                       xlabel=str(self.__result1d.unit),
                       ylabels=["intensity"],
                       filetype="csv",
                       autoheader=True)

    def setProcessing(self):
        self.__setResult(None)
        self.__processing1d = ProcessingWidget.createProcessingWidgetOverlay(
            self.__plot1d)
        self.__processing2d = ProcessingWidget.createProcessingWidgetOverlay(
            self.__plot2d)

    def unsetProcessing(self):
        if self.__processing1d is not None:
            self.__processing1d.deleteLater()
            self.__processing1d = None
        if self.__processing2d is not None:
            self.__processing2d.deleteLater()
            self.__processing2d = None
Example #18
0
    def __init__(self):
        qt.QMainWindow.__init__(self)
        self.setWindowTitle("Plot with synchronized axes")
        widget = qt.QWidget(self)
        self.setCentralWidget(widget)

        layout = qt.QGridLayout()
        widget.setLayout(layout)

        backend = "mpl"
        self.plot2d = plot.Plot2D(parent=widget, backend=backend)
        self.plot2d.setInteractiveMode('pan')
        self.plot1d_x1 = plot.Plot1D(parent=widget, backend=backend)
        self.plot1d_x2 = plot.PlotWidget(parent=widget, backend=backend)
        self.plot1d_y1 = plot.Plot1D(parent=widget, backend=backend)
        self.plot1d_y2 = plot.PlotWidget(parent=widget, backend=backend)

        data = numpy.arange(100 * 100)
        data = (data % 100) / 5.0
        data = numpy.sin(data)
        data = silx.test.utils.add_gaussian_noise(data, mean=0.01)
        data.shape = 100, 100

        self.plot2d.addImage(data)
        self.plot1d_x1.addCurve(x=numpy.arange(100),
                                y=numpy.mean(data, axis=0),
                                legend="mean")
        self.plot1d_x1.addCurve(x=numpy.arange(100),
                                y=numpy.max(data, axis=0),
                                legend="max")
        self.plot1d_x1.addCurve(x=numpy.arange(100),
                                y=numpy.min(data, axis=0),
                                legend="min")
        self.plot1d_x2.addCurve(x=numpy.arange(100), y=numpy.std(data, axis=0))

        self.plot1d_y1.addCurve(y=numpy.arange(100),
                                x=numpy.mean(data, axis=1),
                                legend="mean")
        self.plot1d_y1.addCurve(y=numpy.arange(100),
                                x=numpy.max(data, axis=1),
                                legend="max")
        self.plot1d_y1.addCurve(y=numpy.arange(100),
                                x=numpy.min(data, axis=1),
                                legend="min")
        self.plot1d_y2.addCurve(y=numpy.arange(100), x=numpy.std(data, axis=1))

        self.constraint1 = SyncAxes([
            self.plot2d.getXAxis(),
            self.plot1d_x1.getXAxis(),
            self.plot1d_x2.getXAxis()
        ])
        self.constraint2 = SyncAxes([
            self.plot2d.getYAxis(),
            self.plot1d_y1.getYAxis(),
            self.plot1d_y2.getYAxis()
        ])
        self.constraint3 = SyncAxes(
            [self.plot1d_x1.getYAxis(),
             self.plot1d_y1.getXAxis()])
        self.constraint4 = SyncAxes(
            [self.plot1d_x2.getYAxis(),
             self.plot1d_y2.getXAxis()])

        layout.addWidget(self.plot2d, 0, 0)
        layout.addWidget(self.createCenteredLabel(u"↓↑"), 1, 0)
        layout.addWidget(self.plot1d_x1, 2, 0)
        layout.addWidget(self.createCenteredLabel(u"↓↑"), 3, 0)
        layout.addWidget(self.plot1d_x2, 4, 0)
        layout.addWidget(self.createCenteredLabel(u"→\n←"), 0, 1)
        layout.addWidget(self.plot1d_y1, 0, 2)
        layout.addWidget(self.createCenteredLabel(u"→\n←"), 0, 3)
        layout.addWidget(self.plot1d_y2, 0, 4)
        layout.addWidget(self.createCenteredLabel(u"↗↙"), 2, 2)
        layout.addWidget(self.createCenteredLabel(u"↗↙"), 4, 4)
Example #19
0
 def testDoubleStop(self):
     """Test double stop"""
     sync = SyncAxes([self.plot1.getXAxis(), self.plot2.getXAxis(), self.plot3.getXAxis()])
     sync.stop()
     self.assertRaises(RuntimeError, sync.stop)
Example #20
0
    def __init__(self, parent=None):
        super(IECwindow, self).__init__(parent)  #qt.QMainWindow.__init__(self)
        #self.setWindowTitle("Plot with synchronized axes")
        widget = qt.QWidget(self)
        self.setCentralWidget(widget)
        self.updateThread = None
        layout = qt.QGridLayout()
        widget.setLayout(layout)
        backend = "mpl"
        #self.plot2d_cormap = plot.Plot2D(parent=widget, backend=backend)
        #self.plot2d.setInteractiveMode('pan')
        self.plot1d_chromo = self.createChromoPLot(widget, backend)
        self.plot1d_chromo.getYAxis().setLimits(-0.05, 1.05)
        self.plot1d_chromo.getYAxis().setAutoScale(flag=False)

        self.plot1d_log = plot.Plot1D(parent=widget, backend=backend)
        self.plot1d_subchromo = self.createChromoPLot(widget, backend)
        self.plot1d_ratio = self.createChromoPLot(widget, backend)
        self.plot1d_log.getYAxis().setScale("log")

        self.frameSlider = self.createFrameSlider(widget)
        self.diffSlider = self.createDiffSlider(widget)

        self.saveButton = self.createSaveButton(widget)

        self.l1 = QLabel(str(self.plot1d_chromo.getXAxis().getLimits()[0]) +
                         "," + str(self.frameSlider.minimum),
                         parent=widget)
        self.l1.setAlignment(Qt.AlignCenter)

        clearAction = ClearButton(self.plot1d_ratio, parent=widget)
        #actions_menu =   self.plot1d_ratio.menuBar().addMenu("Custom actions")
        toolbar = qt.QToolBar("My toolbar")
        self.plot1d_ratio.addToolBar(toolbar)
        #actions_menu.addAction(clearAction)
        toolbar.addAction(clearAction)

        self.constraint3 = SyncAxes([
            self.plot1d_chromo.getXAxis(),
            self.plot1d_subchromo.getXAxis(),
            self.plot1d_ratio.getXAxis()
        ])  #],self.plot2d_cormap.getXAxis(),self.plot2d_cormap.getYAxis()])
        #self.constraint3 = SyncAxes([self.plot1d_chromo.getXAxis(), self.plot1d_ratio.getXAxis()])
        #self.constraint1 = SyncAxes([self.plot1d_log.getXAxis(), self.plot1d_loglog.getXAxis(),self.plot1d_kratky.getXAxis(),self.plot1d_holtzer.getXAxis()], syncScale=False)

        #self.plot1d_kratky.getYAxis().setLimits(0,medfilt(I*self.q*self.q,21).max())
        layout.addWidget(self.plot1d_chromo, 0, 0)
        layout.addWidget(self.plot1d_log, 0, 1, 2, 1)

        layout.addWidget(self.frameSlider, 1, 0)
        layout.addWidget(self.diffSlider, 2, 0)
        layout.addWidget(self.plot1d_subchromo, 3, 0)
        layout.addWidget(self.plot1d_ratio, 3, 1)
        layout.addWidget(self.saveButton, 4, 1)
        #layout.addWidget(self.l1)

        currentRoi = self.plot1d_log.getCurvesRoiWidget().getRois()
        print(currentRoi)
        if len(currentRoi) == 0:
            currentRoi = OrderedDict(
                {"low-q range": {
                    "from": 0.1,
                    "to": 1,
                    "type": "X"
                }})
        else:
            currentRoi.update(
                {"low-q range": {
                    "from": 0.1,
                    "to": 1,
                    "type": "X"
                }})
        print(currentRoi)
        self.plot1d_log.getCurvesRoiWidget().setRois(currentRoi)
Example #21
0
class IntegrationPlot(qt.QFrame):

    def __init__(self, parent=None):
        super(IntegrationPlot, self).__init__(parent)

        self.__plot1d, self.__plot2d = self.__createPlots(self)
        self.__statusBar = _StatusBar(self)
        self.__statusBar.setSizeGripEnabled(False)

        layout = qt.QVBoxLayout(self)
        layout.setContentsMargins(1, 1, 1, 1)
        layout.addWidget(self.__plot2d)
        layout.addWidget(self.__plot1d)
        layout.addWidget(self.__statusBar)
        self.__setResult(None)
        self.__processing1d = None
        self.__processing2d = None
        self.__ringItems = {}
        self.__axisOfCurrentView = None
        self.__angleUnderMouse = None
        self.__availableRingAngles = None
        self.__radialUnit = None
        self.__wavelength = None
        self.__directDist = None
        self.__geometry = None
        self.__inverseGeometry = None

        markerModel = CalibrationContext.instance().getCalibrationModel().markerModel()
        self.__markerManager = MarkerManager(self.__plot2d, markerModel)

        self.__plot2d.getXAxis().sigLimitsChanged.connect(self.__axesChanged)
        self.__plot1d.sigPlotSignal.connect(self.__plot1dSignalReceived)
        self.__plot2d.sigPlotSignal.connect(self.__plot2dSignalReceived)

        self.__plotBackground = SynchronizePlotBackground(self.__plot2d)

        widget = self.__plot1d
        if hasattr(widget, "centralWidget"):
            widget.centralWidget()
        widget.installEventFilter(self)
        widget = self.__plot2d
        if hasattr(widget, "centralWidget"):
            widget.centralWidget()
        widget.installEventFilter(self)

        colormap = CalibrationContext.instance().getRawColormap()
        self.__plot2d.setDefaultColormap(colormap)

        from silx.gui.plot.utils.axis import SyncAxes
        self.__syncAxes = SyncAxes([self.__plot1d.getXAxis(), self.__plot2d.getXAxis()])

    def aboutToClose(self):
        # Avoid double free release problem. See #892
        self.__syncAxes.stop()
        self.__syncAxes = None

    def resetZoom(self):
        self.__plot1d.resetZoom()
        self.__plot2d.resetZoom()

    def hasData(self):
        return self.__result1d is not None

    def eventFilter(self, widget, event):
        if event.type() == qt.QEvent.Leave:
            self.__mouseLeave()
            return True
        return False

    def __mouseLeave(self):
        self.__statusBar.clearValues()

        if self.__angleUnderMouse is None:
            return
        if self.__angleUnderMouse not in self.__displayedAngles:
            items = self.__ringItems.get(self.__angleUnderMouse, [])
            for item in items:
                item.setVisible(False)
        self.__angleUnderMouse = None

    def __plot1dSignalReceived(self, event):
        """Called when old style signals at emmited from the plot."""
        if event["event"] == "mouseMoved":
            x, y = event["x"], event["y"]
            self.__mouseMoved(x, y)
            self.__updateStatusBar(x, None)

    def __plot2dSignalReceived(self, event):
        """Called when old style signals at emmited from the plot."""
        if event["event"] == "mouseMoved":
            x, y = event["x"], event["y"]
            self.__mouseMoved(x, y)
            self.__updateStatusBar(x, y)

    def __getClosestAngle(self, angle):
        """
        Returns the closest ring index and ring angle
        """
        # TODO: Could be done in log(n) using bisect search
        result = None
        iresult = None
        minDistance = float("inf")
        for ringId, ringAngle in enumerate(self.__availableRingAngles):
            distance = abs(angle - ringAngle)
            if distance < minDistance:
                minDistance = distance
                result = ringAngle
                iresult = ringId
        return iresult, result

    def dataToChiTth(self, data):
        """Returns chi and 2theta angles in radian from data coordinate"""
        try:
            tthRad = unitutils.tthToRad(data[0],
                                        unit=self.__radialUnit,
                                        wavelength=self.__wavelength,
                                        directDist=self.__directDist)
        except Exception:
            _logger.debug("Backtrace", exc_info=True)
            tthRad = None

        chiDeg = data[1]
        if chiDeg is not None:
            chiRad = numpy.deg2rad(chiDeg)
        else:
            chiRad = None

        return chiRad, tthRad

    def __updateStatusBar(self, x, y):
        chiRad, tthRad = self.dataToChiTth((x, y))

        if y is not None and self.__inverseGeometry is not None:
            pixelY, pixelX = self.__inverseGeometry(x, y, True)
            ax, ay = numpy.array([pixelX]), numpy.array([pixelY])
            tthFromPixel = self.__geometry.tth(ay, ax)[0]
            chiFromPixel = self.__geometry.chi(ay, ax)[0]

            if tthRad is not None:
                error = numpy.sqrt((tthRad - tthFromPixel) ** 2 + (chiRad - chiFromPixel) ** 2)
                if error > 0.05:
                    # The identified pixel is far from the requested chi/tth. Marker ignored.
                    pixelY, pixelX = None, None
        else:
            pixelY, pixelX = None, None

        self.__statusBar.setValues(pixelX, pixelY, chiRad, tthRad)

    def __mouseMoved(self, x, y):
        """Called when mouse move over the plot."""
        if self.__availableRingAngles is None:
            return
        angle = x
        ringId, angle = self.__getClosestAngle(angle)

        if angle == self.__angleUnderMouse:
            return

        if self.__angleUnderMouse not in self.__displayedAngles:
            items = self.__ringItems.get(self.__angleUnderMouse, [])
            for item in items:
                item.setVisible(False)

        self.__angleUnderMouse = angle

        if angle is not None:
            items = self.__getItemsFromAngle(ringId, angle)
            for item in items:
                item.setVisible(True)

    def __axesChanged(self, minValue, maxValue):
        axisOfCurrentView = self.__plot2d.getXAxis().getLimits()
        if self.__axisOfCurrentView == axisOfCurrentView:
            return
        self.__updateRings()

    def __getAvailableAngles(self, minTth, maxTth):
        result = []
        for ringId, angle in enumerate(self.__availableRingAngles):
            if minTth is None or maxTth is None:
                result.append(ringId, angle)
            if minTth <= angle <= maxTth:
                result.append((ringId, angle))
        return result

    def __updateRings(self):
        if self.__availableRingAngles is None:
            return

        minTth, maxTth = self.__plot2d.getXAxis().getLimits()
        angles = self.__getAvailableAngles(minTth, maxTth)

        if len(angles) < 20:
            step = 1
        elif len(angles) < 100:
            step = 2
        elif len(angles) < 200:
            step = 5
        elif len(angles) < 500:
            step = 10
        elif len(angles) < 1000:
            step = 20
        elif len(angles) < 5000:
            step = 100
        else:
            step = int(len(angles) / 50)

        self.__displayedAngles = set([])

        for items in self.__ringItems.values():
            for item in items:
                item.setVisible(False)

        for angleId in range(0, len(angles), step):
            ringId, ringAngle = angles[angleId]
            self.__displayedAngles.add(ringAngle)
            items = self.__getItemsFromAngle(ringId, ringAngle)
            for item in items:
                item.setVisible(True)

    def __getItemsFromAngle(self, ringId, ringAngle):
        items = self.__ringItems.get(ringAngle, None)
        if items is not None:
            return items

        color = CalibrationContext.instance().getMarkerColor(ringId, mode="numpy")
        items = []

        legend = "ring-%i" % (ringId,)

        self.__plot1d.addXMarker(x=ringAngle, color=color, legend=legend)
        item = self.__plot1d._getMarker(legend)
        items.append(item)

        self.__plot2d.addXMarker(x=ringAngle, color=color, legend=legend)
        item = self.__plot2d._getMarker(legend)
        items.append(item)

        self.__ringItems[ringAngle] = items
        return items

    def __syncModeToPlot1d(self, _event):
        modeDict = self.__plot2d.getInteractiveMode()
        mode = modeDict["mode"]
        self.__plot1d.setInteractiveMode(mode)

    def getDefaultColormap(self):
        return self.__plot2d.getDefaultColormap()

    def __createPlots(self, parent):
        margin = 0.02
        plot1d = silx.gui.plot.PlotWidget(parent)
        plot1d.setGraphXLabel("Radial")
        plot1d.setGraphYLabel("Intensity")
        plot1d.setGraphGrid(False)
        plot1d.setDataMargins(margin, margin, margin, margin)
        plot2d = silx.gui.plot.PlotWidget(parent)
        plot2d.setGraphXLabel("Radial")
        plot2d.setGraphYLabel("Azimuthal")
        plot2d.sigInteractiveModeChanged.connect(self.__syncModeToPlot1d)
        plot2d.setDataMargins(margin, margin, margin, margin)

        handle = plot2d.getWidgetHandle()
        handle.setContextMenuPolicy(qt.Qt.CustomContextMenu)
        handle.customContextMenuRequested.connect(self.__plot2dContextMenu)

        from silx.gui.plot import tools
        toolBar = tools.InteractiveModeToolBar(parent=self, plot=plot2d)
        plot2d.addToolBar(toolBar)

        toolBar = tools.ImageToolBar(parent=self, plot=plot2d)
        colormapDialog = CalibrationContext.instance().getColormapDialog()
        toolBar.getColormapAction().setColorDialog(colormapDialog)
        previousResetZoomAction = toolBar.getResetZoomAction()
        resetZoomAction = qt.QAction(toolBar)
        resetZoomAction.triggered.connect(self.resetZoom)
        resetZoomAction.setIcon(previousResetZoomAction.icon())
        resetZoomAction.setText(previousResetZoomAction.text())
        resetZoomAction.setToolTip(previousResetZoomAction.toolTip())
        toolBar.insertAction(previousResetZoomAction, resetZoomAction)
        previousResetZoomAction.setVisible(False)
        self.__resetZoomAction = resetZoomAction
        plot2d.addToolBar(toolBar)

        ownToolBar = qt.QToolBar(plot2d)
        from silx.gui.plot import actions
        logAction = actions.control.YAxisLogarithmicAction(parent=ownToolBar, plot=plot1d)
        logAction.setToolTip("Logarithmic y-axis intensity when checked")
        ownToolBar.addAction(logAction)
        plot2d.addToolBar(ownToolBar)

        action = qt.QAction(ownToolBar)
        action.setIcon(silx.gui.icons.getQIcon("document-save"))
        action.triggered.connect(self.__saveAsCsv)
        action.setToolTip("Save 1D integration as CSV file")
        self.__saveResult1dAction = action
        ownToolBar.addAction(action)

        return plot1d, plot2d

    def __plot2dContextMenu(self, pos):
        from silx.gui.plot.actions.control import ZoomBackAction
        zoomBackAction = ZoomBackAction(plot=self.__plot2d, parent=self.__plot2d)

        menu = qt.QMenu(self)

        menu.addAction(zoomBackAction)
        menu.addSeparator()
        menu.addAction(self.__markerManager.createMarkPixelAction(menu, pos))
        menu.addAction(self.__markerManager.createMarkGeometryAction(menu, pos))
        action = self.__markerManager.createRemoveClosestMaskerAction(menu, pos)
        if action is not None:
            menu.addAction(action)

        handle = self.__plot2d.getWidgetHandle()
        menu.exec_(handle.mapToGlobal(pos))

    def __clearRings(self):
        """Remove of ring item cached on the plots"""
        for items in self.__ringItems.values():
            for item in items:
                self.__plot1d.removeMarker(item.getLegend())
                self.__plot2d.removeMarker(item.getLegend())
        self.__ringItems = {}

    def setIntegrationProcess(self, integrationProcess):
        """
        :param IntegrationProcess integrationProcess: Result of the integration
            process
        """
        self.__clearRings()

        self.__availableRingAngles = integrationProcess.ringAngles()
        self.__updateRings()

        # FIXME set axes units
        result1d = integrationProcess.result1d()
        self.__plot1d.addHistogram(
            legend="result1d",
            align="center",
            edges=result1d.radial,
            color="blue",
            histogram=result1d.intensity,
            resetzoom=False)

        if silx.version_info[0:2] == (0, 8):
            # Invalidate manually the plot datarange on silx 0.8
            # https://github.com/silx-kit/silx/issues/2079
            self.__plot1d._invalidateDataRange()

        self.__setResult(result1d)

        def compute_location(result):
            # Assume that axes are linear
            if result.intensity.shape[1] > 1:
                scaleX = (result.radial[-1] - result.radial[0]) / (result.intensity.shape[1] - 1)
            else:
                scaleX = 1.0
            if result.intensity.shape[0] > 1:
                scaleY = (result.azimuthal[-1] - result.azimuthal[0]) / (result.intensity.shape[0] - 1)
            else:
                scaleY = 1.0
            halfPixel = 0.5 * scaleX, 0.5 * scaleY
            origin = (result.radial[0] - halfPixel[0], result.azimuthal[0] - halfPixel[1])
            return origin, scaleX, scaleY

        resultMask2d = integrationProcess.resultMask2d()
        isMaskDisplayed = resultMask2d is not None

        result2d = integrationProcess.result2d()
        result2d_intensity = result2d.intensity

        # Mask pixels with no data
        result2d_intensity[result2d.count == 0] = float("NaN")

        if isMaskDisplayed:
            maskedColor = CalibrationContext.instance().getMaskedColor()
            transparent = (0.0, 0.0, 0.0, 0.0)
            resultMask2d_rgba = imageutils.maskArrayToRgba(resultMask2d.count != 0,
                                                           falseColor=transparent,
                                                           trueColor=maskedColor)
            origin, scaleX, scaleY = compute_location(resultMask2d)
            self.__plot2d.addImage(
                legend="integrated_mask",
                data=resultMask2d_rgba,
                origin=origin,
                scale=(scaleX, scaleY),
                resetzoom=False)
        else:
            try:
                self.__plot2d.removeImage("integrated_mask")
            except Exception:
                pass

        colormap = self.getDefaultColormap()
        origin, scaleX, scaleY = compute_location(result2d)
        self.__plot2d.addImage(
            legend="integrated_data",
            data=result2d_intensity,
            origin=origin,
            scale=(scaleX, scaleY),
            colormap=colormap,
            resetzoom=False)

        self.__radialUnit = integrationProcess.radialUnit()
        self.__wavelength = integrationProcess.wavelength()
        self.__directDist = integrationProcess.directDist()
        self.__geometry = integrationProcess.geometry()
        self.__inverseGeometry = InvertGeometry(
            self.__geometry.array_from_unit(typ="center", unit=self.__radialUnit, scale=True),
            numpy.rad2deg(self.__geometry.chiArray()))

        self.__markerManager.updateProjection(self.__geometry,
                                              self.__radialUnit,
                                              self.__wavelength,
                                              self.__directDist)

        resetZoomPolicy = integrationProcess.resetZoomPolicy()
        if resetZoomPolicy is None:
            # Default behaviour
            self.resetZoom()
        elif resetZoomPolicy is False:
            pass
        else:
            raise ValueError("Reset zoom policy not implemented")

    def __setResult(self, result1d):
        self.__result1d = result1d
        self.__saveResult1dAction.setEnabled(result1d is not None)

    def __saveAsCsv(self):
        if self.__result1d is None:
            return
        dialog = createSaveDialog(self, "Save 1D integration as CSV file", csv=True)
        result = dialog.exec_()
        if not result:
            return
        filename = dialog.selectedFiles()[0]
        # TODO: it would be good to store the units
        silx.io.save1D(filename,
                       x=self.__result1d.radial,
                       y=self.__result1d.intensity,
                       xlabel="radial",
                       ylabels=["intensity"],
                       filetype="csv",
                       autoheader=True)

    def setProcessing(self):
        self.__setResult(None)
        self.__processing1d = ProcessingWidget.createProcessingWidgetOverlay(self.__plot1d)
        self.__processing2d = ProcessingWidget.createProcessingWidgetOverlay(self.__plot2d)

    def unsetProcessing(self):
        if self.__processing1d is not None:
            self.__processing1d.deleteLater()
            self.__processing1d = None
        if self.__processing2d is not None:
            self.__processing2d.deleteLater()
            self.__processing2d = None