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 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))
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 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)
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))
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 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)
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)
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))
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))
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))
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))
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()])
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 __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)
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 __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)
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