def __init__(self, parent = None, backend=None, selection=False, aspect=True, colormap=False, imageicons=False, standalonesave=True, standalonezoom=True, profileselection=False, polygon=False): qt.QWidget.__init__(self, parent) self.mainLayout = qt.QVBoxLayout(self) self.mainLayout.setContentsMargins(0, 0, 0, 0) self.mainLayout.setSpacing(0) self._keepDataAspectRatioFlag = False self.graph = PlotWidget(parent=self, backend=backend) self.graph.setGraphXLabel("Column") self.graph.setGraphYLabel("Row") self.graph.setYAxisAutoScale(True) self.graph.setXAxisAutoScale(True) plotArea = self.graph.getWidgetHandle() plotArea.setContextMenuPolicy(qt.Qt.CustomContextMenu) plotArea.customContextMenuRequested.connect(self._zoomBack) self._buildToolBar(selection, colormap, imageicons, standalonesave, standalonezoom=standalonezoom, profileselection=profileselection, aspect=aspect, polygon=polygon) if profileselection: if len(self._pickerSelectionButtons): self.graph.sigPlotSignal.connect(\ self._graphPolygonSignalReceived) self._pickerSelectionWidthValue.valueChanged[int].connect( \ self.setPickerSelectionWith) self.saveDirectory = os.getcwd() self.mainLayout.addWidget(self.graph)
def __init__(self, parent=None): super(MaskScatterWidget, self).__init__(parent=parent) self._activeScatterLegend = "active scatter" self._bgImageLegend = "background image" # widgets centralWidget = qt.QWidget(self) self._plot = PlotWidget(parent=centralWidget) self._maskToolsWidget = ScatterMaskToolsWidget.ScatterMaskToolsWidget( plot=self._plot, parent=centralWidget) self._alphaSlider = NamedScatterAlphaSlider(parent=self, plot=self._plot) self._alphaSlider.setOrientation(qt.Qt.Horizontal) self._alphaSlider.setToolTip("Adjust scatter opacity") # layout layout = qt.QVBoxLayout(centralWidget) layout.addWidget(self._plot) layout.addWidget(self._alphaSlider) layout.addWidget(self._maskToolsWidget) centralWidget.setLayout(layout) self.setCentralWidget(centralWidget)
def addItem(self, *var, **kw): if len(var) < 2: if len(var) == 0: return PlotWidget.addItem(self, **kw) else: return PlotWidget.addItem(self, *var, **kw) else: return self.__addItem(*var, **kw)
def __init__(self, *var, **kw): PlotWidget.__init__(self, *var, **kw) # No context menu by default, execute zoomBack on right click if "backend" in kw: setBackend = kw["backend"] else: setBackend = None _logger.info("SilxBackend called with backend = %s" % setBackend) plotArea = self.getWidgetHandle() plotArea.setContextMenuPolicy(qt.Qt.CustomContextMenu) plotArea.customContextMenuRequested.connect(self._zoomBack)
def __init__(self, parent=None): qt.QWidget.__init__(self, parent) self.setWindowTitle("Strip and SNIP Configuration Window") self.mainLayout = qt.QVBoxLayout(self) self.mainLayout.setContentsMargins(0, 0, 0, 0) self.mainLayout.setSpacing(2) self.parametersWidget = BackgroundParamWidget(self) self.graphWidget = PlotWidget(parent=self) self.mainLayout.addWidget(self.parametersWidget) self.mainLayout.addWidget(self.graphWidget) self._x = None self._y = None self.parametersWidget.sigBackgroundParamWidgetSignal.connect(self._slot)
def setUp(self): super(TestNamedImageAlphaSlider, self).setUp() self.plot = PlotWidget() self.aslider = AlphaSlider.NamedImageAlphaSlider(plot=self.plot) self.aslider.setOrientation(qt.Qt.Horizontal) toolbar = qt.QToolBar("plot", self.plot) toolbar.addWidget(self.aslider) self.plot.addToolBar(toolbar) self.plot.show() self.qWaitForWindowExposed(self.plot) self.mouseMove(self.plot) # Move to center self.qapp.processEvents()
def __init__(self, parent=None): super(MyPlotWindow, self).__init__(parent) # Create a PlotWidget self._plot = PlotWidget(parent=self) # Create a colorbar linked with the PlotWidget colorBar = ColorBarWidget(parent=self, plot=self._plot) # Make ColorBarWidget background white by changing its palette colorBar.setAutoFillBackground(True) palette = colorBar.palette() palette.setColor(qt.QPalette.Background, qt.Qt.white) palette.setColor(qt.QPalette.Window, qt.Qt.white) colorBar.setPalette(palette) # Combine the ColorBarWidget and the PlotWidget as # this QMainWindow's central widget gridLayout = qt.QGridLayout() gridLayout.setSpacing(0) gridLayout.setContentsMargins(0, 0, 0, 0) gridLayout.addWidget(self._plot, 0, 0) gridLayout.addWidget(colorBar, 0, 1) gridLayout.setRowStretch(0, 1) gridLayout.setColumnStretch(0, 1) centralWidget = qt.QWidget(self) centralWidget.setLayout(gridLayout) self.setCentralWidget(centralWidget) # Add ready to use toolbar with zoom and pan interaction mode buttons interactionToolBar = tools.InteractiveModeToolBar(parent=self, plot=self._plot) self.addToolBar(interactionToolBar) # Add toolbar actions to activate keyboard shortcuts self.addActions(interactionToolBar.actions()) # Add a new toolbar toolBar = qt.QToolBar("Plot Tools", self) self.addToolBar(toolBar) # Add actions from silx.gui.plot.action to the toolbar resetZoomAction = actions.control.ResetZoomAction(parent=self, plot=self._plot) toolBar.addAction(resetZoomAction) # Add tool buttons from silx.gui.plot.PlotToolButtons aspectRatioButton = PlotToolButtons.AspectToolButton(parent=self, plot=self._plot) toolBar.addWidget(aspectRatioButton) # Add ready to use toolbar with copy, save and print buttons outputToolBar = tools.OutputToolBar(parent=self, plot=self._plot) self.addToolBar(outputToolBar) # Add toolbar actions to activate keyboard shortcuts self.addActions(outputToolBar.actions()) # Add limits tool bar from silx.gui.plot.PlotTools limitsToolBar = tools.LimitsToolBar(parent=self, plot=self._plot) self.addToolBar(qt.Qt.BottomToolBarArea, limitsToolBar)
def addCurve(self, *var, **kw): if "replot" in kw: if kw["replot"]: kw["resetzoom"] = True del kw["replot"] result = PlotWidget.addCurve(self, *var, **kw) allCurves = self.getAllCurves(just_legend=True) if len(allCurves) == 1: self.setActiveCurve(allCurves[0]) return result
def addImage(self, *var, **kw): if "replot" in kw: del kw["replot"] if "xScale" in kw: xScale = kw["xScale"] del kw["xScale"] if "yScale" in kw: yScale = kw["yScale"] del kw["yScale"] if xScale is not None or yScale is not None: origin = kw.get("origin", None) scale = kw.get("scale", None) if origin is None and scale is None: kw["origin"] = xScale[0], yScale[0] kw["scale"] = xScale[1], yScale[1] return PlotWidget.addImage(self, *var, **kw)
class _PlotWidgetTest(TestCaseQt): """Base class for tests of PlotWidget, not a TestCase in itself. plot attribute is the PlotWidget created for the test. """ def setUp(self): super(_PlotWidgetTest, self).setUp() self.plot = PlotWidget() self.plot.show() self.qWaitForWindowExposed(self.plot) def tearDown(self): self.qapp.processEvents() self.plot.setAttribute(qt.Qt.WA_DeleteOnClose) self.plot.close() del self.plot super(_PlotWidgetTest, self).tearDown()
__authors__ = ["P. Knobel"] __license__ = "MIT" __date__ = "25/07/2017" import numpy from silx.gui import qt from silx.gui.plot import PlotWidget from silx.gui.plot import PrintPreviewToolButton app = qt.QApplication([]) x = numpy.arange(1000) # first widget has a standalone print preview action pw1 = PlotWidget() pw1.setWindowTitle("Widget 1 with standalone print preview") toolbar1 = qt.QToolBar(pw1) toolbutton1 = PrintPreviewToolButton.PrintPreviewToolButton(parent=toolbar1, plot=pw1) pw1.addToolBar(toolbar1) toolbar1.addWidget(toolbutton1) pw1.show() pw1.addCurve(x, numpy.tan(x * 2 * numpy.pi / 1000)) # next two plots share a common print preview pw2 = PlotWidget() pw2.setWindowTitle("Widget 2 with shared print preview") toolbar2 = qt.QToolBar(pw2) toolbutton2 = PrintPreviewToolButton.SingletonPrintPreviewToolButton( parent=toolbar2, plot=pw2)
class RGBCorrelatorGraph(qt.QWidget): sigProfileSignal = qt.pyqtSignal(object) def __init__(self, parent = None, backend=None, selection=False, aspect=True, colormap=False, imageicons=False, standalonesave=True, standalonezoom=True, profileselection=False, polygon=False): qt.QWidget.__init__(self, parent) self.mainLayout = qt.QVBoxLayout(self) self.mainLayout.setContentsMargins(0, 0, 0, 0) self.mainLayout.setSpacing(0) self._keepDataAspectRatioFlag = False self.graph = PlotWidget(parent=self, backend=backend) self.graph.setGraphXLabel("Column") self.graph.setGraphYLabel("Row") self.graph.setYAxisAutoScale(True) self.graph.setXAxisAutoScale(True) plotArea = self.graph.getWidgetHandle() plotArea.setContextMenuPolicy(qt.Qt.CustomContextMenu) plotArea.customContextMenuRequested.connect(self._zoomBack) self._buildToolBar(selection, colormap, imageicons, standalonesave, standalonezoom=standalonezoom, profileselection=profileselection, aspect=aspect, polygon=polygon) if profileselection: if len(self._pickerSelectionButtons): self.graph.sigPlotSignal.connect(\ self._graphPolygonSignalReceived) self._pickerSelectionWidthValue.valueChanged[int].connect( \ self.setPickerSelectionWith) self.saveDirectory = os.getcwd() self.mainLayout.addWidget(self.graph) def sizeHint(self): return qt.QSize(1.5 * qt.QWidget.sizeHint(self).width(), qt.QWidget.sizeHint(self).height()) def _buildToolBar(self, selection=False, colormap=False, imageicons=False, standalonesave=True, standalonezoom=True, profileselection=False, aspect=False, polygon=False): self.solidCircleIcon = qt.QIcon(qt.QPixmap(IconDict["solidcircle"])) self.solidEllipseIcon = qt.QIcon(qt.QPixmap(IconDict["solidellipse"])) self.colormapIcon = qt.QIcon(qt.QPixmap(IconDict["colormap"])) self.selectionIcon = qt.QIcon(qt.QPixmap(IconDict["normal"])) self.zoomResetIcon = qt.QIcon(qt.QPixmap(IconDict["zoomreset"])) self.polygonIcon = qt.QIcon(qt.QPixmap(IconDict["polygon"])) self.printIcon = qt.QIcon(qt.QPixmap(IconDict["fileprint"])) self.saveIcon = qt.QIcon(qt.QPixmap(IconDict["filesave"])) self.xAutoIcon = qt.QIcon(qt.QPixmap(IconDict["xauto"])) self.yAutoIcon = qt.QIcon(qt.QPixmap(IconDict["yauto"])) self.hFlipIcon = qt.QIcon(qt.QPixmap(IconDict["gioconda16mirror"])) self.imageIcon = qt.QIcon(qt.QPixmap(IconDict["image"])) self.eraseSelectionIcon = qt.QIcon(qt.QPixmap(IconDict["eraseselect"])) self.rectSelectionIcon = qt.QIcon(qt.QPixmap(IconDict["boxselect"])) self.brushSelectionIcon = qt.QIcon(qt.QPixmap(IconDict["brushselect"])) self.brushIcon = qt.QIcon(qt.QPixmap(IconDict["brush"])) self.additionalIcon = qt.QIcon(qt.QPixmap(IconDict["additionalselect"])) self.hLineIcon = qt.QIcon(qt.QPixmap(IconDict["horizontal"])) self.vLineIcon = qt.QIcon(qt.QPixmap(IconDict["vertical"])) self.lineIcon = qt.QIcon(qt.QPixmap(IconDict["diagonal"])) self.copyIcon = silx_icons.getQIcon("edit-copy") self.toolBar = qt.QWidget(self) self.toolBarLayout = qt.QHBoxLayout(self.toolBar) self.toolBarLayout.setContentsMargins(0, 0, 0, 0) self.toolBarLayout.setSpacing(0) self.mainLayout.addWidget(self.toolBar) #Autoscale if standalonezoom: tb = self._addToolButton(self.zoomResetIcon, self.__zoomReset, 'Auto-Scale the Graph') else: tb = self._addToolButton(self.zoomResetIcon, None, 'Auto-Scale the Graph') self.zoomResetToolButton = tb #y Autoscale tb = self._addToolButton(self.yAutoIcon, self._yAutoScaleToggle, 'Toggle Autoscale Y Axis (On/Off)', toggle = True, state=True) tb.setDown(True) self.yAutoScaleToolButton = tb tb.setDown(True) #x Autoscale tb = self._addToolButton(self.xAutoIcon, self._xAutoScaleToggle, 'Toggle Autoscale X Axis (On/Off)', toggle = True, state=True) self.xAutoScaleToolButton = tb tb.setDown(True) #Aspect ratio if aspect: self.aspectButton = self._addToolButton(self.solidCircleIcon, self._aspectButtonSignal, 'Keep data aspect ratio', toggle=False) self.aspectButton.setChecked(False) #colormap if colormap: tb = self._addToolButton(self.colormapIcon, None, 'Change Colormap') self.colormapToolButton = tb #flip tb = self._addToolButton(self.hFlipIcon, None, 'Flip Horizontal') self.hFlipToolButton = tb #save if standalonesave: tb = self._addToolButton(self.saveIcon, self._saveIconSignal, 'Save Graph') else: tb = self._addToolButton(self.saveIcon, None, 'Save') self.saveToolButton = tb self.copyToolButton = self._addToolButton(self.copyIcon, self._copyIconSignal, "Copy graph to clipboard") #Selection if selection: tb = self._addToolButton(self.selectionIcon, None, 'Toggle Selection Mode', toggle = True, state = False) tb.setDown(False) self.selectionToolButton = tb #image selection icons if imageicons: tb = self._addToolButton(self.imageIcon, None, 'Reset') self.imageToolButton = tb tb = self._addToolButton(self.eraseSelectionIcon, None, 'Erase Selection') self.eraseSelectionToolButton = tb tb = self._addToolButton(self.rectSelectionIcon, None, 'Rectangular Selection') self.rectSelectionToolButton = tb tb = self._addToolButton(self.brushSelectionIcon, None, 'Brush Selection') self.brushSelectionToolButton = tb tb = self._addToolButton(self.brushIcon, None, 'Select Brush') self.brushToolButton = tb if polygon: tb = self._addToolButton(self.polygonIcon, None, 'Polygon selection\nRight click to finish') self.polygonSelectionToolButton = tb tb = self._addToolButton(self.additionalIcon, None, 'Additional Selections Menu') self.additionalSelectionToolButton = tb else: if polygon: tb = self._addToolButton(self.polygonIcon, None, 'Polygon selection\nRight click to finish') self.polygonSelectionToolButton = tb self.imageToolButton = None #picker selection self._pickerSelectionButtons = [] if profileselection: self._profileSelection = True self._polygonSelection = False self._pickerSelectionButtons = [] if self._profileSelection: tb = self._addToolButton(self.hLineIcon, self._hLineProfileClicked, 'Horizontal Profile Selection', toggle=True, state=False) self.hLineProfileButton = tb self._pickerSelectionButtons.append(tb) tb = self._addToolButton(self.vLineIcon, self._vLineProfileClicked, 'Vertical Profile Selection', toggle=True, state=False) self.vLineProfileButton = tb self._pickerSelectionButtons.append(tb) tb = self._addToolButton(self.lineIcon, self._lineProfileClicked, 'Line Profile Selection', toggle=True, state=False) self.lineProfileButton = tb self._pickerSelectionButtons.append(tb) self._pickerSelectionWidthLabel = qt.QLabel(self.toolBar) self._pickerSelectionWidthLabel.setText("W:") self.toolBar.layout().addWidget(self._pickerSelectionWidthLabel) self._pickerSelectionWidthValue = qt.QSpinBox(self.toolBar) self._pickerSelectionWidthValue.setMinimum(0) self._pickerSelectionWidthValue.setMaximum(1000) self._pickerSelectionWidthValue.setValue(1) self.toolBar.layout().addWidget(self._pickerSelectionWidthValue) #tb = self._addToolButton(None, # self._lineProfileClicked, # 'Line Profile Selection', # toggle=True, # state=False) #tb.setText = "W:" #self.lineWidthProfileButton = tb #self._pickerSelectionButtons.append(tb) if self._polygonSelection: _logger.info("Polygon selection not implemented yet") #hide profile selection buttons if imageicons: for button in self._pickerSelectionButtons: button.hide() self.infoWidget = qt.QWidget(self.toolBar) self.infoWidget.mainLayout = qt.QHBoxLayout(self.infoWidget) self.infoWidget.mainLayout.setContentsMargins(0, 0, 0, 0) self.infoWidget.mainLayout.setSpacing(0) self.infoWidget.label = qt.QLabel(self.infoWidget) self.infoWidget.label.setText("X = ???? Y = ???? Z = ????") self.infoWidget.mainLayout.addWidget(self.infoWidget.label) self.toolBarLayout.addWidget(self.infoWidget) self.infoWidget.hide() self.toolBarLayout.addWidget(qt.HorizontalSpacer(self.toolBar)) # ---print self.printPreview = SingletonPrintPreviewToolButton(parent=self, plot=self.graph) self.printPreview.setIcon(self.printIcon) self.toolBarLayout.addWidget(self.printPreview) def _aspectButtonSignal(self): _logger.debug("_aspectButtonSignal") if self._keepDataAspectRatioFlag: self.keepDataAspectRatio(False) else: self.keepDataAspectRatio(True) def keepDataAspectRatio(self, flag=True): if flag: self._keepDataAspectRatioFlag = True self.aspectButton.setIcon(self.solidEllipseIcon) self.aspectButton.setToolTip("Set free data aspect ratio") else: self._keepDataAspectRatioFlag = False self.aspectButton.setIcon(self.solidCircleIcon) self.aspectButton.setToolTip("Keep data aspect ratio") self.graph.setKeepDataAspectRatio(self._keepDataAspectRatioFlag) def showInfo(self): self.infoWidget.show() def hideInfo(self): self.infoWidget.hide() def setInfoText(self, text): self.infoWidget.label.setText(text) def setMouseText(self, text=""): try: if len(text): qt.QToolTip.showText(self.cursor().pos(), text, self, qt.QRect()) else: qt.QToolTip.hideText() except: _logger.warning("Error trying to show mouse text <%s>" % text) def focusOutEvent(self, ev): qt.QToolTip.hideText() def infoText(self): return self.infoWidget.label.text() def setXLabel(self, label="Column"): return self.graph.setGraphXLabel(label) def setYLabel(self, label="Row"): return self.graph.setGraphYLabel(label) def getXLabel(self): return self.graph.getGraphXLabel() def getYLabel(self): return self.graph.getGraphYLabel() def hideImageIcons(self): if self.imageToolButton is None:return self.imageToolButton.hide() self.eraseSelectionToolButton.hide() self.rectSelectionToolButton.hide() self.brushSelectionToolButton.hide() self.brushToolButton.hide() if hasattr(self, "polygonSelectionToolButton"): self.polygonSelectionToolButton.hide() self.additionalSelectionToolButton.hide() def showImageIcons(self): if self.imageToolButton is None:return self.imageToolButton.show() self.eraseSelectionToolButton.show() self.rectSelectionToolButton.show() self.brushSelectionToolButton.show() self.brushToolButton.show() if hasattr(self, "polygonSelectionToolButton"): self.polygonSelectionToolButton.show() self.additionalSelectionToolButton.show() def _hLineProfileClicked(self): for button in self._pickerSelectionButtons: if button != self.hLineProfileButton: button.setChecked(False) if self.hLineProfileButton.isChecked(): self._setPickerSelectionMode("HORIZONTAL") else: self._setPickerSelectionMode(None) def _vLineProfileClicked(self): for button in self._pickerSelectionButtons: if button != self.vLineProfileButton: button.setChecked(False) if self.vLineProfileButton.isChecked(): self._setPickerSelectionMode("VERTICAL") else: self._setPickerSelectionMode(None) def _lineProfileClicked(self): for button in self._pickerSelectionButtons: if button != self.lineProfileButton: button.setChecked(False) if self.lineProfileButton.isChecked(): self._setPickerSelectionMode("LINE") else: self._setPickerSelectionMode(None) def setPickerSelectionWith(self, intValue): self._pickerSelectionWidthValue.setValue(intValue) #get the current mode mode = "NONE" for button in self._pickerSelectionButtons: if button.isChecked(): if button == self.hLineProfileButton: mode = "HORIZONTAL" elif button == self.vLineProfileButton: mode = "VERTICAL" elif button == self.lineProfileButton: mode = "LINE" ddict = {} ddict['event'] = "profileWidthChanged" ddict['pixelwidth'] = self._pickerSelectionWidthValue.value() ddict['mode'] = mode self.sigProfileSignal.emit(ddict) def hideProfileSelectionIcons(self): if not len(self._pickerSelectionButtons): return for button in self._pickerSelectionButtons: button.setChecked(False) button.hide() self._pickerSelectionWidthLabel.hide() self._pickerSelectionWidthValue.hide() if self.graph.getInteractiveMode()['mode'] == 'draw': self.graph.setInteractiveMode('select') def showProfileSelectionIcons(self): if not len(self._pickerSelectionButtons): return for button in self._pickerSelectionButtons: button.show() self._pickerSelectionWidthLabel.show() self._pickerSelectionWidthValue.show() def getPickerSelectionMode(self): if not len(self._pickerSelectionButtons): return None if self.hLineProfileButton.isChecked(): return "HORIZONTAL" if self.vLineProfileButton.isChecked(): return "VERTICAL" if self.lineProfileButton.isChecked(): return "LINE" return None def _setPickerSelectionMode(self, mode=None): if mode is None: self.graph.setInteractiveMode('zoom') else: if mode == "HORIZONTAL": shape = "hline" elif mode == "VERTICAL": shape = "vline" else: shape = "line" self.graph.setInteractiveMode('draw', shape=shape, label=mode) ddict = {} if mode is None: mode = "NONE" ddict['event'] = "profileModeChanged" ddict['mode'] = mode self.sigProfileSignal.emit(ddict) def _graphPolygonSignalReceived(self, ddict): _logger.debug("PolygonSignal Received") for key in ddict.keys(): _logger.debug("%s: %s", key, ddict[key]) if ddict['event'] not in ['drawingProgress', 'drawingFinished']: return label = ddict['parameters']['label'] if label not in ['HORIZONTAL', 'VERTICAL', 'LINE']: return ddict['mode'] = label ddict['pixelwidth'] = self._pickerSelectionWidthValue.value() self.sigProfileSignal.emit(ddict) def _addToolButton(self, icon, action, tip, toggle=None, state=None, position=None): tb = qt.QToolButton(self.toolBar) if icon is not None: tb.setIcon(icon) tb.setToolTip(tip) if toggle is not None: if toggle: tb.setCheckable(1) if state is not None: if state: tb.setChecked(state) else: tb.setChecked(False) if position is not None: self.toolBarLayout.insertWidget(position, tb) else: self.toolBarLayout.addWidget(tb) if action is not None: tb.clicked.connect(action) return tb def __zoomReset(self): self._zoomReset() def _zoomReset(self, replot=None): _logger.debug("_zoomReset") if self.graph is not None: self.graph.resetZoom() def _yAutoScaleToggle(self): if self.graph is not None: self.yAutoScaleToolButton.setDown( not self.graph.isYAxisAutoScale()) self.graph.setYAxisAutoScale( not self.graph.isYAxisAutoScale()) def _xAutoScaleToggle(self): if self.graph is not None: self.xAutoScaleToolButton.setDown( not self.graph.isXAxisAutoScale()) self.graph.setXAxisAutoScale( not self.graph.isXAxisAutoScale()) def _copyIconSignal(self): pngFile = BytesIO() self.graph.saveGraph(pngFile, fileFormat='png') pngFile.flush() pngFile.seek(0) pngData = pngFile.read() pngFile.close() image = qt.QImage.fromData(pngData, 'png') qt.QApplication.clipboard().setImage(image) def _saveIconSignal(self): self.saveDirectory = PyMcaDirs.outputDir fileTypeList = ["Image *.png", "Image *.jpg", "ZoomedImage *.png", "ZoomedImage *.jpg", "Widget *.png", "Widget *.jpg"] outfile = qt.QFileDialog(self) outfile.setModal(1) outfile.setWindowTitle("Output File Selection") if hasattr(qt, "QStringList"): strlist = qt.QStringList() else: strlist = [] for f in fileTypeList: strlist.append(f) if hasattr(outfile, "setFilters"): outfile.setFilters(strlist) else: outfile.setNameFilters(strlist) outfile.setFileMode(outfile.AnyFile) outfile.setAcceptMode(qt.QFileDialog.AcceptSave) outfile.setDirectory(self.saveDirectory) ret = outfile.exec_() if not ret: return if hasattr(outfile, "selectedFilter"): filterused = qt.safe_str(outfile.selectedFilter()).split() else: filterused = qt.safe_str(outfile.selectedNameFilter()).split() filetype = filterused[0] extension = filterused[1] outstr = qt.safe_str(outfile.selectedFiles()[0]) try: outputFile = os.path.basename(outstr) except: outputFile = outstr outputDir = os.path.dirname(outstr) self.saveDirectory = outputDir PyMcaDirs.outputDir = outputDir #always overwrite for the time being if len(outputFile) < len(extension[1:]): outputFile += extension[1:] elif outputFile[-4:] != extension[1:]: outputFile += extension[1:] outputFile = os.path.join(outputDir, outputFile) if os.path.exists(outputFile): try: os.remove(outputFile) except: qt.QMessageBox.critical(self, "Save Error", "Cannot overwrite existing file") return if filetype.upper() == "IMAGE": self.saveGraphImage(outputFile, original=True) elif filetype.upper() == "ZOOMEDIMAGE": self.saveGraphImage(outputFile, original=False) else: self.saveGraphWidget(outputFile) def saveGraphImage(self, filename, original=False): format_ = filename[-3:].upper() activeImage = self.graph.getActiveImage() rgbdata = activeImage.getRgbaImageData() # silx to pymca scale convention (a + b x) xScale = activeImage.getOrigin()[0], activeImage.getScale()[0] yScale = activeImage.getOrigin()[1], activeImage.getScale()[1] if original: # save whole image bgradata = numpy.array(rgbdata, copy=True) bgradata[:, :, 0] = rgbdata[:, :, 2] bgradata[:, :, 2] = rgbdata[:, :, 0] else: shape = rgbdata.shape[:2] xmin, xmax = self.graph.getGraphXLimits() ymin, ymax = self.graph.getGraphYLimits() # save zoomed image, for that we have to get the limits r0, c0 = convertToRowAndColumn(xmin, ymin, shape, xScale=xScale, yScale=yScale, safe=True) r1, c1 = convertToRowAndColumn(xmax, ymax, shape, xScale=xScale, yScale=yScale, safe=True) row0 = int(min(r0, r1)) row1 = int(max(r0, r1)) col0 = int(min(c0, c1)) col1 = int(max(c0, c1)) if row1 < shape[0]: row1 += 1 if col1 < shape[1]: col1 += 1 tmpArray = rgbdata[row0:row1, col0:col1, :] bgradata = numpy.array(tmpArray, copy=True, dtype=rgbdata.dtype) bgradata[:, :, 0] = tmpArray[:, :, 2] bgradata[:, :, 2] = tmpArray[:, :, 0] if self.graph.isYAxisInverted(): qImage = qt.QImage(bgradata, bgradata.shape[1], bgradata.shape[0], qt.QImage.Format_ARGB32) else: qImage = qt.QImage(bgradata, bgradata.shape[1], bgradata.shape[0], qt.QImage.Format_ARGB32).mirrored(False, True) pixmap = qt.QPixmap.fromImage(qImage) if pixmap.save(filename, format_): return else: qt.QMessageBox.critical(self, "Save Error", "%s" % sys.exc_info()[1]) return def saveGraphWidget(self, filename): format_ = filename[-3:].upper() if hasattr(qt.QPixmap, "grabWidget"): # Qt4 pixmap = qt.QPixmap.grabWidget(self.graph.getWidgetHandle()) else: # Qt5 pixmap = self.graph.getWidgetHandle().grab() if pixmap.save(filename, format_): return else: qt.QMessageBox.critical(self, "Save Error", "%s" % sys.exc_info()[1]) return def setSaveDirectory(self, wdir): if os.path.exists(wdir): self.saveDirectory = wdir return True else: return False def selectColormap(self): qt.QMessageBox.information(self, "Open", "Not implemented (yet)") def _zoomBack(self, pos): self.graph.getLimitsHistory().pop()
def setUp(self): super(_PlotWidgetTest, self).setUp() self.plot = PlotWidget() self.plot.show() self.qWaitForWindowExposed(self.plot)
def setUp(self): TestCaseQt.setUp(self) self.plot1 = PlotWidget() self.plot2 = PlotWidget() self.plot3 = PlotWidget()
class RGBCorrelatorGraph(qt.QWidget): sigProfileSignal = qt.pyqtSignal(object) def __init__(self, parent=None, backend=None, selection=False, aspect=True, colormap=False, imageicons=False, standalonesave=True, standalonezoom=True, profileselection=False, polygon=False): qt.QWidget.__init__(self, parent) self.mainLayout = qt.QVBoxLayout(self) self.mainLayout.setContentsMargins(0, 0, 0, 0) self.mainLayout.setSpacing(0) self._keepDataAspectRatioFlag = False self.graph = PlotWidget(parent=self, backend=backend) self.graph.setGraphXLabel("Column") self.graph.setGraphYLabel("Row") self.graph.setYAxisAutoScale(True) self.graph.setXAxisAutoScale(True) plotArea = self.graph.getWidgetHandle() plotArea.setContextMenuPolicy(qt.Qt.CustomContextMenu) plotArea.customContextMenuRequested.connect(self._zoomBack) self._buildToolBar(selection, colormap, imageicons, standalonesave, standalonezoom=standalonezoom, profileselection=profileselection, aspect=aspect, polygon=polygon) if profileselection: if len(self._pickerSelectionButtons): self.graph.sigPlotSignal.connect(\ self._graphPolygonSignalReceived) self._pickerSelectionWidthValue.valueChanged[int].connect( \ self.setPickerSelectionWith) self.saveDirectory = os.getcwd() self.mainLayout.addWidget(self.graph) def sizeHint(self): return qt.QSize(1.5 * qt.QWidget.sizeHint(self).width(), qt.QWidget.sizeHint(self).height()) def _buildToolBar(self, selection=False, colormap=False, imageicons=False, standalonesave=True, standalonezoom=True, profileselection=False, aspect=False, polygon=False): self.solidCircleIcon = qt.QIcon(qt.QPixmap(IconDict["solidcircle"])) self.solidEllipseIcon = qt.QIcon(qt.QPixmap(IconDict["solidellipse"])) self.colormapIcon = qt.QIcon(qt.QPixmap(IconDict["colormap"])) self.selectionIcon = qt.QIcon(qt.QPixmap(IconDict["normal"])) self.zoomResetIcon = qt.QIcon(qt.QPixmap(IconDict["zoomreset"])) self.polygonIcon = qt.QIcon(qt.QPixmap(IconDict["polygon"])) self.printIcon = qt.QIcon(qt.QPixmap(IconDict["fileprint"])) self.saveIcon = qt.QIcon(qt.QPixmap(IconDict["filesave"])) self.xAutoIcon = qt.QIcon(qt.QPixmap(IconDict["xauto"])) self.yAutoIcon = qt.QIcon(qt.QPixmap(IconDict["yauto"])) self.hFlipIcon = qt.QIcon(qt.QPixmap(IconDict["gioconda16mirror"])) self.imageIcon = qt.QIcon(qt.QPixmap(IconDict["image"])) self.eraseSelectionIcon = qt.QIcon(qt.QPixmap(IconDict["eraseselect"])) self.rectSelectionIcon = qt.QIcon(qt.QPixmap(IconDict["boxselect"])) self.brushSelectionIcon = qt.QIcon(qt.QPixmap(IconDict["brushselect"])) self.brushIcon = qt.QIcon(qt.QPixmap(IconDict["brush"])) self.additionalIcon = qt.QIcon(qt.QPixmap( IconDict["additionalselect"])) self.hLineIcon = qt.QIcon(qt.QPixmap(IconDict["horizontal"])) self.vLineIcon = qt.QIcon(qt.QPixmap(IconDict["vertical"])) self.lineIcon = qt.QIcon(qt.QPixmap(IconDict["diagonal"])) self.copyIcon = silx_icons.getQIcon("edit-copy") self.toolBar = qt.QWidget(self) self.toolBarLayout = qt.QHBoxLayout(self.toolBar) self.toolBarLayout.setContentsMargins(0, 0, 0, 0) self.toolBarLayout.setSpacing(0) self.mainLayout.addWidget(self.toolBar) #Autoscale if standalonezoom: tb = self._addToolButton(self.zoomResetIcon, self.__zoomReset, 'Auto-Scale the Graph') else: tb = self._addToolButton(self.zoomResetIcon, None, 'Auto-Scale the Graph') self.zoomResetToolButton = tb #y Autoscale tb = self._addToolButton(self.yAutoIcon, self._yAutoScaleToggle, 'Toggle Autoscale Y Axis (On/Off)', toggle=True, state=True) tb.setDown(True) self.yAutoScaleToolButton = tb tb.setDown(True) #x Autoscale tb = self._addToolButton(self.xAutoIcon, self._xAutoScaleToggle, 'Toggle Autoscale X Axis (On/Off)', toggle=True, state=True) self.xAutoScaleToolButton = tb tb.setDown(True) #Aspect ratio if aspect: self.aspectButton = self._addToolButton(self.solidCircleIcon, self._aspectButtonSignal, 'Keep data aspect ratio', toggle=False) self.aspectButton.setChecked(False) #colormap if colormap: tb = self._addToolButton(self.colormapIcon, None, 'Change Colormap') self.colormapToolButton = tb #flip tb = self._addToolButton(self.hFlipIcon, None, 'Flip Horizontal') self.hFlipToolButton = tb #save if standalonesave: tb = self._addToolButton(self.saveIcon, self._saveIconSignal, 'Save Graph') else: tb = self._addToolButton(self.saveIcon, None, 'Save') self.saveToolButton = tb self.copyToolButton = self._addToolButton(self.copyIcon, self._copyIconSignal, "Copy graph to clipboard") #Selection if selection: tb = self._addToolButton(self.selectionIcon, None, 'Toggle Selection Mode', toggle=True, state=False) tb.setDown(False) self.selectionToolButton = tb #image selection icons if imageicons: tb = self._addToolButton(self.imageIcon, None, 'Reset') self.imageToolButton = tb tb = self._addToolButton(self.eraseSelectionIcon, None, 'Erase Selection') self.eraseSelectionToolButton = tb tb = self._addToolButton(self.rectSelectionIcon, None, 'Rectangular Selection') self.rectSelectionToolButton = tb tb = self._addToolButton(self.brushSelectionIcon, None, 'Brush Selection') self.brushSelectionToolButton = tb tb = self._addToolButton(self.brushIcon, None, 'Select Brush') self.brushToolButton = tb if polygon: tb = self._addToolButton( self.polygonIcon, None, 'Polygon selection\nRight click to finish') self.polygonSelectionToolButton = tb tb = self._addToolButton(self.additionalIcon, None, 'Additional Selections Menu') self.additionalSelectionToolButton = tb else: if polygon: tb = self._addToolButton( self.polygonIcon, None, 'Polygon selection\nRight click to finish') self.polygonSelectionToolButton = tb self.imageToolButton = None #picker selection self._pickerSelectionButtons = [] if profileselection: self._profileSelection = True self._polygonSelection = False self._pickerSelectionButtons = [] if self._profileSelection: tb = self._addToolButton(self.hLineIcon, self._hLineProfileClicked, 'Horizontal Profile Selection', toggle=True, state=False) self.hLineProfileButton = tb self._pickerSelectionButtons.append(tb) tb = self._addToolButton(self.vLineIcon, self._vLineProfileClicked, 'Vertical Profile Selection', toggle=True, state=False) self.vLineProfileButton = tb self._pickerSelectionButtons.append(tb) tb = self._addToolButton(self.lineIcon, self._lineProfileClicked, 'Line Profile Selection', toggle=True, state=False) self.lineProfileButton = tb self._pickerSelectionButtons.append(tb) self._pickerSelectionWidthLabel = qt.QLabel(self.toolBar) self._pickerSelectionWidthLabel.setText("W:") self.toolBar.layout().addWidget( self._pickerSelectionWidthLabel) self._pickerSelectionWidthValue = qt.QSpinBox(self.toolBar) self._pickerSelectionWidthValue.setMinimum(0) self._pickerSelectionWidthValue.setMaximum(1000) self._pickerSelectionWidthValue.setValue(1) self.toolBar.layout().addWidget( self._pickerSelectionWidthValue) #tb = self._addToolButton(None, # self._lineProfileClicked, # 'Line Profile Selection', # toggle=True, # state=False) #tb.setText = "W:" #self.lineWidthProfileButton = tb #self._pickerSelectionButtons.append(tb) if self._polygonSelection: _logger.info("Polygon selection not implemented yet") #hide profile selection buttons if imageicons: for button in self._pickerSelectionButtons: button.hide() self.infoWidget = qt.QWidget(self.toolBar) self.infoWidget.mainLayout = qt.QHBoxLayout(self.infoWidget) self.infoWidget.mainLayout.setContentsMargins(0, 0, 0, 0) self.infoWidget.mainLayout.setSpacing(0) self.infoWidget.label = qt.QLabel(self.infoWidget) self.infoWidget.label.setText("X = ???? Y = ???? Z = ????") self.infoWidget.mainLayout.addWidget(self.infoWidget.label) self.toolBarLayout.addWidget(self.infoWidget) self.infoWidget.hide() self.toolBarLayout.addWidget(qt.HorizontalSpacer(self.toolBar)) # ---print self.printPreview = SingletonPrintPreviewToolButton(parent=self, plot=self.graph) self.printPreview.setIcon(self.printIcon) self.toolBarLayout.addWidget(self.printPreview) def _aspectButtonSignal(self): _logger.debug("_aspectButtonSignal") if self._keepDataAspectRatioFlag: self.keepDataAspectRatio(False) else: self.keepDataAspectRatio(True) def keepDataAspectRatio(self, flag=True): if flag: self._keepDataAspectRatioFlag = True self.aspectButton.setIcon(self.solidEllipseIcon) self.aspectButton.setToolTip("Set free data aspect ratio") else: self._keepDataAspectRatioFlag = False self.aspectButton.setIcon(self.solidCircleIcon) self.aspectButton.setToolTip("Keep data aspect ratio") self.graph.setKeepDataAspectRatio(self._keepDataAspectRatioFlag) def showInfo(self): self.infoWidget.show() def hideInfo(self): self.infoWidget.hide() def setInfoText(self, text): self.infoWidget.label.setText(text) def setMouseText(self, text=""): try: if len(text): qt.QToolTip.showText(self.cursor().pos(), text, self, qt.QRect()) else: qt.QToolTip.hideText() except: _logger.warning("Error trying to show mouse text <%s>" % text) def focusOutEvent(self, ev): qt.QToolTip.hideText() def infoText(self): return self.infoWidget.label.text() def setXLabel(self, label="Column"): return self.graph.setGraphXLabel(label) def setYLabel(self, label="Row"): return self.graph.setGraphYLabel(label) def getXLabel(self): return self.graph.getGraphXLabel() def getYLabel(self): return self.graph.getGraphYLabel() def hideImageIcons(self): if self.imageToolButton is None: return self.imageToolButton.hide() self.eraseSelectionToolButton.hide() self.rectSelectionToolButton.hide() self.brushSelectionToolButton.hide() self.brushToolButton.hide() if hasattr(self, "polygonSelectionToolButton"): self.polygonSelectionToolButton.hide() self.additionalSelectionToolButton.hide() def showImageIcons(self): if self.imageToolButton is None: return self.imageToolButton.show() self.eraseSelectionToolButton.show() self.rectSelectionToolButton.show() self.brushSelectionToolButton.show() self.brushToolButton.show() if hasattr(self, "polygonSelectionToolButton"): self.polygonSelectionToolButton.show() self.additionalSelectionToolButton.show() def _hLineProfileClicked(self): for button in self._pickerSelectionButtons: if button != self.hLineProfileButton: button.setChecked(False) if self.hLineProfileButton.isChecked(): self._setPickerSelectionMode("HORIZONTAL") else: self._setPickerSelectionMode(None) def _vLineProfileClicked(self): for button in self._pickerSelectionButtons: if button != self.vLineProfileButton: button.setChecked(False) if self.vLineProfileButton.isChecked(): self._setPickerSelectionMode("VERTICAL") else: self._setPickerSelectionMode(None) def _lineProfileClicked(self): for button in self._pickerSelectionButtons: if button != self.lineProfileButton: button.setChecked(False) if self.lineProfileButton.isChecked(): self._setPickerSelectionMode("LINE") else: self._setPickerSelectionMode(None) def setPickerSelectionWith(self, intValue): self._pickerSelectionWidthValue.setValue(intValue) #get the current mode mode = "NONE" for button in self._pickerSelectionButtons: if button.isChecked(): if button == self.hLineProfileButton: mode = "HORIZONTAL" elif button == self.vLineProfileButton: mode = "VERTICAL" elif button == self.lineProfileButton: mode = "LINE" ddict = {} ddict['event'] = "profileWidthChanged" ddict['pixelwidth'] = self._pickerSelectionWidthValue.value() ddict['mode'] = mode self.sigProfileSignal.emit(ddict) def hideProfileSelectionIcons(self): if not len(self._pickerSelectionButtons): return for button in self._pickerSelectionButtons: button.setChecked(False) button.hide() self._pickerSelectionWidthLabel.hide() self._pickerSelectionWidthValue.hide() if self.graph.getInteractiveMode()['mode'] == 'draw': self.graph.setInteractiveMode('select') def showProfileSelectionIcons(self): if not len(self._pickerSelectionButtons): return for button in self._pickerSelectionButtons: button.show() self._pickerSelectionWidthLabel.show() self._pickerSelectionWidthValue.show() def getPickerSelectionMode(self): if not len(self._pickerSelectionButtons): return None if self.hLineProfileButton.isChecked(): return "HORIZONTAL" if self.vLineProfileButton.isChecked(): return "VERTICAL" if self.lineProfileButton.isChecked(): return "LINE" return None def _setPickerSelectionMode(self, mode=None): if mode is None: self.graph.setInteractiveMode('zoom') else: if mode == "HORIZONTAL": shape = "hline" elif mode == "VERTICAL": shape = "vline" else: shape = "line" self.graph.setInteractiveMode('draw', shape=shape, label=mode) ddict = {} if mode is None: mode = "NONE" ddict['event'] = "profileModeChanged" ddict['mode'] = mode self.sigProfileSignal.emit(ddict) def _graphPolygonSignalReceived(self, ddict): _logger.debug("PolygonSignal Received") for key in ddict.keys(): _logger.debug("%s: %s", key, ddict[key]) if ddict['event'] not in ['drawingProgress', 'drawingFinished']: return label = ddict['parameters']['label'] if label not in ['HORIZONTAL', 'VERTICAL', 'LINE']: return ddict['mode'] = label ddict['pixelwidth'] = self._pickerSelectionWidthValue.value() self.sigProfileSignal.emit(ddict) def _addToolButton(self, icon, action, tip, toggle=None, state=None, position=None): tb = qt.QToolButton(self.toolBar) if icon is not None: tb.setIcon(icon) tb.setToolTip(tip) if toggle is not None: if toggle: tb.setCheckable(1) if state is not None: if state: tb.setChecked(state) else: tb.setChecked(False) if position is not None: self.toolBarLayout.insertWidget(position, tb) else: self.toolBarLayout.addWidget(tb) if action is not None: tb.clicked.connect(action) return tb def __zoomReset(self): self._zoomReset() def _zoomReset(self, replot=None): _logger.debug("_zoomReset") if self.graph is not None: self.graph.resetZoom() def _yAutoScaleToggle(self): if self.graph is not None: self.yAutoScaleToolButton.setDown( not self.graph.isYAxisAutoScale()) self.graph.setYAxisAutoScale(not self.graph.isYAxisAutoScale()) def _xAutoScaleToggle(self): if self.graph is not None: self.xAutoScaleToolButton.setDown( not self.graph.isXAxisAutoScale()) self.graph.setXAxisAutoScale(not self.graph.isXAxisAutoScale()) def _copyIconSignal(self): pngFile = BytesIO() self.graph.saveGraph(pngFile, fileFormat='png') pngFile.flush() pngFile.seek(0) pngData = pngFile.read() pngFile.close() image = qt.QImage.fromData(pngData, 'png') qt.QApplication.clipboard().setImage(image) def _saveIconSignal(self): self.saveDirectory = PyMcaDirs.outputDir fileTypeList = [ "Image *.png", "Image *.jpg", "ZoomedImage *.png", "ZoomedImage *.jpg", "Widget *.png", "Widget *.jpg" ] outfile = qt.QFileDialog(self) outfile.setModal(1) outfile.setWindowTitle("Output File Selection") if hasattr(qt, "QStringList"): strlist = qt.QStringList() else: strlist = [] for f in fileTypeList: strlist.append(f) if hasattr(outfile, "setFilters"): outfile.setFilters(strlist) else: outfile.setNameFilters(strlist) outfile.setFileMode(outfile.AnyFile) outfile.setAcceptMode(qt.QFileDialog.AcceptSave) outfile.setDirectory(self.saveDirectory) ret = outfile.exec_() if not ret: return if hasattr(outfile, "selectedFilter"): filterused = qt.safe_str(outfile.selectedFilter()).split() else: filterused = qt.safe_str(outfile.selectedNameFilter()).split() filetype = filterused[0] extension = filterused[1] outstr = qt.safe_str(outfile.selectedFiles()[0]) try: outputFile = os.path.basename(outstr) except: outputFile = outstr outputDir = os.path.dirname(outstr) self.saveDirectory = outputDir PyMcaDirs.outputDir = outputDir #always overwrite for the time being if len(outputFile) < len(extension[1:]): outputFile += extension[1:] elif outputFile[-4:] != extension[1:]: outputFile += extension[1:] outputFile = os.path.join(outputDir, outputFile) if os.path.exists(outputFile): try: os.remove(outputFile) except: qt.QMessageBox.critical(self, "Save Error", "Cannot overwrite existing file") return if filetype.upper() == "IMAGE": self.saveGraphImage(outputFile, original=True) elif filetype.upper() == "ZOOMEDIMAGE": self.saveGraphImage(outputFile, original=False) else: self.saveGraphWidget(outputFile) def saveGraphImage(self, filename, original=False): format_ = filename[-3:].upper() activeImage = self.graph.getActiveImage() rgbdata = activeImage.getRgbaImageData() # silx to pymca scale convention (a + b x) xScale = activeImage.getOrigin()[0], activeImage.getScale()[0] yScale = activeImage.getOrigin()[1], activeImage.getScale()[1] if original: # save whole image bgradata = numpy.array(rgbdata, copy=True) bgradata[:, :, 0] = rgbdata[:, :, 2] bgradata[:, :, 2] = rgbdata[:, :, 0] else: shape = rgbdata.shape[:2] xmin, xmax = self.graph.getGraphXLimits() ymin, ymax = self.graph.getGraphYLimits() # save zoomed image, for that we have to get the limits r0, c0 = convertToRowAndColumn(xmin, ymin, shape, xScale=xScale, yScale=yScale, safe=True) r1, c1 = convertToRowAndColumn(xmax, ymax, shape, xScale=xScale, yScale=yScale, safe=True) row0 = int(min(r0, r1)) row1 = int(max(r0, r1)) col0 = int(min(c0, c1)) col1 = int(max(c0, c1)) if row1 < shape[0]: row1 += 1 if col1 < shape[1]: col1 += 1 tmpArray = rgbdata[row0:row1, col0:col1, :] bgradata = numpy.array(tmpArray, copy=True, dtype=rgbdata.dtype) bgradata[:, :, 0] = tmpArray[:, :, 2] bgradata[:, :, 2] = tmpArray[:, :, 0] if self.graph.isYAxisInverted(): qImage = qt.QImage(bgradata, bgradata.shape[1], bgradata.shape[0], qt.QImage.Format_ARGB32) else: qImage = qt.QImage(bgradata, bgradata.shape[1], bgradata.shape[0], qt.QImage.Format_ARGB32).mirrored(False, True) pixmap = qt.QPixmap.fromImage(qImage) if pixmap.save(filename, format_): return else: qt.QMessageBox.critical(self, "Save Error", "%s" % sys.exc_info()[1]) return def saveGraphWidget(self, filename): format_ = filename[-3:].upper() if hasattr(qt.QPixmap, "grabWidget"): # Qt4 pixmap = qt.QPixmap.grabWidget(self.graph.getWidgetHandle()) else: # Qt5 pixmap = self.graph.getWidgetHandle().grab() if pixmap.save(filename, format_): return else: qt.QMessageBox.critical(self, "Save Error", "%s" % sys.exc_info()[1]) return def setSaveDirectory(self, wdir): if os.path.exists(wdir): self.saveDirectory = wdir return True else: return False def selectColormap(self): qt.QMessageBox.information(self, "Open", "Not implemented (yet)") def _zoomBack(self, pos): self.graph.getLimitsHistory().pop()
def getCommentAndPosition(self): legends = self.getPlot().getAllCurves(just_legend=True) comment = "Curves displayed in widget 1:\n\t" if legends: comment += ", ".join(legends) else: comment += "none" return comment, "CENTER" app = qt.QApplication([]) x = numpy.arange(1000) # first widget has a standalone preview action with custom title and comment pw1 = PlotWidget() pw1.setWindowTitle("Widget 1 with standalone print preview") toolbar1 = qt.QToolBar(pw1) toolbutton1 = MyPrintPreviewButton(parent=toolbar1, plot=pw1) pw1.addToolBar(toolbar1) toolbar1.addWidget(toolbutton1) pw1.show() pw1.addCurve(x, numpy.tan(x * 2 * numpy.pi / 1000)) # next two plots share a common standard print preview pw2 = PlotWidget() pw2.setWindowTitle("Widget 2 with shared print preview") toolbar2 = qt.QToolBar(pw2) toolbutton2 = PrintPreviewToolButton.SingletonPrintPreviewToolButton( parent=toolbar2, plot=pw2) pw2.addToolBar(toolbar2)
def setActiveCurve(self, legend, replot=True): return PlotWidget.setActiveCurve(self, legend)
def removeCurve(self, *var, **kw): if "replot" in kw: del kw["replot"] # silx schedules replots, explicit replot call # should not be needed return PlotWidget.removeCurve(self, *var, **kw)
def __init__(self, parent=None): qt.QMainWindow.__init__(self, parent=parent) if parent is not None: # behave as a widget self.setWindowFlags(qt.Qt.Widget) else: self.setWindowTitle("PyMca - Image Selection Tool") centralWidget = qt.QWidget(self) layout = qt.QVBoxLayout(centralWidget) centralWidget.setLayout(layout) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) # Plot self.plot = PlotWidget(parent=centralWidget) self.plot.setWindowFlags(qt.Qt.Widget) self.plot.setDefaultColormap({'name': 'temperature', 'normalization': 'linear', 'autoscale': True, 'vmin': 0., 'vmax': 1.}) layout.addWidget(self.plot) # Mask Widget self._maskToolsDockWidget = None # Image selection slider self.slider = qt.QSlider(self.centralWidget()) self.slider.setOrientation(qt.Qt.Horizontal) self.slider.setMinimum(0) self.slider.setMaximum(0) layout.addWidget(self.slider) self.slider.valueChanged[int].connect(self.showImage) # ADD/REMOVE/REPLACE IMAGE buttons buttonBox = qt.QWidget(self) buttonBoxLayout = qt.QHBoxLayout(buttonBox) buttonBoxLayout.setContentsMargins(0, 0, 0, 0) buttonBoxLayout.setSpacing(0) self.addImageButton = qt.QPushButton(buttonBox) icon = qt.QIcon(qt.QPixmap(IconDict["rgb16"])) self.addImageButton.setIcon(icon) self.addImageButton.setText("ADD IMAGE") self.addImageButton.setToolTip("Add image to RGB correlator") buttonBoxLayout.addWidget(self.addImageButton) self.removeImageButton = qt.QPushButton(buttonBox) self.removeImageButton.setIcon(icon) self.removeImageButton.setText("REMOVE IMAGE") self.removeImageButton.setToolTip("Remove image from RGB correlator") buttonBoxLayout.addWidget(self.removeImageButton) self.replaceImageButton = qt.QPushButton(buttonBox) self.replaceImageButton.setIcon(icon) self.replaceImageButton.setText("REPLACE IMAGE") self.replaceImageButton.setToolTip( "Replace all images in RGB correlator with this one") buttonBoxLayout.addWidget(self.replaceImageButton) self.addImageButton.clicked.connect(self._addImageClicked) self.removeImageButton.clicked.connect(self._removeImageClicked) self.replaceImageButton.clicked.connect(self._replaceImageClicked) layout.addWidget(buttonBox) # median filter widget self._medianParameters = {'row_width': 1, 'column_width': 1, 'conditional': 0} self._medianParametersWidget = MedianParameters(self, use_conditional=True) self._medianParametersWidget.widthSpin.setValue(1) self._medianParametersWidget.widthSpin.valueChanged[int].connect( self._setMedianKernelWidth) self._medianParametersWidget.conditionalSpin.valueChanged[int].connect( self._setMedianConditionalFlag) layout.addWidget(self._medianParametersWidget) # motor positions (hidden by default) self.motorPositionsWidget = MotorInfoWindow.MotorInfoDialog(self, [""], [{}]) self.motorPositionsWidget.setMaximumHeight(100) self.plot.sigPlotSignal.connect(self._updateMotors) self.motorPositionsWidget.hide() self._motors_first_update = True layout.addWidget(self.motorPositionsWidget) self.setCentralWidget(centralWidget) # Init actions self.group = qt.QActionGroup(self) self.group.setExclusive(False) self.resetZoomAction = self.group.addAction( PlotActions.ResetZoomAction(plot=self.plot, parent=self)) self.addAction(self.resetZoomAction) self.zoomInAction = PlotActions.ZoomInAction(plot=self.plot, parent=self) self.addAction(self.zoomInAction) self.zoomOutAction = PlotActions.ZoomOutAction(plot=self.plot, parent=self) self.addAction(self.zoomOutAction) self.xAxisAutoScaleAction = self.group.addAction( PlotActions.XAxisAutoScaleAction(plot=self.plot, parent=self)) self.addAction(self.xAxisAutoScaleAction) self.yAxisAutoScaleAction = self.group.addAction( PlotActions.YAxisAutoScaleAction(plot=self.plot, parent=self)) self.addAction(self.yAxisAutoScaleAction) self.colormapAction = self.group.addAction( PlotActions.ColormapAction(plot=self.plot, parent=self)) self.addAction(self.colormapAction) self.copyAction = self.group.addAction( PlotActions.CopyAction(plot=self.plot, parent=self)) self.addAction(self.copyAction) self.group.addAction(self.getMaskAction()) # Init toolbuttons self.saveToolbutton = SaveToolButton(parent=self, maskImageWidget=self) self.yAxisInvertedButton = PlotToolButtons.YAxisOriginToolButton( parent=self, plot=self.plot) self.keepDataAspectRatioButton = PlotToolButtons.AspectToolButton( parent=self, plot=self.plot) self.backgroundButton = qt.QToolButton(self) self.backgroundButton.setCheckable(True) self.backgroundButton.setIcon(qt.QIcon(qt.QPixmap(IconDict["subtract"]))) self.backgroundButton.setToolTip( 'Toggle background image subtraction from current image\n' + 'No action if no background image available.') self.backgroundButton.clicked.connect(self._subtractBackground) # Creating the toolbar also create actions for toolbuttons self._toolbar = self._createToolBar(title='Plot', parent=None) self.addToolBar(self._toolbar) self._profile = ProfileToolBar(plot=self.plot) self.addToolBar(self._profile) self.setProfileToolbarVisible(False) # add a transparency slider for the stack data self._alphaSliderToolbar = qt.QToolBar("Alpha slider", parent=self) self._alphaSlider = NamedImageAlphaSlider(parent=self._alphaSliderToolbar, plot=self.plot, legend="current") self._alphaSlider.setOrientation(qt.Qt.Vertical) self._alphaSlider.setToolTip("Adjust opacity of stack image overlay") self._alphaSliderToolbar.addWidget(self._alphaSlider) self.addToolBar(qt.Qt.RightToolBarArea, self._alphaSliderToolbar) # hide optional tools and actions self.setAlphaSliderVisible(False) self.setBackgroundActionVisible(False) self.setMedianFilterWidgetVisible(False) self.setProfileToolbarVisible(False) self._images = [] """List of images, as 2D numpy arrays or 3D numpy arrays (RGB(A)). """ self._labels = [] """List of image labels. """ self._bg_images = [] """List of background images, as 2D numpy arrays or 3D numpy arrays (RGB(A)). These images are not active, their colormap cannot be changed and they cannot be the base image used for drawing a mask. """ self._bg_labels = [] self._deltaXY = (1.0, 1.0) # TODO: allow different scale and origin for each image """Current image scale (Xscale, Yscale) (in axis units per image pixel). The scale is adjusted to keep constant width and height for the image when a crop operation is applied.""" self._origin = (0., 0.) """Current image origin: coordinate (x, y) of sample located at (row, column) = (0, 0)""" # scales and origins for background images self._bg_deltaXY = [] self._bg_origins = []
class TestLimitConstaints(unittest.TestCase): """Tests setLimitConstaints class""" def setUp(self): self.plot = PlotWidget() def tearDown(self): self.plot = None def testApi(self): """Test availability of the API""" self.plot.getXAxis().setLimitsConstraints(minPos=1, maxPos=10) self.plot.getXAxis().setRangeConstraints(minRange=1, maxRange=1) self.plot.getYAxis().setLimitsConstraints(minPos=1, maxPos=10) self.plot.getYAxis().setRangeConstraints(minRange=1, maxRange=1) def testXMinMax(self): """Test limit constains on x-axis""" self.plot.getXAxis().setLimitsConstraints(minPos=0, maxPos=100) self.plot.setLimits(xmin=-1, xmax=101, ymin=-1, ymax=101) self.assertEqual(self.plot.getXAxis().getLimits(), (0, 100)) self.assertEqual(self.plot.getYAxis().getLimits(), (-1, 101)) def testYMinMax(self): """Test limit constains on y-axis""" self.plot.getYAxis().setLimitsConstraints(minPos=0, maxPos=100) self.plot.setLimits(xmin=-1, xmax=101, ymin=-1, ymax=101) self.assertEqual(self.plot.getXAxis().getLimits(), (-1, 101)) self.assertEqual(self.plot.getYAxis().getLimits(), (0, 100)) def testMinXRange(self): """Test min range constains on x-axis""" self.plot.getXAxis().setRangeConstraints(minRange=100) self.plot.setLimits(xmin=1, xmax=99, ymin=1, ymax=99) limits = self.plot.getXAxis().getLimits() self.assertEqual(limits[1] - limits[0], 100) limits = self.plot.getYAxis().getLimits() self.assertNotEqual(limits[1] - limits[0], 100) def testMaxXRange(self): """Test max range constains on x-axis""" self.plot.getXAxis().setRangeConstraints(maxRange=100) self.plot.setLimits(xmin=-1, xmax=101, ymin=-1, ymax=101) limits = self.plot.getXAxis().getLimits() self.assertEqual(limits[1] - limits[0], 100) limits = self.plot.getYAxis().getLimits() self.assertNotEqual(limits[1] - limits[0], 100) def testMinYRange(self): """Test min range constains on y-axis""" self.plot.getYAxis().setRangeConstraints(minRange=100) self.plot.setLimits(xmin=1, xmax=99, ymin=1, ymax=99) limits = self.plot.getXAxis().getLimits() self.assertNotEqual(limits[1] - limits[0], 100) limits = self.plot.getYAxis().getLimits() self.assertEqual(limits[1] - limits[0], 100) def testMaxYRange(self): """Test max range constains on y-axis""" self.plot.getYAxis().setRangeConstraints(maxRange=100) self.plot.setLimits(xmin=-1, xmax=101, ymin=-1, ymax=101) limits = self.plot.getXAxis().getLimits() self.assertNotEqual(limits[1] - limits[0], 100) limits = self.plot.getYAxis().getLimits() self.assertEqual(limits[1] - limits[0], 100) def testChangeOfConstraints(self): """Test changing of the constraints""" self.plot.getXAxis().setRangeConstraints(minRange=10, maxRange=10) # There is no more constraints on the range self.plot.getXAxis().setRangeConstraints(minRange=None, maxRange=None) self.plot.setLimits(xmin=-1, xmax=101, ymin=-1, ymax=101) self.assertEqual(self.plot.getXAxis().getLimits(), (-1, 101)) def testSettingConstraints(self): """Test setting a constaint (setLimits first then the constaint)""" self.plot.setLimits(xmin=-1, xmax=101, ymin=-1, ymax=101) self.plot.getXAxis().setLimitsConstraints(minPos=0, maxPos=100) self.assertEqual(self.plot.getXAxis().getLimits(), (0, 100))
class SilxMaskImageWidget(qt.QMainWindow): """Main window with a plot widget, a toolbar and a slider. A list of images can be set with :meth:`setImages`. The mask can be accessed through getter and setter methods: :meth:`setSelectionMask` and :meth:`getSelectionMask`. The plot widget can be accessed as :attr:`plot`. It is a silx plot widget. The toolbar offers some basic interaction tools: zoom control, colormap, aspect ratio, y axis orientation, "save image" menu and a mask widget. """ sigMaskImageWidget = qt.pyqtSignal(object) def __init__(self, parent=None): qt.QMainWindow.__init__(self, parent=parent) if parent is not None: # behave as a widget self.setWindowFlags(qt.Qt.Widget) else: self.setWindowTitle("PyMca - Image Selection Tool") centralWidget = qt.QWidget(self) layout = qt.QVBoxLayout(centralWidget) centralWidget.setLayout(layout) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) # Plot self.plot = PlotWidget(parent=centralWidget) self.plot.setWindowFlags(qt.Qt.Widget) self.plot.setDefaultColormap({'name': 'temperature', 'normalization': 'linear', 'autoscale': True, 'vmin': 0., 'vmax': 1.}) layout.addWidget(self.plot) # Mask Widget self._maskToolsDockWidget = None # Image selection slider self.slider = qt.QSlider(self.centralWidget()) self.slider.setOrientation(qt.Qt.Horizontal) self.slider.setMinimum(0) self.slider.setMaximum(0) layout.addWidget(self.slider) self.slider.valueChanged[int].connect(self.showImage) # ADD/REMOVE/REPLACE IMAGE buttons buttonBox = qt.QWidget(self) buttonBoxLayout = qt.QHBoxLayout(buttonBox) buttonBoxLayout.setContentsMargins(0, 0, 0, 0) buttonBoxLayout.setSpacing(0) self.addImageButton = qt.QPushButton(buttonBox) icon = qt.QIcon(qt.QPixmap(IconDict["rgb16"])) self.addImageButton.setIcon(icon) self.addImageButton.setText("ADD IMAGE") self.addImageButton.setToolTip("Add image to RGB correlator") buttonBoxLayout.addWidget(self.addImageButton) self.removeImageButton = qt.QPushButton(buttonBox) self.removeImageButton.setIcon(icon) self.removeImageButton.setText("REMOVE IMAGE") self.removeImageButton.setToolTip("Remove image from RGB correlator") buttonBoxLayout.addWidget(self.removeImageButton) self.replaceImageButton = qt.QPushButton(buttonBox) self.replaceImageButton.setIcon(icon) self.replaceImageButton.setText("REPLACE IMAGE") self.replaceImageButton.setToolTip( "Replace all images in RGB correlator with this one") buttonBoxLayout.addWidget(self.replaceImageButton) self.addImageButton.clicked.connect(self._addImageClicked) self.removeImageButton.clicked.connect(self._removeImageClicked) self.replaceImageButton.clicked.connect(self._replaceImageClicked) layout.addWidget(buttonBox) # median filter widget self._medianParameters = {'row_width': 1, 'column_width': 1, 'conditional': 0} self._medianParametersWidget = MedianParameters(self, use_conditional=True) self._medianParametersWidget.widthSpin.setValue(1) self._medianParametersWidget.widthSpin.valueChanged[int].connect( self._setMedianKernelWidth) self._medianParametersWidget.conditionalSpin.valueChanged[int].connect( self._setMedianConditionalFlag) layout.addWidget(self._medianParametersWidget) # motor positions (hidden by default) self.motorPositionsWidget = MotorInfoWindow.MotorInfoDialog(self, [""], [{}]) self.motorPositionsWidget.setMaximumHeight(100) self.plot.sigPlotSignal.connect(self._updateMotors) self.motorPositionsWidget.hide() self._motors_first_update = True layout.addWidget(self.motorPositionsWidget) self.setCentralWidget(centralWidget) # Init actions self.group = qt.QActionGroup(self) self.group.setExclusive(False) self.resetZoomAction = self.group.addAction( PlotActions.ResetZoomAction(plot=self.plot, parent=self)) self.addAction(self.resetZoomAction) self.zoomInAction = PlotActions.ZoomInAction(plot=self.plot, parent=self) self.addAction(self.zoomInAction) self.zoomOutAction = PlotActions.ZoomOutAction(plot=self.plot, parent=self) self.addAction(self.zoomOutAction) self.xAxisAutoScaleAction = self.group.addAction( PlotActions.XAxisAutoScaleAction(plot=self.plot, parent=self)) self.addAction(self.xAxisAutoScaleAction) self.yAxisAutoScaleAction = self.group.addAction( PlotActions.YAxisAutoScaleAction(plot=self.plot, parent=self)) self.addAction(self.yAxisAutoScaleAction) self.colormapAction = self.group.addAction( PlotActions.ColormapAction(plot=self.plot, parent=self)) self.addAction(self.colormapAction) self.copyAction = self.group.addAction( PlotActions.CopyAction(plot=self.plot, parent=self)) self.addAction(self.copyAction) self.group.addAction(self.getMaskAction()) # Init toolbuttons self.saveToolbutton = SaveToolButton(parent=self, maskImageWidget=self) self.yAxisInvertedButton = PlotToolButtons.YAxisOriginToolButton( parent=self, plot=self.plot) self.keepDataAspectRatioButton = PlotToolButtons.AspectToolButton( parent=self, plot=self.plot) self.backgroundButton = qt.QToolButton(self) self.backgroundButton.setCheckable(True) self.backgroundButton.setIcon(qt.QIcon(qt.QPixmap(IconDict["subtract"]))) self.backgroundButton.setToolTip( 'Toggle background image subtraction from current image\n' + 'No action if no background image available.') self.backgroundButton.clicked.connect(self._subtractBackground) # Creating the toolbar also create actions for toolbuttons self._toolbar = self._createToolBar(title='Plot', parent=None) self.addToolBar(self._toolbar) self._profile = ProfileToolBar(plot=self.plot) self.addToolBar(self._profile) self.setProfileToolbarVisible(False) # add a transparency slider for the stack data self._alphaSliderToolbar = qt.QToolBar("Alpha slider", parent=self) self._alphaSlider = NamedImageAlphaSlider(parent=self._alphaSliderToolbar, plot=self.plot, legend="current") self._alphaSlider.setOrientation(qt.Qt.Vertical) self._alphaSlider.setToolTip("Adjust opacity of stack image overlay") self._alphaSliderToolbar.addWidget(self._alphaSlider) self.addToolBar(qt.Qt.RightToolBarArea, self._alphaSliderToolbar) # hide optional tools and actions self.setAlphaSliderVisible(False) self.setBackgroundActionVisible(False) self.setMedianFilterWidgetVisible(False) self.setProfileToolbarVisible(False) self._images = [] """List of images, as 2D numpy arrays or 3D numpy arrays (RGB(A)). """ self._labels = [] """List of image labels. """ self._bg_images = [] """List of background images, as 2D numpy arrays or 3D numpy arrays (RGB(A)). These images are not active, their colormap cannot be changed and they cannot be the base image used for drawing a mask. """ self._bg_labels = [] self._deltaXY = (1.0, 1.0) # TODO: allow different scale and origin for each image """Current image scale (Xscale, Yscale) (in axis units per image pixel). The scale is adjusted to keep constant width and height for the image when a crop operation is applied.""" self._origin = (0., 0.) """Current image origin: coordinate (x, y) of sample located at (row, column) = (0, 0)""" # scales and origins for background images self._bg_deltaXY = [] self._bg_origins = [] def sizeHint(self): return qt.QSize(500, 400) def _createToolBar(self, title, parent): """Create a QToolBar with crop, rotate and flip operations :param str title: The title of the QMenu :param qt.QWidget parent: See :class:`QToolBar` """ toolbar = qt.QToolBar(title, parent) # Order widgets with actions objects = self.group.actions() # Add standard push buttons to list index = objects.index(self.colormapAction) objects.insert(index + 1, self.keepDataAspectRatioButton) objects.insert(index + 2, self.yAxisInvertedButton) objects.insert(index + 3, self.saveToolbutton) objects.insert(index + 4, self.backgroundButton) for obj in objects: if isinstance(obj, qt.QAction): toolbar.addAction(obj) else: # keep reference to toolbutton's action for changing visibility if obj is self.keepDataAspectRatioButton: self.keepDataAspectRatioAction = toolbar.addWidget(obj) elif obj is self.yAxisInvertedButton: self.yAxisInvertedAction = toolbar.addWidget(obj) elif obj is self.saveToolbutton: self.saveAction = toolbar.addWidget(obj) elif obj is self.backgroundButton: self.bgAction = toolbar.addWidget(obj) else: raise RuntimeError() return toolbar def _getMaskToolsDockWidget(self): """DockWidget with image mask panel (lazy-loaded).""" if self._maskToolsDockWidget is None: self._maskToolsDockWidget = MyMaskToolsDockWidget( plot=self.plot, name='Mask') self._maskToolsDockWidget.hide() self.addDockWidget(qt.Qt.RightDockWidgetArea, self._maskToolsDockWidget) # self._maskToolsDockWidget.setFloating(True) self._maskToolsDockWidget.sigMaskChanged.connect( self._emitMaskImageWidgetSignal) return self._maskToolsDockWidget def _setMedianKernelWidth(self, value): kernelSize = numpy.asarray(value) if len(kernelSize.shape) == 0: kernelSize = [kernelSize.item()] * 2 self._medianParameters['row_width'] = kernelSize[0] self._medianParameters['column_width'] = kernelSize[1] self._medianParametersWidget.widthSpin.setValue(int(kernelSize[0])) self.showImage(self.slider.value()) def _setMedianConditionalFlag(self, value): self._medianParameters['conditional'] = int(value) self._medianParametersWidget.conditionalSpin.setValue(int(value)) self.showImage(self.slider.value()) def _subtractBackground(self): """When background button is clicked, this causes showImage to display the data after subtracting the stack background image. This background image is unrelated to the background images set with :meth:`setBackgroundImages`, it is simply the first data image whose label ends with 'background'.""" current = self.getCurrentIndex() self.showImage(current) def _updateMotors(self, ddict): if not ddict["event"] == "mouseMoved": return if not self.motorPositionsWidget.isVisible(): return motorsValuesAtCursor = self._getPositionersFromXY(ddict["x"], ddict["y"]) if motorsValuesAtCursor is None: return self.motorPositionsWidget.table.updateTable( legList=[self.plot.getActiveImage().getLegend()], motList=[motorsValuesAtCursor]) if self._motors_first_update: self._select_motors() self._motors_first_update = False def _select_motors(self): """This methods sets the motors in the comboboxes when the widget is first initialized.""" for i, combobox in enumerate(self.motorPositionsWidget.table.header.boxes): # First item (index 0) in combobox is "", so first motor name is at index 1. # First combobox in header.boxes is at index 1 (boxes[0] is None). if i == 0: continue if i < combobox.count(): combobox.setCurrentIndex(i) def _getPositionersFromXY(self, x, y): """Return positioner values for a stack pixel identified by it's (x, y) coordinates. """ activeImage = self.plot.getActiveImage() if activeImage is None: return None info = activeImage.getInfo() if not info or not isinstance(info, dict): return None positioners = info.get("positioners", {}) nRows, nCols = activeImage.getData().shape xScale, yScale = activeImage.getScale() xOrigin, yOrigin = activeImage.getOrigin() r, c = convertToRowAndColumn( x, y, shape=(nRows, nCols), xScale=(xOrigin, xScale), yScale=(yOrigin, yScale), safe=True) idx1d = r * nCols + c positionersAtIdx = {} for motorName, motorValues in positioners.items(): if numpy.isscalar(motorValues): positionersAtIdx[motorName] = motorValues elif len(motorValues.shape) == 1: positionersAtIdx[motorName] = motorValues[idx1d] else: positionersAtIdx[motorName] = motorValues.reshape((-1,))[idx1d] return positionersAtIdx # widgets visibility toggling def setBackgroundActionVisible(self, visible): """Set visibility of the background toolbar button. :param visible: True to show tool button, False to hide it. """ self.bgAction.setVisible(visible) def setProfileToolbarVisible(self, visible): """Set visibility of the profile toolbar :param visible: True to show toolbar, False to hide it. """ self._profile.setVisible(visible) def setMedianFilterWidgetVisible(self, visible): """Set visibility of the median filter parametrs widget. :param visible: True to show widget, False to hide it. """ self._medianParametersWidget.setVisible(visible) def setMotorPositionsVisible(self, flag): """Show or hide motor positions widget""" self.motorPositionsWidget.setVisible(flag) def setAlphaSliderVisible(self, visible): """Set visibility of the transparency slider widget in the right toolbar area. :param visible: True to show widget, False to hide it. """ self._alphaSliderToolbar.setVisible(visible) def setImagesAlpha(self, alpha): """Set the opacity of the images layer. Full opacity means that the background images layer will not be visible. :param float alpha: Opacity of images layer, in [0., 1.] """ self._alphaSlider.setValue(round(alpha * 255)) def getMaskAction(self): """QAction toggling image mask dock widget :rtype: QAction """ return self._getMaskToolsDockWidget().toggleViewAction() def _emitMaskImageWidgetSignal(self): mask = self.getSelectionMask() if not mask.size: # workaround to ignore the empty mask emitted when the mask widget # is initialized return self.sigMaskImageWidget.emit( {"event": "selectionMaskChanged", "current": self.getSelectionMask(), "id": id(self)}) def setSelectionMask(self, mask, copy=True): """Set the mask to a new array. :param numpy.ndarray mask: The array to use for the mask. Mask type: array of uint8 of dimension 2, Array of other types are converted. :param bool copy: True (the default) to copy the array, False to use it as is if possible. :return: None if failed, shape of mask as 2-tuple if successful. The mask can be cropped or padded to fit active image, the returned shape is that of the active image. """ # disconnect temporarily to avoid infinite loop self._getMaskToolsDockWidget().sigMaskChanged.disconnect( self._emitMaskImageWidgetSignal) if mask is None and silx.version_info <= (0, 7, 0): self._getMaskToolsDockWidget().resetSelectionMask() ret = None else: # from silx 0.8 onwards, setSelectionMask(None) is supported ret = self._getMaskToolsDockWidget().setSelectionMask(mask, copy=copy) self._getMaskToolsDockWidget().sigMaskChanged.connect( self._emitMaskImageWidgetSignal) return ret def getSelectionMask(self, copy=True): """Get the current mask as a 2D array. :param bool copy: True (default) to get a copy of the mask. If False, the returned array MUST not be modified. :return: The array of the mask with dimension of the 'active' image. If there is no active image, None is returned. :rtype: 2D numpy.ndarray of uint8 """ return self._getMaskToolsDockWidget().getSelectionMask(copy=copy) @staticmethod def _RgbaToGrayscale(image): """Convert RGBA image to 2D array of grayscale values (Luma coding) :param image: RGBA image, as a numpy array of shapes (nrows, ncols, 3/4) :return: Image as a 2D array """ if len(image.shape) == 2: return image assert len(image.shape) == 3 imageData = image[:, :, 0] * 0.299 +\ image[:, :, 1] * 0.587 +\ image[:, :, 2] * 0.114 return imageData def getImageData(self): """Return current image data to be sent to RGB correlator :return: Image as a 2D array """ index = self.slider.value() image = self._images[index] return self._RgbaToGrayscale(image) def getFirstBgImageData(self): """Return first bg image data to be sent to RGB correlator :return: Image as a 2D array """ image = self._bg_images[0] return self._RgbaToGrayscale(image) def getBgImagesDict(self): """Return a dict containing the data for all background images.""" bgimages = {} for i, label in enumerate(self._bg_labels): data = self._bg_images[i] origin = self._bg_origins[i] delta_w, delta_h = self._bg_deltaXY[i] w, h = delta_w * data.shape[1], delta_h * data.shape[0] bgimages[label] = {"data": data, "origin": origin, "width": w, "height": h} return bgimages def _addImageClicked(self): imageData = self.getImageData() ddict = { 'event': "addImageClicked", 'image': imageData, 'title': self.plot.getGraphTitle(), 'id': id(self)} self.sigMaskImageWidget.emit(ddict) def _replaceImageClicked(self): imageData = self.getImageData() ddict = { 'event': "replaceImageClicked", 'image': imageData, 'title': self.plot.getGraphTitle(), 'id': id(self)} self.sigMaskImageWidget.emit(ddict) def _removeImageClicked(self): imageData = self.getImageData() ddict = { 'event': "removeImageClicked", 'image': imageData, 'title': self.plot.getGraphTitle(), 'id': id(self)} self.sigMaskImageWidget.emit(ddict) def showImage(self, index=0): """Show data image corresponding to index. Update slider to index. """ if not self._images: return assert index < len(self._images) bg_index = None if self.backgroundButton.isChecked(): for i, imageName in enumerate(self._labels): if imageName.lower().endswith('background'): bg_index = i break mf_text = "" a = self._medianParameters['row_width'] b = self._medianParameters['column_width'] if max(a, b) > 1: mf_text = "MF(%d,%d) " % (a, b) imdata = self._getMedianData(self._images[index]) if bg_index is None: self.plot.setGraphTitle(mf_text + self._labels[index]) else: self.plot.setGraphTitle(mf_text + self._labels[index] + " Net") imdata -= self._images[bg_index] self.plot.addImage(imdata, legend="current", origin=self._origin, scale=self._deltaXY, replace=False, z=0, info=self._infos[index]) self.plot.setActiveImage("current") self.slider.setValue(index) def _getMedianData(self, data): data = copy.copy(data) if max(self._medianParameters['row_width'], self._medianParameters['column_width']) > 1: data = medfilt2d(data, [self._medianParameters['row_width'], self._medianParameters['column_width']], conditional=self._medianParameters['conditional']) return data def setImages(self, images, labels=None, origin=None, height=None, width=None, infos=None): """Set the list of data images. All images share the same origin, width and height. :param images: List of 2D or 3D (for RGBA data) numpy arrays of image data. All images must have the same shape. :type images: List of ndarrays :param labels: list of image names :param origin: Image origin: coordinate (x, y) of sample located at (row, column) = (0, 0). If None, use (0., 0.) :param height: Image height in Y axis units. If None, use the image height in number of pixels. :param width: Image width in X axis units. If None, use the image width in number of pixels. :param infos: List of info dicts, one per image, or None. """ self._images = images if labels is None: labels = ["Image %d" % (i + 1) for i in range(len(images))] if infos is None: infos = [{} for _img in images] self._labels = labels self._infos = infos height_pixels, width_pixels = images[0].shape[0:2] height = height or height_pixels width = width or width_pixels self._deltaXY = (float(width) / width_pixels, float(height) / height_pixels) self._origin = origin or (0., 0.) current = self.slider.value() self.slider.setMaximum(len(self._images) - 1) if current < len(self._images): self.showImage(current) else: self.showImage(0) # _maskParamsCache = width, height, self._origin, self._deltaXY # if _maskParamsCache != self._maskParamsCache: # self._maskParamsCache = _maskParamsCache # self.resetMask(width, height, self._origin, self._deltaXY) def _updateBgScales(self, heights, widths): """Recalculate BG scales (e.g after a crop operation on :attr:`_bg_images`)""" self._bg_deltaXY = [] for w, h, img in zip(widths, heights, self._bg_images): self._bg_deltaXY.append( (float(w) / img.shape[1], float(h) / img.shape[0]) ) def setBackgroundImages(self, images, labels=None, origins=None, heights=None, widths=None): """Set the list of background images. Each image should be a tile and have an origin (x, y) tuple, a height and a width defined, so that all images can be plotted on the same background layer. :param images: List of 2D or 3D (for RGBA data) numpy arrays of image data. All images must have the same shape. :type images: List of ndarrays :param labels: list of image names :param origins: Images origins: list of coordinate tuples (x, y) of sample located at (row, column) = (0, 0). If None, use (0., 0.) for all images. :param heights: Image height in Y axis units. If None, use the image height in number of pixels. :param widths: Image width in X axis units. If None, use the image width in number of pixels. """ self._bg_images = images if labels is None: labels = ["Background image %d" % (i + 1) for i in range(len(images))] # delete existing images for label in self._bg_labels: self.plot.removeImage(label) self._bg_labels = labels if heights is None: heights = [image.shape[0] for image in images] else: assert len(heights) == len(images) if widths is None: widths = [image.shape[1] for image in images] else: assert len(widths) == len(images) if origins is None: origins = [(0, 0) for _img in images] else: assert len(origins) == len(images) self._bg_origins = origins self._updateBgScales(heights, widths) for bg_deltaXY, bg_orig, label, img in zip(self._bg_deltaXY, self._bg_origins, labels, images): # FIXME: we use z=-1 because the mask is always on z=1, # so the data must be on z=0. To be fixed after the silx mask # is improved self.plot.addImage(img, origin=bg_orig, scale=bg_deltaXY, legend=label, replace=False, z=-1) # TODO: z=0 def getCurrentIndex(self): """ :return: Index of slider widget used for image selection. """ return self.slider.value() def getCurrentColormap(self): """Return colormap dict associated with the current image. If the current image is a RGBA Image, return None. See doc of silx.gui.plot.Plot for an explanation about the colormap dictionary. """ image = self.plot.getImage(legend="current") if not hasattr(image, "getColormap"): # isinstance(image, silx.gui.plot.items.ImageRgba): return None return self.plot.getImage(legend="current").getColormap() def showAndRaise(self): self.show() self.raise_()
if maxValue == 1: startIndex = 1 colors = numpy.zeros((2, 4), dtype=numpy.uint8) colors[1, 3] = 255 else: raise ValueError("Different mask levels require color list input") oldShape = pixmap.shape pixmap.shape = -1, 4 maskView = mask[:] maskView.shape = -1, blendFactor = 0.8 for i in range(startIndex, maxValue + 1): idx = (maskView==i) pixmap[idx, :] = pixmap[idx, :] * blendFactor + \ colors[i] * (1.0 - blendFactor) pixmap.shape = oldShape return pixmap if __name__ == "__main__": from PyMca5.PyMcaGui import PyMcaQt as qt from silx.gui.plot import PlotWidget app = qt.QApplication([]) w = PlotWidget() data = numpy.arange(10000.).reshape(100, 100) mask = numpy.zeros(data.shape, dtype=numpy.uint8) mask[25:75, 25:75] = 1 image = getPixmapFromData(data, mask=mask) w.addImage(image, mask=mask) w.show() app.exec_()
class TestNamedImageAlphaSlider(TestCaseQt): def setUp(self): super(TestNamedImageAlphaSlider, self).setUp() self.plot = PlotWidget() self.aslider = AlphaSlider.NamedImageAlphaSlider(plot=self.plot) self.aslider.setOrientation(qt.Qt.Horizontal) toolbar = qt.QToolBar("plot", self.plot) toolbar.addWidget(self.aslider) self.plot.addToolBar(toolbar) self.plot.show() self.qWaitForWindowExposed(self.plot) self.mouseMove(self.plot) # Move to center self.qapp.processEvents() def tearDown(self): self.qapp.processEvents() self.plot.setAttribute(qt.Qt.WA_DeleteOnClose) self.plot.close() del self.plot del self.aslider super(TestNamedImageAlphaSlider, self).tearDown() def testWidgetEnabled(self): # no image set initially, slider must be deactivate self.assertFalse(self.aslider.isEnabled()) self.plot.addImage(numpy.array([[0, 1, 2], [3, 4, 5]]), legend="1") self.aslider.setLegend("1") # now we have an image set self.assertTrue(self.aslider.isEnabled()) def testGetImage(self): self.plot.addImage(numpy.array([[0, 1, 2], [3, 4, 5]]), legend="1") self.plot.addImage(numpy.array([[0, 1, 3], [2, 4, 6]]), legend="2") self.aslider.setLegend("1") self.assertEqual(self.plot.getImage("1"), self.aslider.getItem()) self.aslider.setLegend("2") self.assertEqual(self.plot.getImage("2"), self.aslider.getItem()) def testGetAlpha(self): self.plot.addImage(numpy.array([[0, 1, 2], [3, 4, 5]]), legend="1") self.aslider.setLegend("1") self.aslider.setValue(128) self.assertAlmostEqual(self.aslider.getAlpha(), 128. / 255)
def setGraphYLimits(self, *var, **kw): if "replot" in kw: del kw["replot"] return PlotWidget.setGraphYLimits(self, *var, **kw)
class MaskScatterWidget(qt.QMainWindow): """Simple plot widget designed to display a scatter plot on top of a background image. A transparency slider is provided to adjust the transparency of the scatter points. A mask tools widget is provided to select/mask points of the scatter plot. """ def __init__(self, parent=None): super(MaskScatterWidget, self).__init__(parent=parent) self._activeScatterLegend = "active scatter" self._bgImageLegend = "background image" # widgets centralWidget = qt.QWidget(self) self._plot = PlotWidget(parent=centralWidget) self._maskToolsWidget = ScatterMaskToolsWidget.ScatterMaskToolsWidget( plot=self._plot, parent=centralWidget) self._alphaSlider = NamedScatterAlphaSlider(parent=self, plot=self._plot) self._alphaSlider.setOrientation(qt.Qt.Horizontal) self._alphaSlider.setToolTip("Adjust scatter opacity") # layout layout = qt.QVBoxLayout(centralWidget) layout.addWidget(self._plot) layout.addWidget(self._alphaSlider) layout.addWidget(self._maskToolsWidget) centralWidget.setLayout(layout) self.setCentralWidget(centralWidget) def setSelectionMask(self, mask, copy=True): """Set the mask to a new array. :param numpy.ndarray mask: The array to use for the mask. Mask type: array of uint8 of dimension 1, Array of other types are converted. :param bool copy: True (the default) to copy the array, False to use it as is if possible. :return: None if failed, shape of mask as 1-tuple if successful. """ return self._maskToolsWidget.setSelectionMask(mask, copy=copy) def getSelectionMask(self, copy=True): """Get the current mask as a 1D array. :param bool copy: True (default) to get a copy of the mask. If False, the returned array MUST not be modified. :return: The array of the mask with dimension of the scatter data. If there is no scatter data, None is returned. :rtype: 1D numpy.ndarray of uint8 """ return self._maskToolsWidget.getSelectionMask(copy=copy) def setBackgroundImage(self, image, xscale=(0, 1.), yscale=(0, 1.), colormap=None): """Set a background image :param image: 2D image, array of shape (nrows, ncolumns) or (nrows, ncolumns, 3) or (nrows, ncolumns, 4) RGB(A) pixmap :param xscale: Factors for polynomial scaling for x-axis, *(a, b)* such as :math:`x \mapsto a + bx` :param yscale: Factors for polynomial scaling for y-axis """ self._plot.addImage(image, legend=self._bgImageLegend, origin=(xscale[0], yscale[0]), scale=(xscale[1], yscale[1]), z=0, colormap=colormap) def setScatter(self, x, y, v=None, info=None, colormap=None): """Set the scatter data, by providing its data as a 1D array or as a pixmap. The scatter plot set through this method is associated with the transparency slider. :param x: 1D array of x coordinates :param y: 1D array of y coordinates :param v: Array of values for each point, represented as the color of the point on the plot. """ self._plot.addScatter(x, y, v, legend=self._activeScatterLegend, info=info, colormap=colormap) # the mask is associated with the active scatter self._plot._setActiveItem(kind="scatter", legend=self._activeScatterLegend) self._alphaSlider.setLegend(self._activeScatterLegend)
def setActiveImage(self, *var, **kw): if "replot" in kw: del kw["replot"] return PlotWidget.setActiveImage(self, *var, **kw)
""" ob = Medfilt2DPlugin(plotWindow) return ob if __name__ == "__main__": # python -m PyMca5.PyMcaPlugins.Medfilt2DPlugin from PyMca5.PyMcaGui.PluginsToolButton import PluginsToolButton from PyMca5 import PyMcaPlugins import os from silx.test.utils import add_relative_noise from silx.gui.plot import PlotWidget # build a plot widget with a plugin toolbar button app = qt.QApplication([]) master_plot = PlotWidget() toolb = qt.QToolBar(master_plot) plugins_tb_2d = PluginsToolButton(plot=master_plot, parent=toolb, method="getPlugin2DInstance") plugins_tb_2d.getPlugins( method="getPlugin2DInstance", directoryList=[os.path.dirname(PyMcaPlugins.__file__)]) toolb.addWidget(plugins_tb_2d) master_plot.addToolBar(toolb) master_plot.show() # add a noisy image a, b = numpy.meshgrid(numpy.linspace(-10, 10, 500), numpy.linspace(-10, 5, 400), indexing="ij")
def __init__(self, parent=None): qt.QMainWindow.__init__(self, parent=parent) if parent is not None: # behave as a widget self.setWindowFlags(qt.Qt.Widget) else: self.setWindowTitle("PyMca - Image Selection Tool") centralWidget = qt.QWidget(self) layout = qt.QVBoxLayout(centralWidget) centralWidget.setLayout(layout) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) # Plot self.plot = PlotWidget(parent=centralWidget) self.plot.setWindowFlags(qt.Qt.Widget) self.plot.setDefaultColormap({ 'name': 'temperature', 'normalization': 'linear', 'autoscale': True, 'vmin': 0., 'vmax': 1. }) layout.addWidget(self.plot) # Mask Widget self._maskToolsDockWidget = None # Image selection slider self.slider = qt.QSlider(self.centralWidget()) self.slider.setOrientation(qt.Qt.Horizontal) self.slider.setMinimum(0) self.slider.setMaximum(0) layout.addWidget(self.slider) self.slider.valueChanged[int].connect(self.showImage) # ADD/REMOVE/REPLACE IMAGE buttons buttonBox = qt.QWidget(self) buttonBoxLayout = qt.QHBoxLayout(buttonBox) buttonBoxLayout.setContentsMargins(0, 0, 0, 0) buttonBoxLayout.setSpacing(0) self.addImageButton = qt.QPushButton(buttonBox) icon = qt.QIcon(qt.QPixmap(IconDict["rgb16"])) self.addImageButton.setIcon(icon) self.addImageButton.setText("ADD IMAGE") self.addImageButton.setToolTip("Add image to RGB correlator") buttonBoxLayout.addWidget(self.addImageButton) self.removeImageButton = qt.QPushButton(buttonBox) self.removeImageButton.setIcon(icon) self.removeImageButton.setText("REMOVE IMAGE") self.removeImageButton.setToolTip("Remove image from RGB correlator") buttonBoxLayout.addWidget(self.removeImageButton) self.replaceImageButton = qt.QPushButton(buttonBox) self.replaceImageButton.setIcon(icon) self.replaceImageButton.setText("REPLACE IMAGE") self.replaceImageButton.setToolTip( "Replace all images in RGB correlator with this one") buttonBoxLayout.addWidget(self.replaceImageButton) self.addImageButton.clicked.connect(self._addImageClicked) self.removeImageButton.clicked.connect(self._removeImageClicked) self.replaceImageButton.clicked.connect(self._replaceImageClicked) layout.addWidget(buttonBox) # median filter widget self._medianParameters = {'row_width': 1, 'column_width': 1} self._medianParametersWidget = MedianParameters(self) self._medianParametersWidget.widthSpin.setValue(1) self._medianParametersWidget.widthSpin.valueChanged[int].connect( self._setMedianKernelWidth) layout.addWidget(self._medianParametersWidget) self.setCentralWidget(centralWidget) # Init actions self.group = qt.QActionGroup(self) self.group.setExclusive(False) self.resetZoomAction = self.group.addAction( PlotActions.ResetZoomAction(plot=self.plot, parent=self)) self.addAction(self.resetZoomAction) self.zoomInAction = PlotActions.ZoomInAction(plot=self.plot, parent=self) self.addAction(self.zoomInAction) self.zoomOutAction = PlotActions.ZoomOutAction(plot=self.plot, parent=self) self.addAction(self.zoomOutAction) self.xAxisAutoScaleAction = self.group.addAction( PlotActions.XAxisAutoScaleAction(plot=self.plot, parent=self)) self.addAction(self.xAxisAutoScaleAction) self.yAxisAutoScaleAction = self.group.addAction( PlotActions.YAxisAutoScaleAction(plot=self.plot, parent=self)) self.addAction(self.yAxisAutoScaleAction) self.colormapAction = self.group.addAction( PlotActions.ColormapAction(plot=self.plot, parent=self)) self.addAction(self.colormapAction) self.copyAction = self.group.addAction( PlotActions.CopyAction(plot=self.plot, parent=self)) self.addAction(self.copyAction) self.group.addAction(self.getMaskAction()) # Init toolbuttons self.saveToolbutton = SaveToolButton(parent=self, maskImageWidget=self) self.yAxisInvertedButton = PlotToolButtons.YAxisOriginToolButton( parent=self, plot=self.plot) self.keepDataAspectRatioButton = PlotToolButtons.AspectToolButton( parent=self, plot=self.plot) self.backgroundButton = qt.QToolButton(self) self.backgroundButton.setCheckable(True) self.backgroundButton.setIcon( qt.QIcon(qt.QPixmap(IconDict["subtract"]))) self.backgroundButton.setToolTip( 'Toggle background image subtraction from current image\n' + 'No action if no background image available.') self.backgroundButton.clicked.connect(self._subtractBackground) # Creating the toolbar also create actions for toolbuttons self._toolbar = self._createToolBar(title='Plot', parent=None) self.addToolBar(self._toolbar) self._profile = ProfileToolBar(plot=self.plot) self.addToolBar(self._profile) self.setProfileToolbarVisible(False) # add a transparency slider for the stack data self._alphaSliderToolbar = qt.QToolBar("Alpha slider", parent=self) self._alphaSlider = NamedImageAlphaSlider( parent=self._alphaSliderToolbar, plot=self.plot, legend="current") self._alphaSlider.setOrientation(qt.Qt.Vertical) self._alphaSlider.setToolTip("Adjust opacity of stack image overlay") self._alphaSliderToolbar.addWidget(self._alphaSlider) self.addToolBar(qt.Qt.RightToolBarArea, self._alphaSliderToolbar) # hide optional tools and actions self.setAlphaSliderVisible(False) self.setBackgroundActionVisible(False) self.setMedianFilterWidgetVisible(False) self.setProfileToolbarVisible(False) self._images = [] """List of images, as 2D numpy arrays or 3D numpy arrays (RGB(A)). """ self._labels = [] """List of image labels. """ self._bg_images = [] """List of background images, as 2D numpy arrays or 3D numpy arrays (RGB(A)). These images are not active, their colormap cannot be changed and they cannot be the base image used for drawing a mask. """ self._bg_labels = [] self._deltaXY = ( 1.0, 1.0) # TODO: allow different scale and origin for each image """Current image scale (Xscale, Yscale) (in axis units per image pixel). The scale is adjusted to keep constant width and height for the image when a crop operation is applied.""" self._origin = (0., 0.) """Current image origin: coordinate (x, y) of sample located at (row, column) = (0, 0)""" # scales and origins for background images self._bg_deltaXY = [] self._bg_origins = []
def setUp(self): self.plot = PlotWidget()
class TestAxisSync(TestCaseQt): """Tests AxisSync class""" def setUp(self): TestCaseQt.setUp(self) self.plot1 = PlotWidget() self.plot2 = PlotWidget() self.plot3 = PlotWidget() def tearDown(self): self.plot1 = None self.plot2 = None self.plot3 = None TestCaseQt.tearDown(self) def testMoveFirstAxis(self): """Test synchronization after construction""" _sync = SyncAxes([self.plot1.getXAxis(), self.plot2.getXAxis(), 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 testMoveSecondAxis(self): """Test synchronization after construction""" _sync = SyncAxes([self.plot1.getXAxis(), self.plot2.getXAxis(), self.plot3.getXAxis()]) self.plot2.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 testMoveTwoAxes(self): """Test synchronization after construction""" _sync = SyncAxes([self.plot1.getXAxis(), self.plot2.getXAxis(), self.plot3.getXAxis()]) self.plot1.getXAxis().setLimits(1, 50) self.plot2.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 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 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 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 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 testDoubleStop(self): """Test double stop""" sync = SyncAxes([self.plot1.getXAxis(), self.plot2.getXAxis(), self.plot3.getXAxis()]) sync.stop() self.assertRaises(RuntimeError, sync.stop) def testDoubleStart(self): """Test double stop""" sync = SyncAxes([self.plot1.getXAxis(), self.plot2.getXAxis(), self.plot3.getXAxis()]) self.assertRaises(RuntimeError, sync.start) 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 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)
class TestSaveAction(unittest.TestCase): def setUp(self): self.plot = PlotWidget(backend='none') self.saveAction = SaveAction(plot=self.plot) self.tempdir = tempfile.mkdtemp() self.out_fname = os.path.join(self.tempdir, "out.dat") def tearDown(self): os.unlink(self.out_fname) os.rmdir(self.tempdir) def testSaveMultipleCurvesAsSpec(self): """Test that labels are properly used.""" self.plot.setGraphXLabel("graph x label") self.plot.setGraphYLabel("graph y label") self.plot.addCurve([0, 1], [1, 2], "curve with labels", xlabel="curve0 X", ylabel="curve0 Y") self.plot.addCurve([-1, 3], [-6, 2], "curve with X label", xlabel="curve1 X") self.plot.addCurve([-2, 0], [8, 12], "curve with Y label", ylabel="curve2 Y") self.plot.addCurve([3, 1], [7, 6], "curve with no labels") self.saveAction._saveCurves(self.out_fname, SaveAction.ALL_CURVES_FILTERS[0] ) # "All curves as SpecFile (*.dat)" with open(self.out_fname, "rb") as f: file_content = f.read() if hasattr(file_content, "decode"): file_content = file_content.decode() # case with all curve labels specified self.assertIn("#S 1 curve0 Y", file_content) self.assertIn("#L curve0 X curve0 Y", file_content) # graph X&Y labels are used when no curve label is specified self.assertIn("#S 2 graph y label", file_content) self.assertIn("#L curve1 X graph y label", file_content) self.assertIn("#S 3 curve2 Y", file_content) self.assertIn("#L graph x label curve2 Y", file_content) self.assertIn("#S 4 graph y label", file_content) self.assertIn("#L graph x label graph y label", file_content)
def hideCurve(self, *var, **kw): if "replot" in kw: del kw["replot"] return PlotWidget.hideCurve(self, *var, **kw)
def setActiveCurveColor(self, *var, **kw): return PlotWidget.setActiveCurveStyle(self, *var, **kw)
class ColormapDialog(qt.QDialog): sigColormapChanged = qt.pyqtSignal(object) def __init__(self, parent=None, name="Colormap Dialog"): qt.QDialog.__init__(self, parent) self.setWindowTitle(name) self.title = name self.colormapList = ["Greyscale", "Reverse Grey", "Temperature", "Red", "Green", "Blue", "Many"] # histogramData is tupel(bins, counts) self.histogramData = None # default values self.dataMin = -10 self.dataMax = 10 self.minValue = 0 self.maxValue = 1 self.colormapIndex = 2 self.colormapType = 0 self.autoscale = False self.autoscale90 = False # main layout vlayout = qt.QVBoxLayout(self) vlayout.setContentsMargins(10, 10, 10, 10) vlayout.setSpacing(0) # layout 1 : -combo to choose colormap # -autoscale button # -autoscale 90% button hbox1 = qt.QWidget(self) hlayout1 = qt.QHBoxLayout(hbox1) vlayout.addWidget(hbox1) hlayout1.setContentsMargins(0, 0, 0, 0) hlayout1.setSpacing(10) # combo self.combo = qt.QComboBox(hbox1) for colormap in self.colormapList: self.combo.addItem(colormap) self.combo.activated[int].connect(self.colormapChange) hlayout1.addWidget(self.combo) # autoscale self.autoScaleButton = qt.QPushButton("Autoscale", hbox1) self.autoScaleButton.setCheckable(True) self.autoScaleButton.setAutoDefault(False) self.autoScaleButton.toggled[bool].connect(self.autoscaleChange) hlayout1.addWidget(self.autoScaleButton) # autoscale 90% self.autoScale90Button = qt.QPushButton("Autoscale 90%", hbox1) self.autoScale90Button.setCheckable(True) self.autoScale90Button.setAutoDefault(False) self.autoScale90Button.toggled[bool].connect(self.autoscale90Change) hlayout1.addWidget(self.autoScale90Button) # hlayout hbox0 = qt.QWidget(self) self.__hbox0 = hbox0 hlayout0 = qt.QHBoxLayout(hbox0) hlayout0.setContentsMargins(0, 0, 0, 0) hlayout0.setSpacing(0) vlayout.addWidget(hbox0) #hlayout0.addStretch(10) self.buttonGroup = qt.QButtonGroup() g1 = qt.QCheckBox(hbox0) g1.setText("Linear") g2 = qt.QCheckBox(hbox0) g2.setText("Logarithmic") g3 = qt.QCheckBox(hbox0) g3.setText("Gamma") self.buttonGroup.addButton(g1, 0) self.buttonGroup.addButton(g2, 1) self.buttonGroup.addButton(g3, 2) self.buttonGroup.setExclusive(True) if self.colormapType == 1: self.buttonGroup.button(1).setChecked(True) elif self.colormapType == 2: self.buttonGroup.button(2).setChecked(True) else: self.buttonGroup.button(0).setChecked(True) hlayout0.addWidget(g1) hlayout0.addWidget(g2) hlayout0.addWidget(g3) vlayout.addWidget(hbox0) self.buttonGroup.buttonClicked[int].connect(self.buttonGroupChange) vlayout.addSpacing(20) hboxlimits = qt.QWidget(self) hboxlimitslayout = qt.QHBoxLayout(hboxlimits) hboxlimitslayout.setContentsMargins(0, 0, 0, 0) hboxlimitslayout.setSpacing(0) self.slider = None vlayout.addWidget(hboxlimits) vboxlimits = qt.QWidget(hboxlimits) vboxlimitslayout = qt.QVBoxLayout(vboxlimits) vboxlimitslayout.setContentsMargins(0, 0, 0, 0) vboxlimitslayout.setSpacing(0) hboxlimitslayout.addWidget(vboxlimits) # hlayout 2 : - min label # - min texte hbox2 = qt.QWidget(vboxlimits) self.__hbox2 = hbox2 hlayout2 = qt.QHBoxLayout(hbox2) hlayout2.setContentsMargins(0, 0, 0, 0) hlayout2.setSpacing(0) #vlayout.addWidget(hbox2) vboxlimitslayout.addWidget(hbox2) hlayout2.addStretch(10) self.minLabel = qt.QLabel(hbox2) self.minLabel.setText("Minimum") hlayout2.addWidget(self.minLabel) hlayout2.addSpacing(5) hlayout2.addStretch(1) self.minText = MyQLineEdit(hbox2) self.minText.setFixedWidth(150) self.minText.setAlignment(qt.Qt.AlignRight) self.minText.returnPressed[()].connect(self.minTextChanged) hlayout2.addWidget(self.minText) # hlayout 3 : - min label # - min text hbox3 = qt.QWidget(vboxlimits) self.__hbox3 = hbox3 hlayout3 = qt.QHBoxLayout(hbox3) hlayout3.setContentsMargins(0, 0, 0, 0) hlayout3.setSpacing(0) #vlayout.addWidget(hbox3) vboxlimitslayout.addWidget(hbox3) hlayout3.addStretch(10) self.maxLabel = qt.QLabel(hbox3) self.maxLabel.setText("Maximum") hlayout3.addWidget(self.maxLabel) hlayout3.addSpacing(5) hlayout3.addStretch(1) self.maxText = MyQLineEdit(hbox3) self.maxText.setFixedWidth(150) self.maxText.setAlignment(qt.Qt.AlignRight) self.maxText.returnPressed[()].connect(self.maxTextChanged) hlayout3.addWidget(self.maxText) # Graph widget for color curve... self.c = PlotWidget(self, backend=None) self.c.setGraphXLabel("Data Values") self.c.setInteractiveMode('select') self.marge = (abs(self.dataMax) + abs(self.dataMin)) / 6.0 self.minmd = self.dataMin - self.marge self.maxpd = self.dataMax + self.marge self.c.setGraphXLimits(self.minmd, self.maxpd) self.c.setGraphYLimits(-11.5, 11.5) x = [self.minmd, self.dataMin, self.dataMax, self.maxpd] y = [-10, -10, 10, 10 ] self.c.addCurve(x, y, legend="ConstrainedCurve", color='black', symbol='o', linestyle='-') self.markers = [] self.__x = x self.__y = y labelList = ["","Min", "Max", ""] for i in range(4): if i in [1, 2]: draggable = True color = "blue" else: draggable = False color = "black" #TODO symbol legend = "%d" % i self.c.addXMarker(x[i], legend=legend, text=labelList[i], draggable=draggable, color=color) self.markers.append((legend, "")) self.c.setMinimumSize(qt.QSize(250,200)) vlayout.addWidget(self.c) self.c.sigPlotSignal.connect(self.chval) # colormap window can not be resized self.setFixedSize(vlayout.minimumSize()) def plotHistogram(self, data=None): if data is not None: self.histogramData = data if self.histogramData is None: return False bins, counts = self.histogramData self.c.addCurve(bins, counts, "Histogram", color='darkYellow', histogram='center', yaxis='right', fill=True) def _update(self): _logger.debug("colormap _update called") self.marge = (abs(self.dataMax) + abs(self.dataMin)) / 6.0 self.minmd = self.dataMin - self.marge self.maxpd = self.dataMax + self.marge self.c.setGraphXLimits(self.minmd, self.maxpd) self.c.setGraphYLimits( -11.5, 11.5) self.__x = [self.minmd, self.dataMin, self.dataMax, self.maxpd] self.__y = [-10, -10, 10, 10] self.c.addCurve(self.__x, self.__y, legend="ConstrainedCurve", color='black', symbol='o', linestyle='-') self.c.clearMarkers() for i in range(4): if i in [1, 2]: draggable = True color = "blue" else: draggable = False color = "black" key = self.markers[i][0] label = self.markers[i][1] self.c.addXMarker(self.__x[i], legend=key, text=label, draggable=draggable, color=color) self.sendColormap() def buttonGroupChange(self, val): _logger.debug("buttonGroup asking to update colormap") self.setColormapType(val, update=True) self._update() def setColormapType(self, val, update=False): self.colormapType = val if self.colormapType == 1: self.buttonGroup.button(1).setChecked(True) elif self.colormapType == 2: self.buttonGroup.button(2).setChecked(True) else: self.colormapType = 0 self.buttonGroup.button(0).setChecked(True) if update: self._update() def chval(self, ddict): _logger.debug("Received %s", ddict) if ddict['event'] == 'markerMoving': diam = int(ddict['label']) x = ddict['x'] if diam == 1: self.setDisplayedMinValue(x) elif diam == 2: self.setDisplayedMaxValue(x) elif ddict['event'] == 'markerMoved': diam = int(ddict['label']) x = ddict['x'] if diam == 1: self.setMinValue(x) if diam == 2: self.setMaxValue(x) """ Colormap """ def setColormap(self, colormap): self.colormapIndex = colormap if QTVERSION < '4.0.0': self.combo.setCurrentItem(colormap) else: self.combo.setCurrentIndex(colormap) def colormapChange(self, colormap): self.colormapIndex = colormap self.sendColormap() # AUTOSCALE """ Autoscale """ def autoscaleChange(self, val): self.autoscale = val self.setAutoscale(val) self.sendColormap() def setAutoscale(self, val): _logger.debug("setAutoscale called %s", val) if val: self.autoScaleButton.setChecked(True) self.autoScale90Button.setChecked(False) #self.autoScale90Button.setDown(False) self.setMinValue(self.dataMin) self.setMaxValue(self.dataMax) self.maxText.setEnabled(0) self.minText.setEnabled(0) self.c.setEnabled(False) #self.c.disablemarkermode() else: self.autoScaleButton.setChecked(False) self.autoScale90Button.setChecked(False) self.minText.setEnabled(1) self.maxText.setEnabled(1) self.c.setEnabled(True) #self.c.enablemarkermode() """ set rangeValues to dataMin ; dataMax-10% """ def autoscale90Change(self, val): self.autoscale90 = val self.setAutoscale90(val) self.sendColormap() def setAutoscale90(self, val): if val: self.autoScaleButton.setChecked(False) self.setMinValue(self.dataMin) self.setMaxValue(self.dataMax - abs(self.dataMax/10)) self.minText.setEnabled(0) self.maxText.setEnabled(0) self.c.setEnabled(False) else: self.autoScale90Button.setChecked(False) self.minText.setEnabled(1) self.maxText.setEnabled(1) self.c.setEnabled(True) self.c.setFocus() # MINIMUM """ change min value and update colormap """ def setMinValue(self, val): v = float(str(val)) self.minValue = v self.minText.setText("%g" % v) self.__x[1] = v key = self.markers[1][0] label = self.markers[1][1] self.c.addXMarker(v, legend=key, text=label, color="blue", draggable=True) self.c.addCurve(self.__x, self.__y, legend="ConstrainedCurve", color='black', symbol='o', linestyle='-') self.sendColormap() """ min value changed by text """ def minTextChanged(self): text = str(self.minText.text()) if not len(text): return val = float(text) self.setMinValue(val) if self.minText.hasFocus(): self.c.setFocus() """ change only the displayed min value """ def setDisplayedMinValue(self, val): val = float(val) self.minValue = val self.minText.setText("%g"%val) self.__x[1] = val key = self.markers[1][0] label = self.markers[1][1] self.c.addXMarker(val, legend=key, text=label, color="blue", draggable=True) self.c.addCurve(self.__x, self.__y, legend="ConstrainedCurve", color='black', symbol='o', linestyle='-') # MAXIMUM """ change max value and update colormap """ def setMaxValue(self, val): v = float(str(val)) self.maxValue = v self.maxText.setText("%g"%v) self.__x[2] = v key = self.markers[2][0] label = self.markers[2][1] self.c.addXMarker(v, legend=key, text=label, color="blue", draggable=True) self.c.addCurve(self.__x, self.__y, legend="ConstrainedCurve", color='black', symbol='o', linestyle='-') self.sendColormap() """ max value changed by text """ def maxTextChanged(self): text = str(self.maxText.text()) if not len(text):return val = float(text) self.setMaxValue(val) if self.maxText.hasFocus(): self.c.setFocus() """ change only the displayed max value """ def setDisplayedMaxValue(self, val): val = float(val) self.maxValue = val self.maxText.setText("%g"%val) self.__x[2] = val key = self.markers[2][0] label = self.markers[2][1] self.c.addXMarker(val, legend=key, text=label, color="blue", draggable=True) self.c.addCurve(self.__x, self.__y, legend="ConstrainedCurve", color='black', symbol='o', linestyle='-') # DATA values """ set min/max value of data source """ def setDataMinMax(self, minVal, maxVal, update=True): if minVal is not None: vmin = float(str(minVal)) self.dataMin = vmin if maxVal is not None: vmax = float(str(maxVal)) self.dataMax = vmax if update: # are current values in the good range ? self._update() def getColormap(self): if self.minValue > self.maxValue: vmax = self.minValue vmin = self.maxValue else: vmax = self.maxValue vmin = self.minValue cmap = [self.colormapIndex, self.autoscale, vmin, vmax, self.dataMin, self.dataMax, self.colormapType] return cmap """ send 'ColormapChanged' signal """ def sendColormap(self): _logger.debug("sending colormap") try: cmap = self.getColormap() self.sigColormapChanged.emit(cmap) except: sys.excepthook(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2])
class SilxMaskImageWidget(qt.QMainWindow): """Main window with a plot widget, a toolbar and a slider. A list of images can be set with :meth:`setImages`. The mask can be accessed through getter and setter methods: :meth:`setSelectionMask` and :meth:`getSelectionMask`. The plot widget can be accessed as :attr:`plot`. It is a silx plot widget. The toolbar offers some basic interaction tools: zoom control, colormap, aspect ratio, y axis orientation, "save image" menu and a mask widget. """ sigMaskImageWidget = qt.pyqtSignal(object) def __init__(self, parent=None): qt.QMainWindow.__init__(self, parent=parent) if parent is not None: # behave as a widget self.setWindowFlags(qt.Qt.Widget) else: self.setWindowTitle("PyMca - Image Selection Tool") centralWidget = qt.QWidget(self) layout = qt.QVBoxLayout(centralWidget) centralWidget.setLayout(layout) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) # Plot self.plot = PlotWidget(parent=centralWidget) self.plot.setWindowFlags(qt.Qt.Widget) self.plot.setDefaultColormap({ 'name': 'temperature', 'normalization': 'linear', 'autoscale': True, 'vmin': 0., 'vmax': 1. }) layout.addWidget(self.plot) # Mask Widget self._maskToolsDockWidget = None # Image selection slider self.slider = qt.QSlider(self.centralWidget()) self.slider.setOrientation(qt.Qt.Horizontal) self.slider.setMinimum(0) self.slider.setMaximum(0) layout.addWidget(self.slider) self.slider.valueChanged[int].connect(self.showImage) # ADD/REMOVE/REPLACE IMAGE buttons buttonBox = qt.QWidget(self) buttonBoxLayout = qt.QHBoxLayout(buttonBox) buttonBoxLayout.setContentsMargins(0, 0, 0, 0) buttonBoxLayout.setSpacing(0) self.addImageButton = qt.QPushButton(buttonBox) icon = qt.QIcon(qt.QPixmap(IconDict["rgb16"])) self.addImageButton.setIcon(icon) self.addImageButton.setText("ADD IMAGE") self.addImageButton.setToolTip("Add image to RGB correlator") buttonBoxLayout.addWidget(self.addImageButton) self.removeImageButton = qt.QPushButton(buttonBox) self.removeImageButton.setIcon(icon) self.removeImageButton.setText("REMOVE IMAGE") self.removeImageButton.setToolTip("Remove image from RGB correlator") buttonBoxLayout.addWidget(self.removeImageButton) self.replaceImageButton = qt.QPushButton(buttonBox) self.replaceImageButton.setIcon(icon) self.replaceImageButton.setText("REPLACE IMAGE") self.replaceImageButton.setToolTip( "Replace all images in RGB correlator with this one") buttonBoxLayout.addWidget(self.replaceImageButton) self.addImageButton.clicked.connect(self._addImageClicked) self.removeImageButton.clicked.connect(self._removeImageClicked) self.replaceImageButton.clicked.connect(self._replaceImageClicked) layout.addWidget(buttonBox) # median filter widget self._medianParameters = {'row_width': 1, 'column_width': 1} self._medianParametersWidget = MedianParameters(self) self._medianParametersWidget.widthSpin.setValue(1) self._medianParametersWidget.widthSpin.valueChanged[int].connect( self._setMedianKernelWidth) layout.addWidget(self._medianParametersWidget) self.setCentralWidget(centralWidget) # Init actions self.group = qt.QActionGroup(self) self.group.setExclusive(False) self.resetZoomAction = self.group.addAction( PlotActions.ResetZoomAction(plot=self.plot, parent=self)) self.addAction(self.resetZoomAction) self.zoomInAction = PlotActions.ZoomInAction(plot=self.plot, parent=self) self.addAction(self.zoomInAction) self.zoomOutAction = PlotActions.ZoomOutAction(plot=self.plot, parent=self) self.addAction(self.zoomOutAction) self.xAxisAutoScaleAction = self.group.addAction( PlotActions.XAxisAutoScaleAction(plot=self.plot, parent=self)) self.addAction(self.xAxisAutoScaleAction) self.yAxisAutoScaleAction = self.group.addAction( PlotActions.YAxisAutoScaleAction(plot=self.plot, parent=self)) self.addAction(self.yAxisAutoScaleAction) self.colormapAction = self.group.addAction( PlotActions.ColormapAction(plot=self.plot, parent=self)) self.addAction(self.colormapAction) self.copyAction = self.group.addAction( PlotActions.CopyAction(plot=self.plot, parent=self)) self.addAction(self.copyAction) self.group.addAction(self.getMaskAction()) # Init toolbuttons self.saveToolbutton = SaveToolButton(parent=self, maskImageWidget=self) self.yAxisInvertedButton = PlotToolButtons.YAxisOriginToolButton( parent=self, plot=self.plot) self.keepDataAspectRatioButton = PlotToolButtons.AspectToolButton( parent=self, plot=self.plot) self.backgroundButton = qt.QToolButton(self) self.backgroundButton.setCheckable(True) self.backgroundButton.setIcon( qt.QIcon(qt.QPixmap(IconDict["subtract"]))) self.backgroundButton.setToolTip( 'Toggle background image subtraction from current image\n' + 'No action if no background image available.') self.backgroundButton.clicked.connect(self._subtractBackground) # Creating the toolbar also create actions for toolbuttons self._toolbar = self._createToolBar(title='Plot', parent=None) self.addToolBar(self._toolbar) self._profile = ProfileToolBar(plot=self.plot) self.addToolBar(self._profile) self.setProfileToolbarVisible(False) # add a transparency slider for the stack data self._alphaSliderToolbar = qt.QToolBar("Alpha slider", parent=self) self._alphaSlider = NamedImageAlphaSlider( parent=self._alphaSliderToolbar, plot=self.plot, legend="current") self._alphaSlider.setOrientation(qt.Qt.Vertical) self._alphaSlider.setToolTip("Adjust opacity of stack image overlay") self._alphaSliderToolbar.addWidget(self._alphaSlider) self.addToolBar(qt.Qt.RightToolBarArea, self._alphaSliderToolbar) # hide optional tools and actions self.setAlphaSliderVisible(False) self.setBackgroundActionVisible(False) self.setMedianFilterWidgetVisible(False) self.setProfileToolbarVisible(False) self._images = [] """List of images, as 2D numpy arrays or 3D numpy arrays (RGB(A)). """ self._labels = [] """List of image labels. """ self._bg_images = [] """List of background images, as 2D numpy arrays or 3D numpy arrays (RGB(A)). These images are not active, their colormap cannot be changed and they cannot be the base image used for drawing a mask. """ self._bg_labels = [] self._deltaXY = ( 1.0, 1.0) # TODO: allow different scale and origin for each image """Current image scale (Xscale, Yscale) (in axis units per image pixel). The scale is adjusted to keep constant width and height for the image when a crop operation is applied.""" self._origin = (0., 0.) """Current image origin: coordinate (x, y) of sample located at (row, column) = (0, 0)""" # scales and origins for background images self._bg_deltaXY = [] self._bg_origins = [] def sizeHint(self): return qt.QSize(500, 400) def _createToolBar(self, title, parent): """Create a QToolBar with crop, rotate and flip operations :param str title: The title of the QMenu :param qt.QWidget parent: See :class:`QToolBar` """ toolbar = qt.QToolBar(title, parent) # Order widgets with actions objects = self.group.actions() # Add standard push buttons to list index = objects.index(self.colormapAction) objects.insert(index + 1, self.keepDataAspectRatioButton) objects.insert(index + 2, self.yAxisInvertedButton) objects.insert(index + 3, self.saveToolbutton) objects.insert(index + 4, self.backgroundButton) for obj in objects: if isinstance(obj, qt.QAction): toolbar.addAction(obj) else: # keep reference to toolbutton's action for changing visibility if obj is self.keepDataAspectRatioButton: self.keepDataAspectRatioAction = toolbar.addWidget(obj) elif obj is self.yAxisInvertedButton: self.yAxisInvertedAction = toolbar.addWidget(obj) elif obj is self.saveToolbutton: self.saveAction = toolbar.addWidget(obj) elif obj is self.backgroundButton: self.bgAction = toolbar.addWidget(obj) else: raise RuntimeError() return toolbar def _getMaskToolsDockWidget(self): """DockWidget with image mask panel (lazy-loaded).""" if self._maskToolsDockWidget is None: self._maskToolsDockWidget = MyMaskToolsDockWidget(plot=self.plot, name='Mask') self._maskToolsDockWidget.hide() self.addDockWidget(qt.Qt.RightDockWidgetArea, self._maskToolsDockWidget) # self._maskToolsDockWidget.setFloating(True) self._maskToolsDockWidget.sigMaskChanged.connect( self._emitMaskImageWidgetSignal) return self._maskToolsDockWidget def _setMedianKernelWidth(self, value): kernelSize = numpy.asarray(value) if len(kernelSize.shape) == 0: kernelSize = [kernelSize.item()] * 2 self._medianParameters['row_width'] = kernelSize[0] self._medianParameters['column_width'] = kernelSize[1] self._medianParametersWidget.widthSpin.setValue(int(kernelSize[0])) current = self.slider.value() self.showImage(current) def _subtractBackground(self): """When background button is clicked, this causes showImage to display the data after subtracting the stack background image. This background image is unrelated to the background images set with :meth:`setBackgroundImages`, it is simply the first data image whose label ends with 'background'.""" current = self.getCurrentIndex() self.showImage(current) def setBackgroundActionVisible(self, visible): """Set visibility of the background toolbar button. :param visible: True to show tool button, False to hide it. """ self.bgAction.setVisible(visible) def setProfileToolbarVisible(self, visible): """Set visibility of the profile toolbar :param visible: True to show toolbar, False to hide it. """ self._profile.setVisible(visible) def setMedianFilterWidgetVisible(self, visible): """Set visibility of the median filter parametrs widget. :param visible: True to show widget, False to hide it. """ self._medianParametersWidget.setVisible(visible) def setAlphaSliderVisible(self, visible): """Set visibility of the transparency slider widget in the right toolbar area. :param visible: True to show widget, False to hide it. """ self._alphaSliderToolbar.setVisible(visible) def setImagesAlpha(self, alpha): """Set the opacity of the images layer. Full opacity means that the background images layer will not be visible. :param float alpha: Opacity of images layer, in [0., 1.] """ self._alphaSlider.setValue(round(alpha * 255)) def getMaskAction(self): """QAction toggling image mask dock widget :rtype: QAction """ return self._getMaskToolsDockWidget().toggleViewAction() def _emitMaskImageWidgetSignal(self): mask = self.getSelectionMask() if not mask.size: # workaround to ignore the empty mask emitted when the mask widget # is initialized return self.sigMaskImageWidget.emit({ "event": "selectionMaskChanged", "current": self.getSelectionMask(), "id": id(self) }) def setSelectionMask(self, mask, copy=True): """Set the mask to a new array. :param numpy.ndarray mask: The array to use for the mask. Mask type: array of uint8 of dimension 2, Array of other types are converted. :param bool copy: True (the default) to copy the array, False to use it as is if possible. :return: None if failed, shape of mask as 2-tuple if successful. The mask can be cropped or padded to fit active image, the returned shape is that of the active image. """ if mask is None: mask = numpy.zeros_like( self._getMaskToolsDockWidget().getSelectionMask()) if not len(mask): return # disconnect temporarily to avoid infinite loop self._getMaskToolsDockWidget().sigMaskChanged.disconnect( self._emitMaskImageWidgetSignal) ret = self._getMaskToolsDockWidget().setSelectionMask(mask, copy=copy) self._getMaskToolsDockWidget().sigMaskChanged.connect( self._emitMaskImageWidgetSignal) return ret def getSelectionMask(self, copy=True): """Get the current mask as a 2D array. :param bool copy: True (default) to get a copy of the mask. If False, the returned array MUST not be modified. :return: The array of the mask with dimension of the 'active' image. If there is no active image, an empty array is returned. :rtype: 2D numpy.ndarray of uint8 """ return self._getMaskToolsDockWidget().getSelectionMask(copy=copy) @staticmethod def _RgbaToGrayscale(image): """Convert RGBA image to 2D array of grayscale values (Luma coding) :param image: RGBA image, as a numpy array of shapes (nrows, ncols, 3/4) :return: Image as a 2D array """ if len(image.shape) == 2: return image assert len(image.shape) == 3 imageData = image[:, :, 0] * 0.299 +\ image[:, :, 1] * 0.587 +\ image[:, :, 2] * 0.114 return imageData def getImageData(self): """Return current image data to be sent to RGB correlator :return: Image as a 2D array """ index = self.slider.value() image = self._images[index] return self._RgbaToGrayscale(image) def getFirstBgImageData(self): """Return first bg image data to be sent to RGB correlator :return: Image as a 2D array """ image = self._bg_images[0] return self._RgbaToGrayscale(image) def _addImageClicked(self): imageData = self.getImageData() ddict = { 'event': "addImageClicked", 'image': imageData, 'title': self.plot.getGraphTitle(), 'id': id(self) } self.sigMaskImageWidget.emit(ddict) def _replaceImageClicked(self): imageData = self.getImageData() ddict = { 'event': "replaceImageClicked", 'image': imageData, 'title': self.plot.getGraphTitle(), 'id': id(self) } self.sigMaskImageWidget.emit(ddict) def _removeImageClicked(self): imageData = self.getImageData() ddict = { 'event': "removeImageClicked", 'image': imageData, 'title': self.plot.getGraphTitle(), 'id': id(self) } self.sigMaskImageWidget.emit(ddict) def showImage(self, index=0): """Show data image corresponding to index. Update slider to index. """ if not self._images: return assert index < len(self._images) bg_index = None if self.backgroundButton.isChecked(): for i, imageName in enumerate(self._labels): if imageName.lower().endswith('background'): bg_index = i break mf_text = "" a = self._medianParameters['row_width'] b = self._medianParameters['column_width'] if max(a, b) > 1: mf_text = "MF(%d,%d) " % (a, b) imdata = self._getMedianData(self._images[index]) if bg_index is None: self.plot.setGraphTitle(mf_text + self._labels[index]) else: self.plot.setGraphTitle(mf_text + self._labels[index] + " Net") imdata -= self._images[bg_index] self.plot.addImage(imdata, legend="current", origin=self._origin, scale=self._deltaXY, replace=False, z=0) self.plot.setActiveImage("current") self.slider.setValue(index) def _getMedianData(self, data): data = copy.copy(data) if max(self._medianParameters['row_width'], self._medianParameters['column_width']) > 1: data = medfilt2d(data, [ self._medianParameters['row_width'], self._medianParameters['column_width'] ]) return data def setImages(self, images, labels=None, origin=None, height=None, width=None): """Set the list of data images. All images share the same origin, width and height. :param images: List of 2D or 3D (for RGBA data) numpy arrays of image data. All images must have the same shape. :type images: List of ndarrays :param labels: list of image names :param origin: Image origin: coordinate (x, y) of sample located at (row, column) = (0, 0). If None, use (0., 0.) :param height: Image height in Y axis units. If None, use the image height in number of pixels. :param width: Image width in X axis units. If None, use the image width in number of pixels. """ self._images = images if labels is None: labels = ["Image %d" % (i + 1) for i in range(len(images))] self._labels = labels height_pixels, width_pixels = images[0].shape[0:2] height = height or height_pixels width = width or width_pixels self._deltaXY = (float(width) / width_pixels, float(height) / height_pixels) self._origin = origin or (0., 0.) current = self.slider.value() self.slider.setMaximum(len(self._images) - 1) if current < len(self._images): self.showImage(current) else: self.showImage(0) # _maskParamsCache = width, height, self._origin, self._deltaXY # if _maskParamsCache != self._maskParamsCache: # self._maskParamsCache = _maskParamsCache # self.resetMask(width, height, self._origin, self._deltaXY) def _updateBgScales(self, heights, widths): """Recalculate BG scales (e.g after a crop operation on :attr:`_bg_images`)""" self._bg_deltaXY = [] for w, h, img in zip(widths, heights, self._bg_images): self._bg_deltaXY.append( (float(w) / img.shape[1], float(h) / img.shape[0])) def setBackgroundImages(self, images, labels=None, origins=None, heights=None, widths=None): """Set the list of background images. Each image should be a tile and have an origin (x, y) tuple, a height and a width defined, so that all images can be plotted on the same background layer. :param images: List of 2D or 3D (for RGBA data) numpy arrays of image data. All images must have the same shape. :type images: List of ndarrays :param labels: list of image names :param origins: Images origins: list of coordinate tuples (x, y) of sample located at (row, column) = (0, 0). If None, use (0., 0.) for all images. :param height: Image height in Y axis units. If None, use the image height in number of pixels. :param width: Image width in X axis units. If None, use the image width in number of pixels. """ self._bg_images = images if labels is None: labels = [ "Background image %d" % (i + 1) for i in range(len(images)) ] # delete existing images for label in self._bg_labels: self.plot.removeImage(label) self._bg_labels = labels if heights is None: heights = [image.shape[0] for image in images] else: assert len(heights) == len(images) if widths is None: widths = [image.shape[1] for image in images] else: assert len(widths) == len(images) if origins is None: origins = [(0, 0) for _img in images] else: assert len(origins) == len(images) self._bg_origins = origins self._updateBgScales(heights, widths) for bg_deltaXY, bg_orig, label, img in zip(self._bg_deltaXY, self._bg_origins, labels, images): # FIXME: we use z=-1 because the mask is always on z=1, # so the data must be on z=0. To be fixed after the silx mask # is improved self.plot.addImage(img, origin=bg_orig, scale=bg_deltaXY, legend=label, replace=False, z=-1) # TODO: z=0 def getCurrentIndex(self): """ :return: Index of slider widget used for image selection. """ return self.slider.value() def getCurrentColormap(self): """Return colormap dict associated with the current image. If the current image is a RGBA Image, return None. See doc of silx.gui.plot.Plot for an explanation about the colormap dictionary. """ image = self.plot.getImage(legend="current") if not hasattr(image, "getColormap" ): # isinstance(image, silx.gui.plot.items.ImageRgba): return None return self.plot.getImage(legend="current").getColormap() def showAndRaise(self): self.show() self.raise_()
class TestAxisSync(TestCaseQt): """Tests AxisSync class""" def setUp(self): TestCaseQt.setUp(self) self.plot1 = PlotWidget() self.plot2 = PlotWidget() self.plot3 = PlotWidget() def tearDown(self): self.plot1 = None self.plot2 = None self.plot3 = None TestCaseQt.tearDown(self) def testMoveFirstAxis(self): """Test synchronization after construction""" _sync = SyncAxes([ self.plot1.getXAxis(), self.plot2.getXAxis(), 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 testMoveSecondAxis(self): """Test synchronization after construction""" _sync = SyncAxes([ self.plot1.getXAxis(), self.plot2.getXAxis(), self.plot3.getXAxis() ]) self.plot2.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 testMoveTwoAxes(self): """Test synchronization after construction""" _sync = SyncAxes([ self.plot1.getXAxis(), self.plot2.getXAxis(), self.plot3.getXAxis() ]) self.plot1.getXAxis().setLimits(1, 50) self.plot2.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 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 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 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 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 testDoubleStop(self): """Test double stop""" sync = SyncAxes([ self.plot1.getXAxis(), self.plot2.getXAxis(), self.plot3.getXAxis() ]) sync.stop() self.assertRaises(RuntimeError, sync.stop) def testDoubleStart(self): """Test double stop""" sync = SyncAxes([ self.plot1.getXAxis(), self.plot2.getXAxis(), self.plot3.getXAxis() ]) self.assertRaises(RuntimeError, sync.start) 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 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 createWidget(self, parent): plot = PlotWidget(parent) plot.setAutoReplot(False) return plot
def main(argv): """ Main function to launch the viewer as an application :param argv: Command line arguments :returns: exit status """ parser = argparse.ArgumentParser(description=__doc__) parser.add_argument( 'files', nargs=argparse.ZERO_OR_MORE, help='Data file to show (h5 file, edf files, spec files)') parser.add_argument( '--debug', dest="debug", action="store_true", default=False, help='Set logging system in debug mode') parser.add_argument( '--use-opengl-plot', dest="use_opengl_plot", action="store_true", default=False, help='Use OpenGL for plots (instead of matplotlib)') options = parser.parse_args(argv[1:]) if options.debug: logging.root.setLevel(logging.DEBUG) # # Import most of the things here to be sure to use the right logging level # try: # it should be loaded before h5py import hdf5plugin # noqa except ImportError: _logger.debug("Backtrace", exc_info=True) hdf5plugin = None try: import h5py except ImportError: _logger.debug("Backtrace", exc_info=True) h5py = None if h5py is None: message = "Module 'h5py' is not installed but is mandatory."\ + " You can install it using \"pip install h5py\"." _logger.error(message) return -1 if hdf5plugin is None: message = "Module 'hdf5plugin' is not installed. It supports some hdf5"\ + " compressions. You can install it using \"pip install hdf5plugin\"." _logger.warning(message) # # Run the application # if options.use_opengl_plot: from silx.gui.plot import PlotWidget PlotWidget.setDefaultBackend("opengl") app = qt.QApplication([]) qt.QLocale.setDefault(qt.QLocale.c()) sys.excepthook = qt.exceptionHandler window = Viewer() window.setAttribute(qt.Qt.WA_DeleteOnClose, True) window.resize(qt.QSize(640, 480)) for filename in options.files: try: window.appendFile(filename) except IOError as e: _logger.error(e.args[0]) _logger.debug("Backtrace", exc_info=True) window.show() result = app.exec_() # remove ending warnings relative to QTimer app.deleteLater() return result
class BackgroundWidget(qt.QWidget): """Background configuration widget, with a plot to preview the results. Strip and snip filters parameters can be adjusted using input widgets, and the computed backgrounds are plotted next to the original data to show the result.""" def __init__(self, parent=None): qt.QWidget.__init__(self, parent) self.setWindowTitle("Strip and SNIP Configuration Window") self.mainLayout = qt.QVBoxLayout(self) self.mainLayout.setContentsMargins(0, 0, 0, 0) self.mainLayout.setSpacing(2) self.parametersWidget = BackgroundParamWidget(self) self.graphWidget = PlotWidget(parent=self) self.mainLayout.addWidget(self.parametersWidget) self.mainLayout.addWidget(self.graphWidget) self._x = None self._y = None self.parametersWidget.sigBackgroundParamWidgetSignal.connect(self._slot) def getParameters(self): """Return dictionary of parameters defined in the GUI The returned dictionary contains following values: - *algorithm*: *"strip"* or *"snip"* - *StripWidth*: width of strip iterator - *StripIterations*: number of iterations - *StripThreshold*: strip curvature (currently fixed to 1.0) - *SnipWidth*: width of snip algorithm - *SmoothingFlag*: flag to enable/disable smoothing - *SmoothingWidth*: width of Savitsky-Golay smoothing filter - *AnchorsFlag*: flag to enable/disable anchors - *AnchorsList*: list of anchors (X coordinates of fixed values) """ return self.parametersWidget.getParameters() def setParameters(self, ddict): """Set values for all input widgets. :param dict ddict: Input dictionary, must have the same keys as the dictionary output by :meth:`getParameters` """ return self.parametersWidget.setParameters(ddict) def setData(self, x, y, xmin=None, xmax=None): """Set data for the original curve, and _update strip and snip curves accordingly. :param x: Array or sequence of curve abscissa values :param y: Array or sequence of curve ordinate values :param xmin: Min value to be displayed on the X axis :param xmax: Max value to be displayed on the X axis """ self._x = x self._y = y self._xmin = xmin self._xmax = xmax self._update(resetzoom=True) def _slot(self, ddict): self._update() def _update(self, resetzoom=False): """Compute strip and snip backgrounds, update the curves """ if self._y is None: return pars = self.getParameters() # smoothed data y = numpy.ravel(numpy.array(self._y)).astype(numpy.float) if pars["SmoothingFlag"]: ysmooth = filters.savitsky_golay(y, pars['SmoothingWidth']) f = [0.25, 0.5, 0.25] ysmooth[1:-1] = numpy.convolve(ysmooth, f, mode=0) ysmooth[0] = 0.5 * (ysmooth[0] + ysmooth[1]) ysmooth[-1] = 0.5 * (ysmooth[-1] + ysmooth[-2]) else: ysmooth = y # loop for anchors x = self._x niter = pars['StripIterations'] anchors_indices = [] if pars['AnchorsFlag'] and pars['AnchorsList'] is not None: ravelled = x for channel in pars['AnchorsList']: if channel <= ravelled[0]: continue index = numpy.nonzero(ravelled >= channel)[0] if len(index): index = min(index) if index > 0: anchors_indices.append(index) stripBackground = filters.strip(ysmooth, w=pars['StripWidth'], niterations=niter, factor=pars['StripThreshold'], anchors=anchors_indices) if niter >= 1000: # final smoothing stripBackground = filters.strip(stripBackground, w=1, niterations=50*pars['StripWidth'], factor=pars['StripThreshold'], anchors=anchors_indices) if len(anchors_indices) == 0: anchors_indices = [0, len(ysmooth)-1] anchors_indices.sort() snipBackground = 0.0 * ysmooth lastAnchor = 0 for anchor in anchors_indices: if (anchor > lastAnchor) and (anchor < len(ysmooth)): snipBackground[lastAnchor:anchor] =\ filters.snip1d(ysmooth[lastAnchor:anchor], pars['SnipWidth']) lastAnchor = anchor if lastAnchor < len(ysmooth): snipBackground[lastAnchor:] =\ filters.snip1d(ysmooth[lastAnchor:], pars['SnipWidth']) self.graphWidget.addCurve(x, y, legend='Input Data', replace=True, resetzoom=resetzoom) self.graphWidget.addCurve(x, stripBackground, legend='Strip Background', resetzoom=False) self.graphWidget.addCurve(x, snipBackground, legend='SNIP Background', resetzoom=False) if self._xmin is not None and self._xmax is not None: self.graphWidget.getXAxis().setLimits(self._xmin, self._xmax)
def setUp(self): self.plot = PlotWidget(backend='none') self.saveAction = SaveAction(plot=self.plot) self.tempdir = tempfile.mkdtemp() self.out_fname = os.path.join(self.tempdir, "out.dat")
class TestSaveActionSaveCurvesAsSpec(unittest.TestCase): def setUp(self): self.plot = PlotWidget(backend='none') self.saveAction = SaveAction(plot=self.plot) self.tempdir = tempfile.mkdtemp() self.out_fname = os.path.join(self.tempdir, "out.dat") def tearDown(self): os.unlink(self.out_fname) os.rmdir(self.tempdir) def testSaveMultipleCurvesAsSpec(self): """Test that labels are properly used.""" self.plot.setGraphXLabel("graph x label") self.plot.setGraphYLabel("graph y label") self.plot.addCurve([0, 1], [1, 2], "curve with labels", xlabel="curve0 X", ylabel="curve0 Y") self.plot.addCurve([-1, 3], [-6, 2], "curve with X label", xlabel="curve1 X") self.plot.addCurve([-2, 0], [8, 12], "curve with Y label", ylabel="curve2 Y") self.plot.addCurve([3, 1], [7, 6], "curve with no labels") self.saveAction._saveCurves(self.plot, self.out_fname, SaveAction.DEFAULT_ALL_CURVES_FILTERS[0]) # "All curves as SpecFile (*.dat)" with open(self.out_fname, "rb") as f: file_content = f.read() if hasattr(file_content, "decode"): file_content = file_content.decode() # case with all curve labels specified self.assertIn("#S 1 curve0 Y", file_content) self.assertIn("#L curve0 X curve0 Y", file_content) # graph X&Y labels are used when no curve label is specified self.assertIn("#S 2 graph y label", file_content) self.assertIn("#L curve1 X graph y label", file_content) self.assertIn("#S 3 curve2 Y", file_content) self.assertIn("#L graph x label curve2 Y", file_content) self.assertIn("#S 4 graph y label", file_content) self.assertIn("#L graph x label graph y label", file_content)
def __init__(self, parent=None, name="Colormap Dialog"): qt.QDialog.__init__(self, parent) self.setWindowTitle(name) self.title = name self.colormapList = ["Greyscale", "Reverse Grey", "Temperature", "Red", "Green", "Blue", "Many"] # histogramData is tupel(bins, counts) self.histogramData = None # default values self.dataMin = -10 self.dataMax = 10 self.minValue = 0 self.maxValue = 1 self.colormapIndex = 2 self.colormapType = 0 self.autoscale = False self.autoscale90 = False # main layout vlayout = qt.QVBoxLayout(self) vlayout.setContentsMargins(10, 10, 10, 10) vlayout.setSpacing(0) # layout 1 : -combo to choose colormap # -autoscale button # -autoscale 90% button hbox1 = qt.QWidget(self) hlayout1 = qt.QHBoxLayout(hbox1) vlayout.addWidget(hbox1) hlayout1.setContentsMargins(0, 0, 0, 0) hlayout1.setSpacing(10) # combo self.combo = qt.QComboBox(hbox1) for colormap in self.colormapList: self.combo.addItem(colormap) self.combo.activated[int].connect(self.colormapChange) hlayout1.addWidget(self.combo) # autoscale self.autoScaleButton = qt.QPushButton("Autoscale", hbox1) self.autoScaleButton.setCheckable(True) self.autoScaleButton.setAutoDefault(False) self.autoScaleButton.toggled[bool].connect(self.autoscaleChange) hlayout1.addWidget(self.autoScaleButton) # autoscale 90% self.autoScale90Button = qt.QPushButton("Autoscale 90%", hbox1) self.autoScale90Button.setCheckable(True) self.autoScale90Button.setAutoDefault(False) self.autoScale90Button.toggled[bool].connect(self.autoscale90Change) hlayout1.addWidget(self.autoScale90Button) # hlayout hbox0 = qt.QWidget(self) self.__hbox0 = hbox0 hlayout0 = qt.QHBoxLayout(hbox0) hlayout0.setContentsMargins(0, 0, 0, 0) hlayout0.setSpacing(0) vlayout.addWidget(hbox0) #hlayout0.addStretch(10) self.buttonGroup = qt.QButtonGroup() g1 = qt.QCheckBox(hbox0) g1.setText("Linear") g2 = qt.QCheckBox(hbox0) g2.setText("Logarithmic") g3 = qt.QCheckBox(hbox0) g3.setText("Gamma") self.buttonGroup.addButton(g1, 0) self.buttonGroup.addButton(g2, 1) self.buttonGroup.addButton(g3, 2) self.buttonGroup.setExclusive(True) if self.colormapType == 1: self.buttonGroup.button(1).setChecked(True) elif self.colormapType == 2: self.buttonGroup.button(2).setChecked(True) else: self.buttonGroup.button(0).setChecked(True) hlayout0.addWidget(g1) hlayout0.addWidget(g2) hlayout0.addWidget(g3) vlayout.addWidget(hbox0) self.buttonGroup.buttonClicked[int].connect(self.buttonGroupChange) vlayout.addSpacing(20) hboxlimits = qt.QWidget(self) hboxlimitslayout = qt.QHBoxLayout(hboxlimits) hboxlimitslayout.setContentsMargins(0, 0, 0, 0) hboxlimitslayout.setSpacing(0) self.slider = None vlayout.addWidget(hboxlimits) vboxlimits = qt.QWidget(hboxlimits) vboxlimitslayout = qt.QVBoxLayout(vboxlimits) vboxlimitslayout.setContentsMargins(0, 0, 0, 0) vboxlimitslayout.setSpacing(0) hboxlimitslayout.addWidget(vboxlimits) # hlayout 2 : - min label # - min texte hbox2 = qt.QWidget(vboxlimits) self.__hbox2 = hbox2 hlayout2 = qt.QHBoxLayout(hbox2) hlayout2.setContentsMargins(0, 0, 0, 0) hlayout2.setSpacing(0) #vlayout.addWidget(hbox2) vboxlimitslayout.addWidget(hbox2) hlayout2.addStretch(10) self.minLabel = qt.QLabel(hbox2) self.minLabel.setText("Minimum") hlayout2.addWidget(self.minLabel) hlayout2.addSpacing(5) hlayout2.addStretch(1) self.minText = MyQLineEdit(hbox2) self.minText.setFixedWidth(150) self.minText.setAlignment(qt.Qt.AlignRight) self.minText.returnPressed[()].connect(self.minTextChanged) hlayout2.addWidget(self.minText) # hlayout 3 : - min label # - min text hbox3 = qt.QWidget(vboxlimits) self.__hbox3 = hbox3 hlayout3 = qt.QHBoxLayout(hbox3) hlayout3.setContentsMargins(0, 0, 0, 0) hlayout3.setSpacing(0) #vlayout.addWidget(hbox3) vboxlimitslayout.addWidget(hbox3) hlayout3.addStretch(10) self.maxLabel = qt.QLabel(hbox3) self.maxLabel.setText("Maximum") hlayout3.addWidget(self.maxLabel) hlayout3.addSpacing(5) hlayout3.addStretch(1) self.maxText = MyQLineEdit(hbox3) self.maxText.setFixedWidth(150) self.maxText.setAlignment(qt.Qt.AlignRight) self.maxText.returnPressed[()].connect(self.maxTextChanged) hlayout3.addWidget(self.maxText) # Graph widget for color curve... self.c = PlotWidget(self, backend=None) self.c.setGraphXLabel("Data Values") self.c.setInteractiveMode('select') self.marge = (abs(self.dataMax) + abs(self.dataMin)) / 6.0 self.minmd = self.dataMin - self.marge self.maxpd = self.dataMax + self.marge self.c.setGraphXLimits(self.minmd, self.maxpd) self.c.setGraphYLimits(-11.5, 11.5) x = [self.minmd, self.dataMin, self.dataMax, self.maxpd] y = [-10, -10, 10, 10 ] self.c.addCurve(x, y, legend="ConstrainedCurve", color='black', symbol='o', linestyle='-') self.markers = [] self.__x = x self.__y = y labelList = ["","Min", "Max", ""] for i in range(4): if i in [1, 2]: draggable = True color = "blue" else: draggable = False color = "black" #TODO symbol legend = "%d" % i self.c.addXMarker(x[i], legend=legend, text=labelList[i], draggable=draggable, color=color) self.markers.append((legend, "")) self.c.setMinimumSize(qt.QSize(250,200)) vlayout.addWidget(self.c) self.c.sigPlotSignal.connect(self.chval) # colormap window can not be resized self.setFixedSize(vlayout.minimumSize())