summaryrefslogtreecommitdiff
path: root/PyMca5/PyMcaGui/plotting/ImageView.py
diff options
context:
space:
mode:
Diffstat (limited to 'PyMca5/PyMcaGui/plotting/ImageView.py')
-rw-r--r--PyMca5/PyMcaGui/plotting/ImageView.py898
1 files changed, 31 insertions, 867 deletions
diff --git a/PyMca5/PyMcaGui/plotting/ImageView.py b/PyMca5/PyMcaGui/plotting/ImageView.py
index 2f8bd11..3371533 100644
--- a/PyMca5/PyMcaGui/plotting/ImageView.py
+++ b/PyMca5/PyMcaGui/plotting/ImageView.py
@@ -31,25 +31,11 @@ __contact__ = "thomas.vincent@esrf.fr"
__license__ = "MIT"
__copyright__ = "European Synchrotron Radiation Facility, Grenoble, France"
__doc__ = """
-QWidget displaying a 2D image with histograms on its sides.
+The classes in this module are deprecated. Use
+:class:`silx.gui.plot.ImageView` and
+:class:`silx.gui.plot.ImageViewMainWindow` instead.
-The :class:`ImageView` implements this widget, and
-:class:`ImageViewMainWindow` provides a main window with additional toolbar
-and status bar.
-
-Basic usage of :class:`ImageView` is through the following methods:
-
-- :meth:`ImageView.getColormap`, :meth:`ImageView.setColormap` to update the
- default colormap to use and update the currently displayed image.
-- :meth:`ImageView.setImage` to update the displayed image.
-
-The :class:`ImageView` uses :class:`PlotWindow` and also
-exposes :class:`PyMca5.PyMcaGraph.Plot` API for further control
-(plot title, axes labels, adding other images, ...).
-
-For an example of use, see the implementation of :class:`ImageViewMainWindow`.
-
-The ImageView module can also be used to open an EDF or TIFF file
+This module can be used to open an EDF or TIFF file
from the shell command line.
To view an image file:
``python -m PyMca5.PyMcaGui.plotting.ImageView <file to open>``
@@ -59,872 +45,50 @@ To get help:
# import ######################################################################
-
-import numpy as np
-
+import logging
+import traceback
try:
from .. import PyMcaQt as qt
except ImportError:
from PyMca5.PyMcaGui import PyMcaQt as qt
-from .PlotWindow import PlotWindow
-from .Toolbars import ProfileToolBar, LimitsToolBar
-
-from PyMca5.PyMcaGraph import Plot
-
-
-# utils #######################################################################
-
-_COLORMAP_CURSOR_COLORS = {
- 'gray': 'pink',
- 'reversed gray': 'pink',
- 'temperature': 'black',
- 'red': 'gray',
- 'green': 'gray',
- 'blue': 'gray'}
-
-
-def _cursorColorForColormap(colormapName):
- """Get a color suitable for overlay over a colormap.
-
- :param str colormapName: The name of the colormap.
- :return: Name of the color.
- :rtype: str
- """
- return _COLORMAP_CURSOR_COLORS.get(colormapName, 'black')
-
-
-# RadarView ###################################################################
-
-class RadarView(qt.QGraphicsView):
- """Widget presenting a synthetic view of a 2D area and
- the current visible area.
-
- Coordinates are as in QGraphicsView:
- x goes from left to right and y goes from top to bottom.
- This widget preserves the aspect ratio of the areas.
-
- The 2D area and the visible area can be set with :meth:`setDataRect`
- and :meth:`setVisibleRect`.
- When the visible area has been dragged by the user, its new position
- is signaled by the *visibleRectDragged* signal.
-
- It is possible to invert the direction of the axes by using the
- :meth:`scale` method of QGraphicsView.
- """
-
- visibleRectDragged = qt.pyqtSignal(float, float, float, float)
- """Signals that the visible rectangle has been dragged.
-
- It provides: left, top, width, height in data coordinates.
- """
-
- _DATA_PEN = qt.QPen(qt.QColor('white'))
- _DATA_BRUSH = qt.QBrush(qt.QColor('light gray'))
- _VISIBLE_PEN = qt.QPen(qt.QColor('red'))
- _VISIBLE_BRUSH = qt.QBrush(qt.QColor(0, 0, 0, 0))
- _TOOLTIP = 'Radar View:\nVisible area (in red)\nof the image (in gray).'
-
- _PIXMAP_SIZE = 256
-
- class _DraggableRectItem(qt.QGraphicsRectItem):
- """RectItem which signals its change through visibleRectDragged."""
- def __init__(self, *args, **kwargs):
- super(RadarView._DraggableRectItem, self).__init__(*args, **kwargs)
- self.setFlag(qt.QGraphicsItem.ItemIsMovable)
- self.setFlag(qt.QGraphicsItem.ItemSendsGeometryChanges)
- self._ignoreChange = False
- self._constraint = 0, 0, 0, 0
-
- def setConstraintRect(self, left, top, width, height):
- """Set the constraint rectangle for dragging.
-
- The coordinates are in the _DraggableRectItem coordinate system.
-
- This constraint only applies to modification through interaction
- (i.e., this constraint is not applied to change through API).
-
- If the _DraggableRectItem is smaller than the constraint rectangle,
- the _DraggableRectItem remains within the constraint rectangle.
- If the _DraggableRectItem is wider than the constraint rectangle,
- the constraint rectangle remains within the _DraggableRectItem.
- """
- self._constraint = left, left + width, top, top + height
-
- def setPos(self, *args, **kwargs):
- """Overridden to ignore changes from API in itemChange."""
- self._ignoreChange = True
- super(RadarView._DraggableRectItem, self).setPos(*args, **kwargs)
- self._ignoreChange = False
-
- def moveBy(self, *args, **kwargs):
- """Overridden to ignore changes from API in itemChange."""
- self._ignoreChange = True
- super(RadarView._DraggableRectItem, self).moveBy(*args, **kwargs)
- self._ignoreChange = False
-
- def itemChange(self, change, value):
- """Callback called before applying changes to the item."""
- if (change == qt.QGraphicsItem.ItemPositionChange and
- not self._ignoreChange):
- # Makes sure that the visible area is in the data
- # or that data is in the visible area if area is too wide
- x, y = value.x(), value.y()
- xMin, xMax, yMin, yMax = self._constraint
-
- if self.rect().width() <= (xMax - xMin):
- if x < xMin:
- value.setX(xMin)
- elif x > xMax - self.rect().width():
- value.setX(xMax - self.rect().width())
- else:
- if x > xMin:
- value.setX(xMin)
- elif x < xMax - self.rect().width():
- value.setX(xMax - self.rect().width())
-
- if self.rect().height() <= (yMax - yMin):
- if y < yMin:
- value.setY(yMin)
- elif y > yMax - self.rect().height():
- value.setY(yMax - self.rect().height())
- else:
- if y > yMin:
- value.setY(yMin)
- elif y < yMax - self.rect().height():
- value.setY(yMax - self.rect().height())
-
- if self.pos() != value:
- # Notify change through signal
- views = self.scene().views()
- assert len(views) == 1
- views[0].visibleRectDragged.emit(
- value.x() + self.rect().left(),
- value.y() + self.rect().top(),
- self.rect().width(),
- self.rect().height())
-
- return value
-
- return super(RadarView._DraggableRectItem, self).itemChange(
- change, value)
-
- def __init__(self, parent=None):
- self._scene = qt.QGraphicsScene()
- self._dataRect = self._scene.addRect(0, 0, 1, 1,
- self._DATA_PEN,
- self._DATA_BRUSH)
- self._visibleRect = self._DraggableRectItem(0, 0, 1, 1)
- self._visibleRect.setPen(self._VISIBLE_PEN)
- self._visibleRect.setBrush(self._VISIBLE_BRUSH)
- self._scene.addItem(self._visibleRect)
-
- super(RadarView, self).__init__(self._scene, parent)
- self.setHorizontalScrollBarPolicy(qt.Qt.ScrollBarAlwaysOff)
- self.setVerticalScrollBarPolicy(qt.Qt.ScrollBarAlwaysOff)
- self.setFocusPolicy(qt.Qt.NoFocus)
- self.setStyleSheet('border: 0px')
- self.setToolTip(self._TOOLTIP)
-
- def sizeHint(self):
- # """Overridden to avoid sizeHint to depend on content size."""
- return self.minimumSizeHint()
-
- def wheelEvent(self, event):
- # """Overridden to disable vertical scrolling with wheel."""
- event.ignore()
-
- def resizeEvent(self, event):
- # """Overridden to fit current content to new size."""
- self.fitInView(self._scene.itemsBoundingRect(), qt.Qt.KeepAspectRatio)
- super(RadarView, self).resizeEvent(event)
-
- def setDataRect(self, left, top, width, height):
- """Set the bounds of the data rectangular area.
-
- This sets the coordinate system.
- """
- self._dataRect.setRect(left, top, width, height)
- self._visibleRect.setConstraintRect(left, top, width, height)
- self.fitInView(self._scene.itemsBoundingRect(), qt.Qt.KeepAspectRatio)
-
- def setVisibleRect(self, left, top, width, height):
- """Set the visible rectangular area.
-
- The coordinates are relative to the data rect.
- """
- self._visibleRect.setRect(0, 0, width, height)
- self._visibleRect.setPos(left, top)
- self.fitInView(self._scene.itemsBoundingRect(), qt.Qt.KeepAspectRatio)
-
-
-# ImageView ###################################################################
-
-class ImageView(qt.QWidget):
- """Display a single image with horizontal and vertical histograms.
-
- Use :meth:`setImage` to control the displayed image.
- This class also provides the :class:`PyMca5.PyMcaGraph.Plot` API.
- """
-
- HISTOGRAMS_COLOR = 'blue'
- """Color to use for the side histograms."""
-
- HISTOGRAMS_HEIGHT = 200
- """Height in pixels of the side histograms."""
-
- IMAGE_MIN_SIZE = 200
- """Minimum size in pixels of the image area."""
-
- # Qt signals
- valueChanged = qt.pyqtSignal(float, float, float)
- """Signals that the data value under the cursor has changed.
-
- It provides: row, column, data value.
-
- When the cursor is over an histogram, either row or column is Nan
- and the provided data value is the histogram value
- (i.e., the sum along the corresponding row/column).
- Row and columns are either Nan or integer values.
- """
-
- def __init__(self, parent=None, windowFlags=qt.Qt.Widget, backend=None):
- self._imageLegend = '__ImageView__image' + str(id(self))
- self._cache = None # Store currently visible data information
- self._updatingLimits = False
-
- super(ImageView, self).__init__(parent, windowFlags)
- self.setStyleSheet('background-color: white;')
- self._initWidgets(backend)
-
- # Sync PlotBackend and ImageView
- self._updateYAxisInverted()
-
- # Set-up focus proxy to handle arrow key event
- self.setFocusProxy(self._imagePlot)
-
- def _initWidgets(self, backend):
- """Set-up layout and plots."""
- # Monkey-patch for histogram size
- # alternative: create a layout that does not use widget size hints
- def sizeHint():
- return qt.QSize(self.HISTOGRAMS_HEIGHT, self.HISTOGRAMS_HEIGHT)
-
- self._histoHPlot = Plot.Plot(backend=backend)
- self._histoHPlot.setZoomModeEnabled(True)
- self._histoHPlot.setCallback(self._histoHPlotCB)
- self._histoHPlot.getWidgetHandle().sizeHint = sizeHint
- self._histoHPlot.getWidgetHandle().minimumSizeHint = sizeHint
-
- self._imagePlot = PlotWindow(backend=backend, plugins=False,
- colormap=True, flip=True,
- grid=False, togglePoints=False,
- logx=False, logy=False,
- aspect=True)
- self._imagePlot.usePlotBackendColormap = True
- self._imagePlot.setPanWithArrowKeys(True)
-
- self._imagePlot.setZoomModeEnabled(True) # Color is set in setColormap
- self._imagePlot.sigPlotSignal.connect(self._imagePlotCB)
- self._imagePlot.hFlipToolButton.clicked.connect(
- self._updateYAxisInverted)
- self._imagePlot.sigColormapChangedSignal.connect(self.setColormap)
-
- self._histoVPlot = Plot.Plot(backend=backend)
- self._histoVPlot.setZoomModeEnabled(True)
- self._histoVPlot.setCallback(self._histoVPlotCB)
- self._histoVPlot.getWidgetHandle().sizeHint = sizeHint
- self._histoVPlot.getWidgetHandle().minimumSizeHint = sizeHint
-
- self._radarView = RadarView()
- self._radarView.visibleRectDragged.connect(self._radarViewCB)
-
- self._layout = qt.QGridLayout()
- self._layout.addWidget(self._imagePlot, 0, 0)
- self._layout.addWidget(self._histoVPlot.getWidgetHandle(), 0, 1)
- self._layout.addWidget(self._histoHPlot.getWidgetHandle(), 1, 0)
- self._layout.addWidget(self._radarView, 1, 1)
-
- self._layout.setColumnMinimumWidth(0, self.IMAGE_MIN_SIZE)
- self._layout.setColumnStretch(0, 1)
- self._layout.setColumnMinimumWidth(1, self.HISTOGRAMS_HEIGHT)
- self._layout.setColumnStretch(1, 0)
-
- self._layout.setRowMinimumHeight(0, self.IMAGE_MIN_SIZE)
- self._layout.setRowStretch(0, 1)
- self._layout.setRowMinimumHeight(1, self.HISTOGRAMS_HEIGHT)
- self._layout.setRowStretch(1, 0)
-
- self._layout.setSpacing(0)
- self._layout.setContentsMargins(0, 0, 0, 0)
-
- self.setLayout(self._layout)
-
- def _dirtyCache(self):
- self._cache = None
-
- def _updateHistograms(self):
- """Update histograms content using current active image."""
- activeImage = self._imagePlot.getActiveImage()
- if activeImage is not None:
- wasUpdatingLimits = self._updatingLimits
- self._updatingLimits = True
-
- data, legend, info, pixmap = activeImage
- xScale, yScale = info['plot_xScale'], info['plot_yScale']
- height, width = data.shape
-
- xMin, xMax = self._imagePlot.getGraphXLimits()
- yMin, yMax = self._imagePlot.getGraphYLimits()
-
- # Convert plot area limits to image coordinates
- # and work in image coordinates (i.e., in pixels)
- xMin = int((xMin - xScale[0]) / xScale[1])
- xMax = int((xMax - xScale[0]) / xScale[1])
- yMin = int((yMin - yScale[0]) / yScale[1])
- yMax = int((yMax - yScale[0]) / yScale[1])
-
- if (xMin < width and xMax >= 0 and
- yMin < height and yMax >= 0):
- # The image is at least partly in the plot area
- # Get the visible bounds in image coords (i.e., in pixels)
- subsetXMin = 0 if xMin < 0 else xMin
- subsetXMax = (width if xMax >= width else xMax) + 1
- subsetYMin = 0 if yMin < 0 else yMin
- subsetYMax = (height if yMax >= height else yMax) + 1
-
- if (self._cache is None or
- subsetXMin != self._cache['dataXMin'] or
- subsetXMax != self._cache['dataXMax'] or
- subsetYMin != self._cache['dataYMin'] or
- subsetYMax != self._cache['dataYMax']):
- # The visible area of data has changed, update histograms
-
- # Rebuild histograms for visible area
- visibleData = data[subsetYMin:subsetYMax,
- subsetXMin:subsetXMax]
- histoHVisibleData = np.sum(visibleData, axis=0)
- histoVVisibleData = np.sum(visibleData, axis=1)
-
- self._cache = {
- 'dataXMin': subsetXMin,
- 'dataXMax': subsetXMax,
- 'dataYMin': subsetYMin,
- 'dataYMax': subsetYMax,
-
- 'histoH': histoHVisibleData,
- 'histoHMin': np.min(histoHVisibleData),
- 'histoHMax': np.max(histoHVisibleData),
-
- 'histoV': histoVVisibleData,
- 'histoVMin': np.min(histoVVisibleData),
- 'histoVMax': np.max(histoVVisibleData)
- }
-
- # Convert to histogram curve and update plots
- # Taking into account origin and scale
- coords = np.arange(2 * histoHVisibleData.size)
- xCoords = (coords + 1) // 2 + subsetXMin
- xCoords = xScale[0] + xScale[1] * xCoords
- xData = np.take(histoHVisibleData, coords // 2)
- self._histoHPlot.addCurve(xCoords, xData,
- xlabel='', ylabel='',
- replace=False, replot=False,
- color=self.HISTOGRAMS_COLOR,
- linestyle='-',
- selectable=False)
- vMin = self._cache['histoHMin']
- vMax = self._cache['histoHMax']
- vOffset = 0.1 * (vMax - vMin)
- if vOffset == 0.:
- vOffset = 1.
- self._histoHPlot.setGraphYLimits(vMin - vOffset,
- vMax + vOffset)
-
- coords = np.arange(2 * histoVVisibleData.size)
- yCoords = (coords + 1) // 2 + subsetYMin
- yCoords = yScale[0] + yScale[1] * yCoords
- yData = np.take(histoVVisibleData, coords // 2)
- self._histoVPlot.addCurve(yData, yCoords,
- xlabel='', ylabel='',
- replace=False, replot=False,
- color=self.HISTOGRAMS_COLOR,
- linestyle='-',
- selectable=False)
- vMin = self._cache['histoVMin']
- vMax = self._cache['histoVMax']
- vOffset = 0.1 * (vMax - vMin)
- if vOffset == 0.:
- vOffset = 1.
- self._histoVPlot.setGraphXLimits(vMin - vOffset,
- vMax + vOffset)
- else:
- self._dirtyCache()
- self._histoHPlot.clearCurves()
- self._histoVPlot.clearCurves()
+from silx.gui.plot.ImageView import ImageView as SilxImageView
+from silx.gui.plot.ImageView import ImageViewMainWindow as SilxImageViewMainWindow
- self._updatingLimits = wasUpdatingLimits
- def _updateRadarView(self):
- """Update radar view visible area.
+_logger = logging.getLogger(__name__)
+_logger.warning("%s is deprecated, you are advised to use "
+ "silx.gui.plot.ImageView instead",
+ __name__)
+for line in traceback.format_stack(limit=3):
+ _logger.warning(line.rstrip())
- Takes care of y coordinate conversion.
- """
- xMin, xMax = self._imagePlot.getGraphXLimits()
- yMin, yMax = self._imagePlot.getGraphYLimits()
- self._radarView.setVisibleRect(xMin, yMin, xMax - xMin, yMax - yMin)
-
- # Plots event listeners
-
- def _imagePlotCB(self, eventDict):
- """Callback for imageView plot events."""
- if eventDict['event'] == 'mouseMoved':
- activeImage = self._imagePlot.getActiveImage()
- if activeImage is not None:
- data = activeImage[0]
- height, width = data.shape
- x, y = int(eventDict['x']), int(eventDict['y'])
- if x >= 0 and x < width and y >= 0 and y < height:
- self.valueChanged.emit(float(x), float(y),
- data[y][x])
- elif eventDict['event'] == 'limitsChanged':
- # Do not handle histograms limitsChanged while
- # updating their limits from here.
- self._updatingLimits = True
-
- # Refresh histograms
- self._updateHistograms()
-
- # could use eventDict['xdata'], eventDict['ydata'] instead
- xMin, xMax = self._imagePlot.getGraphXLimits()
- yMin, yMax = self._imagePlot.getGraphYLimits()
-
- # Set horizontal histo limits
- self._histoHPlot.setGraphXLimits(xMin, xMax)
- self._histoHPlot.replot()
-
- # Set vertical histo limits
- self._histoVPlot.setGraphYLimits(yMin, yMax)
- self._histoVPlot.replot()
-
- self._updateRadarView()
-
- self._updatingLimits = False
-
- # Replot in case limitsChanged due to set*Limits
- # called from console.
- # This results in an extra replot call in other cases.
- self._imagePlot.replot()
-
- def _histoHPlotCB(self, eventDict):
- """Callback for horizontal histogram plot events."""
- if eventDict['event'] == 'mouseMoved':
- if self._cache is not None:
- activeImage = self._imagePlot.getActiveImage()
- if activeImage is not None:
- xOrigin, xScaleFactor = activeImage[2]['plot_xScale']
-
- minValue = xOrigin + xScaleFactor * self._cache['dataXMin']
- data = self._cache['histoH']
- width = data.shape[0]
- x = int(eventDict['x'])
- if x >= minValue and x < minValue + width:
- self.valueChanged.emit(float('nan'), float(x),
- data[x - minValue])
- elif eventDict['event'] == 'limitsChanged':
- if (not self._updatingLimits and
- eventDict['xdata'] != self._imagePlot.getGraphXLimits()):
- xMin, xMax = eventDict['xdata']
- self._imagePlot.setGraphXLimits(xMin, xMax)
- self._imagePlot.replot()
-
- def _histoVPlotCB(self, eventDict):
- """Callback for vertical histogram plot events."""
- if eventDict['event'] == 'mouseMoved':
- if self._cache is not None:
- activeImage = self._imagePlot.getActiveImage()
- if activeImage is not None:
- yOrigin, yScaleFactor = activeImage[2]['plot_yScale']
-
- minValue = yOrigin + yScaleFactor * self._cache['dataYMin']
- data = self._cache['histoV']
- height = data.shape[0]
- y = int(eventDict['y'])
- if y >= minValue and y < minValue + height:
- self.valueChanged.emit(float(y), float('nan'),
- data[y - minValue])
- elif eventDict['event'] == 'limitsChanged':
- if (not self._updatingLimits and
- eventDict['ydata'] != self._imagePlot.getGraphYLimits()):
- yMin, yMax = eventDict['ydata']
- self._imagePlot.setGraphYLimits(yMin, yMax)
- self._imagePlot.replot()
-
- def _radarViewCB(self, left, top, width, height):
- """Slot for radar view visible rectangle changes."""
- if not self._updatingLimits:
- # Takes care of Y axis conversion
- self._imagePlot.setLimits(left, left + width, top, top + height)
- self._imagePlot.replot()
-
- def _updateYAxisInverted(self):
- """Sync image, vertical histogram and radar view axis orientation."""
- inverted = self._imagePlot.isYAxisInverted()
-
- self._imagePlot.invertYAxis(inverted)
- self._histoVPlot.invertYAxis(inverted)
-
- # Use scale to invert radarView
- # RadarView default Y direction is from top to bottom
- # As opposed to Plot. So invert RadarView when Plot is NOT inverted.
- self._radarView.resetTransform()
- if not inverted:
- self._radarView.scale(1., -1.)
- self._updateRadarView()
-
- self._imagePlot.replot()
- self._histoVPlot.replot()
- self._radarView.update()
-
- def getHistogram(self, axis):
- """Return the histogram and corresponding row or column extent.
-
- The returned value when an histogram is available is a dict with keys:
-
- - 'data': numpy array of the histogram values.
- - 'extent': (start, end) row or column index.
- end index is not included in the histogram.
- :param str axis: 'x' for horizontal, 'y' for vertical
- :return: The histogram and its extent as a dict or None.
- :rtype: dict
+class ImageView(SilxImageView):
+ def __init__(self, parent=None, windowFlags=None, backend=None):
"""
- assert axis in ('x', 'y')
- if self._cache is None:
- return None
- else:
- if axis == 'x':
- return dict(
- data=np.array(self._cache['histoH'], copy=True),
- extent=(self._cache['dataXMin'], self._cache['dataXMax']))
- else:
- return dict(
- data=np.array(self._cache['histoV'], copy=True),
- extent=(self._cache['dataYMin'], self._cache['dataYMax']))
- def radarView(self):
- """Get the lower right radarView widget."""
- return self._radarView
-
- def setRadarView(self, radarView):
- """Change the lower right radarView widget.
-
- :param RadarView radarView: Widget subclassing RadarView to replace
- the lower right corner widget.
- """
- self._radarView.visibleRectDragged.disconnect(self._radarViewCB)
- self._radarView = radarView
- self._radarView.visibleRectDragged.connect(self._radarViewCB)
- self._layout.addWidget(self._radarView, 1, 1)
-
- self._updateYAxisInverted()
-
- # PlotWindow toolbar
-
- def toolBar(self):
- """Returns the tool bar associated with the image plot.
-
- This is the toolBar provided by :class:`PlotWindow`.
-
- :return: The toolBar associated to the image plot.
- :rtype: QToolBar
- """
- return self._imagePlot.toolBar
-
- # High-level API
-
- def getColormap(self):
- """Get the default colormap description.
-
- :return: A description of the current colormap.
- See :meth:`setColormap` for details.
- :rtype: dict
+ :param parent:
+ :param windowFlags: windowFlags (e.g. qt.Qt.Widget, qt.Qt.Window...)
+ If None, the silx default behavior is used: behave as a widget if
+ parent is not None, else behave as a Window.
+ :param backend:
"""
- return self._imagePlot.getDefaultColormap()
-
- def setColormap(self, colormap=None, normalization=None,
- autoscale=None, vmin=None, vmax=None, colors=256):
- """Set the default colormap and update active image.
-
- Parameters that are not provided are taken from the current colormap.
-
- The colormap parameter can also be a dict with the following keys:
-
- - *name*: string. The colormap to use:
- 'gray', 'reversed gray', 'temperature', 'red', 'green', 'blue'.
- - *normalization*: string. The mapping to use for the colormap:
- either 'linear' or 'log'.
- - *autoscale*: bool. Whether to use autoscale (True)
- or range provided by keys 'vmin' and 'vmax' (False).
- - *vmin*: float. The minimum value of the range to use if 'autoscale'
- is False.
- - *vmax*: float. The maximum value of the range to use if 'autoscale'
- is False.
-
- :param colormap: Name of the colormap in
- 'gray', 'reversed gray', 'temperature', 'red', 'green', 'blue'.
- Or the description of the colormap as a dict.
- :type colormap: dict or str.
- :param str normalization: Colormap mapping: 'linear' or 'log'.
- :param bool autoscale: Whether to use autoscale (True)
- or [vmin, vmax] range (False).
- :param float vmin: The minimum value of the range to use if
- 'autoscale' is False.
- :param float vmax: The maximum value of the range to use if
- 'autoscale' is False.
- """
- cmapDict = self._imagePlot.getDefaultColormap()
-
- if isinstance(colormap, dict):
- # Support colormap parameter as a dict
- assert normalization is None
- assert autoscale is None
- assert vmin is None
- assert vmax is None
- assert colors == 256
- for key, value in colormap.items():
- cmapDict[key] = value
-
- else:
- if colormap is not None:
- cmapDict['name'] = colormap
- if normalization is not None:
- cmapDict['normalization'] = normalization
- if autoscale is not None:
- cmapDict['autoscale'] = autoscale
- if vmin is not None:
- cmapDict['vmin'] = vmin
- if vmax is not None:
- cmapDict['vmax'] = vmax
-
- if 'colors' not in cmapDict:
- cmapDict['colors'] = 256
-
- cursorColor = _cursorColorForColormap(cmapDict['name'])
- self._imagePlot.setZoomModeEnabled(True, color=cursorColor)
+ super(ImageView, self).__init__(parent=parent, backend=backend)
- self._imagePlot.setDefaultColormap(cmapDict)
+ # SilxImageView does not have a windowFlags parameter.
+ # A silx PlotWidget behaves as a Widget if parent is not None,
+ # else it behaves as a QMainWindow.
+ if windowFlags is not None:
+ self.setWindowFlags(windowFlags)
- activeImage = self._imagePlot.getActiveImage()
- if activeImage is not None: # Refresh image with new colormap
- data, legend, info, pixmap = activeImage
-
- self._imagePlot.addImage(data, legend=legend, info=info,
- colormap=self.getColormap(),
- replace=False, replot=False)
- self._imagePlot.replot()
-
- def setImage(self, image, origin=(0, 0), scale=(1., 1.),
- copy=True, reset=True):
- """Set the image to display.
-
- :param image: A 2D array representing the image or None to empty plot.
- :type image: numpy.ndarray-like with 2 dimensions or None.
- :param origin: The (x, y) position of the origin of the image.
- Default: (0, 0).
- The origin is the lower left corner of the image when
- the Y axis is not inverted.
- :type origin: Tuple of 2 floats: (origin x, origin y).
- :param scale: The scale factor to apply to the image on X and Y axes.
- Default: (1, 1).
- It is the size of a pixel in the coordinates of the axes.
- Scales must be positive numbers.
- :type scale: Tuple of 2 floats: (scale x, scale y).
- :param bool copy: Whether to copy image data (default) or not.
- :param bool reset: Whether to reset zoom and ROI (default) or not.
- """
- self._dirtyCache()
-
- assert len(origin) == 2
- assert len(scale) == 2
- assert scale[0] > 0
- assert scale[1] > 0
-
- if image is None:
- self._imagePlot.removeImage(self._imageLegend, replot=False)
- return
-
- data = np.array(image, order='C', copy=copy)
- assert data.size != 0
- assert len(data.shape) == 2
- height, width = data.shape
-
- self._imagePlot.addImage(data,
- legend=self._imageLegend,
- xScale=(origin[0], scale[0]),
- yScale=(origin[1], scale[1]),
- colormap=self.getColormap(),
- replace=False,
- replot=False)
- self._imagePlot.setActiveImage(self._imageLegend)
- self._updateHistograms()
-
- self._radarView.setDataRect(origin[0],
- origin[1],
- width * scale[0],
- height * scale[1])
-
- if reset:
- self.resetZoom()
- else:
- self._histoHPlot.replot()
- self._histoVPlot.replot()
- self._imagePlot.replot()
-
- ####################
- # Plot API proxies #
- ####################
-
- # Rebuild side histograms if active image gets changed through the Plot API
-
- def addImage(self, data, legend=None, info=None,
- replace=True, replot=True,
- xScale=None, yScale=None, z=0,
- selectable=False, draggable=False,
- colormap=None, **kw):
- if legend == self._imagePlot.getActiveImage(just_legend=True):
- # Updating active image, resets side histograms cache
- self._dirtyCache()
-
- result = self._imagePlot.addImage(data, legend, info, replace, replot,
- xScale, yScale, z,
- selectable, draggable,
- colormap, **kw)
- self._updateHistograms()
-
- if replot:
- self._histoHPlot.replot()
- self._histoVPlot.replot()
-
- return result
-
- def clear(self):
- self._dirtyCache()
- return self._imagePlot.clear()
-
- def clearImages(self):
- self._dirtyCache()
- return self._imagePlot.clearImages()
-
- def removeImage(self, legend, replot=True):
- if legend == self._imagePlot.getActiveImage(just_legend=True):
- # Removing active image, resets side histograms cache
- self._dirtyCache()
-
- result = self._imageView.removeImage(legend, replot)
- self._updateHistograms()
-
- if replot:
- self._histoHPlot.replot()
- self._histoVPlot.replot()
-
- return result
-
- def setActiveImage(self, legend, replot=True):
- # Active image changes, resets side histogram cache
- self._dirtyCache()
-
- result = self._imagePlot.setActiveImage(legend, replot)
- self._updateHistograms()
-
- if replot:
- self._histoHPlot.replot()
- self._histoVPlot.replot()
- return result
-
- # Invert axes
-
- def invertYAxis(self, flag=True):
- result = self._imagePlot.invertYAxis(flag)
- self._updateYAxisInverted() # To sync vert. histo and radar view
- return result
-
- # Ugly yet simple proxy for the Plot API
-
- def __getattr__(self, name):
- """Proxy to expose image plot API."""
- return getattr(self._imagePlot, name)
-
-
-# ImageViewMainWindow #########################################################
-
-class ImageViewMainWindow(qt.QMainWindow):
- """QMainWindow embedding an ImageView.
-
- Surrounds the ImageView with an associated toolbar and status bar.
- """
+class ImageViewMainWindow(SilxImageViewMainWindow):
def __init__(self, parent=None, windowFlags=qt.Qt.Widget, backend=None):
- self._dataInfo = None
- super(ImageViewMainWindow, self).__init__(parent, windowFlags)
-
- # Create the ImageView widget and add it to the QMainWindow
- self.imageView = ImageView(backend=backend)
- self.imageView.setGraphXLabel('X')
- self.imageView.setGraphYLabel('Y')
- self.imageView.setGraphTitle('Image')
- self.imageView._imagePlot.sigColormapChangedSignal.connect(
- self._colormapUpdated)
- self.setCentralWidget(self.imageView)
-
- # Using PlotWindow's toolbar
- self.addToolBar(self.imageView.toolBar())
- self.profileToolBar = ProfileToolBar(self.imageView._imagePlot)
- self.addToolBar(self.profileToolBar)
- self.addToolBar(qt.Qt.BottomToolBarArea, LimitsToolBar(self.imageView))
-
- self.statusBar()
-
- # Connect to ImageView's signal
- self.imageView.valueChanged.connect(self._statusBarSlot)
-
- def _colormapUpdated(self, colormap):
- """Sync ROI color with current colormap"""
- self.profileToolBar.overlayColor = _cursorColorForColormap(
- colormap['name'])
-
- def _statusBarSlot(self, row, column, value):
- """Update status bar with coordinates/value from plots."""
- if np.isnan(row):
- msg = 'Column: %d, Sum: %g' % (int(column), value)
- elif np.isnan(column):
- msg = 'Row: %d, Sum: %g' % (int(row), value)
- else:
- msg = 'Position: (%d, %d), Value: %g' % (int(row), int(column),
- value)
- if self._dataInfo is not None:
- msg = self._dataInfo + ', ' + msg
-
- self.statusBar().showMessage(msg)
-
- def setImage(self, image, *args, **kwargs):
- """Set the displayed image.
-
- See :meth:`ImageView.setImage` for details.
- """
- if hasattr(image, 'dtype') and hasattr(image, 'shape'):
- assert len(image.shape) == 2
- height, width = image.shape
- self._dataInfo = 'Data: %dx%d (%s)' % (width, height,
- str(image.dtype))
- self.statusBar().showMessage(self._dataInfo)
- else:
- self._dataInfo = None
+ super(ImageViewMainWindow, self).__init__(parent, backend)
+ if windowFlags is not None:
+ self.setWindowFlags(windowFlags)
- # Set the new image in ImageView widget
- self.imageView.setImage(image, *args, **kwargs)
- self.profileToolBar.updateProfile()
- self.setStatusBar(None)
# main ########################################################################