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))
def testDoubleStop(self): """Test double stop""" sync = SyncAxes([ self.plot1.getXAxis(), self.plot2.getXAxis(), self.plot3.getXAxis() ]) sync.stop() self.assertRaises(RuntimeError, sync.stop)
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))
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
def testDoubleStop(self): """Test double stop""" sync = SyncAxes([self.plot1.getXAxis(), self.plot2.getXAxis(), self.plot3.getXAxis()]) sync.stop() self.assertRaises(RuntimeError, sync.stop)
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