ImageView: add support for RGB levels mode
This commit is contained in:
parent
07d1a62bfc
commit
4a4a7383bc
@ -12,7 +12,7 @@ Widget used for displaying 2D or 3D data. Features:
|
||||
- ROI plotting
|
||||
- Image normalization through a variety of methods
|
||||
"""
|
||||
import os
|
||||
import os, sys
|
||||
import numpy as np
|
||||
|
||||
from ..Qt import QtCore, QtGui, USE_PYSIDE
|
||||
@ -26,6 +26,7 @@ from ..graphicsItems.ROI import *
|
||||
from ..graphicsItems.LinearRegionItem import *
|
||||
from ..graphicsItems.InfiniteLine import *
|
||||
from ..graphicsItems.ViewBox import *
|
||||
from ..graphicsItems.VTickGroup import VTickGroup
|
||||
from ..graphicsItems.GradientEditorItem import addGradientListToDocstring
|
||||
from .. import ptime as ptime
|
||||
from .. import debug as debug
|
||||
@ -79,7 +80,8 @@ class ImageView(QtGui.QWidget):
|
||||
sigTimeChanged = QtCore.Signal(object, object)
|
||||
sigProcessingChanged = QtCore.Signal(object)
|
||||
|
||||
def __init__(self, parent=None, name="ImageView", view=None, imageItem=None, *args):
|
||||
def __init__(self, parent=None, name="ImageView", view=None, imageItem=None,
|
||||
levelMode='mono', *args):
|
||||
"""
|
||||
By default, this class creates an :class:`ImageItem <pyqtgraph.ImageItem>` to display image data
|
||||
and a :class:`ViewBox <pyqtgraph.ViewBox>` to contain the ImageItem.
|
||||
@ -101,6 +103,9 @@ class ImageView(QtGui.QWidget):
|
||||
imageItem (ImageItem) If specified, this object will be used to
|
||||
display the image. Must be an instance of ImageItem
|
||||
or other compatible object.
|
||||
levelMode See the *levelMode* argument to
|
||||
:func:`HistogramLUTItem.__init__()
|
||||
<pyqtgraph.HistogramLUTItem.__init__>`
|
||||
============= =========================================================
|
||||
|
||||
Note: to display axis ticks inside the ImageView, instantiate it
|
||||
@ -109,8 +114,10 @@ class ImageView(QtGui.QWidget):
|
||||
pg.ImageView(view=pg.PlotItem())
|
||||
"""
|
||||
QtGui.QWidget.__init__(self, parent, *args)
|
||||
self.levelMax = 4096
|
||||
self.levelMin = 0
|
||||
self._imageLevels = None # [(min, max), ...] per channel image metrics
|
||||
self.levelMin = None # min / max levels across all channels
|
||||
self.levelMax = None
|
||||
|
||||
self.name = name
|
||||
self.image = None
|
||||
self.axes = {}
|
||||
@ -118,6 +125,7 @@ class ImageView(QtGui.QWidget):
|
||||
self.ui = Ui_Form()
|
||||
self.ui.setupUi(self)
|
||||
self.scene = self.ui.graphicsView.scene()
|
||||
self.ui.histogram.setLevelMode(levelMode)
|
||||
|
||||
self.ignoreTimeLine = False
|
||||
|
||||
@ -151,13 +159,15 @@ class ImageView(QtGui.QWidget):
|
||||
self.normRoi.setZValue(20)
|
||||
self.view.addItem(self.normRoi)
|
||||
self.normRoi.hide()
|
||||
self.roiCurve = self.ui.roiPlot.plot()
|
||||
self.timeLine = InfiniteLine(0, movable=True)
|
||||
self.roiCurves = []
|
||||
self.timeLine = InfiniteLine(0, movable=True, markers=[('^', 0), ('v', 1)])
|
||||
self.timeLine.setPen((255, 255, 0, 200))
|
||||
self.timeLine.setZValue(1)
|
||||
self.ui.roiPlot.addItem(self.timeLine)
|
||||
self.ui.splitter.setSizes([self.height()-35, 35])
|
||||
self.ui.roiPlot.hideAxis('left')
|
||||
self.frameTicks = VTickGroup(yrange=[0.8, 1], pen=0.4)
|
||||
self.ui.roiPlot.addItem(self.frameTicks, ignoreBounds=True)
|
||||
|
||||
self.keysPressed = {}
|
||||
self.playTimer = QtCore.QTimer()
|
||||
@ -200,7 +210,7 @@ class ImageView(QtGui.QWidget):
|
||||
|
||||
self.roiClicked() ## initialize roi plot to correct shape / visibility
|
||||
|
||||
def setImage(self, img, autoRange=True, autoLevels=True, levels=None, axes=None, xvals=None, pos=None, scale=None, transform=None, autoHistogramRange=True):
|
||||
def setImage(self, img, autoRange=True, autoLevels=True, levels=None, axes=None, xvals=None, pos=None, scale=None, transform=None, autoHistogramRange=True, levelMode=None):
|
||||
"""
|
||||
Set the image to be displayed in the widget.
|
||||
|
||||
@ -208,8 +218,9 @@ class ImageView(QtGui.QWidget):
|
||||
**Arguments:**
|
||||
img (numpy array) the image to be displayed. See :func:`ImageItem.setImage` and
|
||||
*notes* below.
|
||||
xvals (numpy array) 1D array of z-axis values corresponding to the third axis
|
||||
in a 3D image. For video, this array should contain the time of each frame.
|
||||
xvals (numpy array) 1D array of z-axis values corresponding to the first axis
|
||||
in a 3D image. For video, this array should contain the time of each
|
||||
frame.
|
||||
autoRange (bool) whether to scale/pan the view to fit the image.
|
||||
autoLevels (bool) whether to update the white/black levels to fit the image.
|
||||
levels (min, max); the white and black level values to use.
|
||||
@ -224,7 +235,11 @@ class ImageView(QtGui.QWidget):
|
||||
and *scale*.
|
||||
autoHistogramRange If True, the histogram y-range is automatically scaled to fit the
|
||||
image data.
|
||||
================== ===========================================================================
|
||||
levelMode If specified, this sets the user interaction mode for setting image
|
||||
levels. Options are 'mono', which provides a single level control for
|
||||
all image channels, and 'rgb' or 'rgba', which provide individual
|
||||
controls for each channel.
|
||||
================== =======================================================================
|
||||
|
||||
**Notes:**
|
||||
|
||||
@ -252,6 +267,8 @@ class ImageView(QtGui.QWidget):
|
||||
|
||||
self.image = img
|
||||
self.imageDisp = None
|
||||
if levelMode is not None:
|
||||
self.ui.histogram.setLevelMode(levelMode)
|
||||
|
||||
profiler()
|
||||
|
||||
@ -310,10 +327,9 @@ class ImageView(QtGui.QWidget):
|
||||
profiler()
|
||||
|
||||
if self.axes['t'] is not None:
|
||||
#self.ui.roiPlot.show()
|
||||
self.ui.roiPlot.setXRange(self.tVals.min(), self.tVals.max())
|
||||
self.frameTicks.setXVals(self.tVals)
|
||||
self.timeLine.setValue(0)
|
||||
#self.ui.roiPlot.setMouseEnabled(False, False)
|
||||
if len(self.tVals) > 1:
|
||||
start = self.tVals.min()
|
||||
stop = self.tVals.max() + abs(self.tVals[-1] - self.tVals[0]) * 0.02
|
||||
@ -325,8 +341,7 @@ class ImageView(QtGui.QWidget):
|
||||
stop = 1
|
||||
for s in [self.timeLine, self.normRgn]:
|
||||
s.setBounds([start, stop])
|
||||
#else:
|
||||
#self.ui.roiPlot.hide()
|
||||
|
||||
profiler()
|
||||
|
||||
self.imageItem.resetTransform()
|
||||
@ -364,11 +379,14 @@ class ImageView(QtGui.QWidget):
|
||||
|
||||
def autoLevels(self):
|
||||
"""Set the min/max intensity levels automatically to match the image data."""
|
||||
self.setLevels(self.levelMin, self.levelMax)
|
||||
self.setLevels(rgba=self._imageLevels)
|
||||
|
||||
def setLevels(self, min, max):
|
||||
"""Set the min/max (bright and dark) levels."""
|
||||
self.ui.histogram.setLevels(min, max)
|
||||
def setLevels(self, *args, **kwds):
|
||||
"""Set the min/max (bright and dark) levels.
|
||||
|
||||
See :func:`HistogramLUTItem.setLevels <pyqtgraph.HistogramLUTItem.setLevels>`.
|
||||
"""
|
||||
self.ui.histogram.setLevels(*args, **kwds)
|
||||
|
||||
def autoRange(self):
|
||||
"""Auto scale and pan the view around the image such that the image fills the view."""
|
||||
@ -377,12 +395,13 @@ class ImageView(QtGui.QWidget):
|
||||
|
||||
def getProcessedImage(self):
|
||||
"""Returns the image data after it has been processed by any normalization options in use.
|
||||
This method also sets the attributes self.levelMin and self.levelMax
|
||||
to indicate the range of data in the image."""
|
||||
"""
|
||||
if self.imageDisp is None:
|
||||
image = self.normalize(self.image)
|
||||
self.imageDisp = image
|
||||
self.levelMin, self.levelMax = list(map(float, self.quickMinMax(self.imageDisp)))
|
||||
self._imageLevels = self.quickMinMax(self.imageDisp)
|
||||
self.levelMin = min([level[0] for level in self._imageLevels])
|
||||
self.levelMax = max([level[1] for level in self._imageLevels])
|
||||
|
||||
return self.imageDisp
|
||||
|
||||
@ -527,13 +546,15 @@ class ImageView(QtGui.QWidget):
|
||||
#self.ui.roiPlot.show()
|
||||
self.ui.roiPlot.setMouseEnabled(True, True)
|
||||
self.ui.splitter.setSizes([self.height()*0.6, self.height()*0.4])
|
||||
self.roiCurve.show()
|
||||
for c in self.roiCurves:
|
||||
c.show()
|
||||
self.roiChanged()
|
||||
self.ui.roiPlot.showAxis('left')
|
||||
else:
|
||||
self.roi.hide()
|
||||
self.ui.roiPlot.setMouseEnabled(False, False)
|
||||
self.roiCurve.hide()
|
||||
for c in self.roiCurves:
|
||||
c.hide()
|
||||
self.ui.roiPlot.hideAxis('left')
|
||||
|
||||
if self.hasTimeAxis():
|
||||
@ -557,36 +578,65 @@ class ImageView(QtGui.QWidget):
|
||||
return
|
||||
|
||||
image = self.getProcessedImage()
|
||||
if image.ndim == 2:
|
||||
axes = (0, 1)
|
||||
elif image.ndim == 3:
|
||||
axes = (1, 2)
|
||||
else:
|
||||
return
|
||||
|
||||
|
||||
# Extract image data from ROI
|
||||
axes = (self.axes['x'], self.axes['y'])
|
||||
|
||||
data, coords = self.roi.getArrayRegion(image.view(np.ndarray), self.imageItem, axes, returnMappedCoords=True)
|
||||
if data is not None:
|
||||
while data.ndim > 1:
|
||||
data = data.mean(axis=1)
|
||||
if image.ndim == 3:
|
||||
self.roiCurve.setData(y=data, x=self.tVals)
|
||||
if data is None:
|
||||
return
|
||||
|
||||
# Convert extracted data into 1D plot data
|
||||
if self.axes['t'] is None:
|
||||
# Average across y-axis of ROI
|
||||
data = data.mean(axis=axes[1])
|
||||
coords = coords[:,:,0] - coords[:,0:1,0]
|
||||
xvals = (coords**2).sum(axis=0) ** 0.5
|
||||
else:
|
||||
# Average data within entire ROI for each frame
|
||||
data = data.mean(axis=max(axes)).mean(axis=min(axes))
|
||||
xvals = self.tVals
|
||||
|
||||
# Handle multi-channel data
|
||||
if data.ndim == 1:
|
||||
plots = [(xvals, data, 'w')]
|
||||
if data.ndim == 2:
|
||||
if data.shape[1] == 1:
|
||||
colors = 'w'
|
||||
else:
|
||||
while coords.ndim > 2:
|
||||
coords = coords[:,:,0]
|
||||
coords = coords - coords[:,0,np.newaxis]
|
||||
xvals = (coords**2).sum(axis=0) ** 0.5
|
||||
self.roiCurve.setData(y=data, x=xvals)
|
||||
colors = 'rgbw'
|
||||
plots = []
|
||||
for i in range(data.shape[1]):
|
||||
d = data[:,i]
|
||||
plots.append((xvals, d, colors[i]))
|
||||
|
||||
# Update plot line(s)
|
||||
while len(plots) < len(self.roiCurves):
|
||||
c = self.roiCurves.pop()
|
||||
c.scene().removeItem(c)
|
||||
while len(plots) > len(self.roiCurves):
|
||||
self.roiCurves.append(self.ui.roiPlot.plot())
|
||||
for i in range(len(plots)):
|
||||
x, y, p = plots[i]
|
||||
self.roiCurves[i].setData(x, y, pen=p)
|
||||
|
||||
def quickMinMax(self, data):
|
||||
"""
|
||||
Estimate the min/max values of *data* by subsampling.
|
||||
Returns [(min, max), ...] with one item per channel
|
||||
"""
|
||||
while data.size > 1e6:
|
||||
ax = np.argmax(data.shape)
|
||||
sl = [slice(None)] * data.ndim
|
||||
sl[ax] = slice(None, None, 2)
|
||||
data = data[sl]
|
||||
return nanmin(data), nanmax(data)
|
||||
|
||||
cax = self.axes['c']
|
||||
if cax is None:
|
||||
return [(float(nanmin(data)), float(nanmax(data)))]
|
||||
else:
|
||||
return [(float(nanmin(data.take(i, axis=cax))),
|
||||
float(nanmax(data.take(i, axis=cax)))) for i in range(data.shape[-1])]
|
||||
|
||||
def normalize(self, image):
|
||||
"""
|
||||
|
Loading…
Reference in New Issue
Block a user