HistogramLUTItem: add rgb level mode, save/restore methods
This commit is contained in:
parent
f627a6a447
commit
6962777b92
|
@ -146,7 +146,8 @@ class GraphicsItem(object):
|
||||||
return parents
|
return parents
|
||||||
|
|
||||||
def viewRect(self):
|
def viewRect(self):
|
||||||
"""Return the bounds (in item coordinates) of this item's ViewBox or GraphicsWidget"""
|
"""Return the visible bounds of this item's ViewBox or GraphicsWidget,
|
||||||
|
in the local coordinate system of the item."""
|
||||||
view = self.getViewBox()
|
view = self.getViewBox()
|
||||||
if view is None:
|
if view is None:
|
||||||
return None
|
return None
|
||||||
|
|
|
@ -36,7 +36,7 @@ class HistogramLUTItem(GraphicsWidget):
|
||||||
sigLevelsChanged = QtCore.Signal(object)
|
sigLevelsChanged = QtCore.Signal(object)
|
||||||
sigLevelChangeFinished = QtCore.Signal(object)
|
sigLevelChangeFinished = QtCore.Signal(object)
|
||||||
|
|
||||||
def __init__(self, image=None, fillHistogram=True):
|
def __init__(self, image=None, fillHistogram=True, rgbHistogram=False, levelMode='mono'):
|
||||||
"""
|
"""
|
||||||
If *image* (ImageItem) is provided, then the control will be automatically linked to the image and changes to the control will be immediately reflected in the image's appearance.
|
If *image* (ImageItem) is provided, then the control will be automatically linked to the image and changes to the control will be immediately reflected in the image's appearance.
|
||||||
By default, the histogram is rendered with a fill. For performance, set *fillHistogram* = False.
|
By default, the histogram is rendered with a fill. For performance, set *fillHistogram* = False.
|
||||||
|
@ -44,6 +44,8 @@ class HistogramLUTItem(GraphicsWidget):
|
||||||
GraphicsWidget.__init__(self)
|
GraphicsWidget.__init__(self)
|
||||||
self.lut = None
|
self.lut = None
|
||||||
self.imageItem = lambda: None # fake a dead weakref
|
self.imageItem = lambda: None # fake a dead weakref
|
||||||
|
self.levelMode = levelMode
|
||||||
|
self.rgbHistogram = rgbHistogram
|
||||||
|
|
||||||
self.layout = QtGui.QGraphicsGridLayout()
|
self.layout = QtGui.QGraphicsGridLayout()
|
||||||
self.setLayout(self.layout)
|
self.setLayout(self.layout)
|
||||||
|
@ -56,9 +58,27 @@ class HistogramLUTItem(GraphicsWidget):
|
||||||
self.gradient = GradientEditorItem()
|
self.gradient = GradientEditorItem()
|
||||||
self.gradient.setOrientation('right')
|
self.gradient.setOrientation('right')
|
||||||
self.gradient.loadPreset('grey')
|
self.gradient.loadPreset('grey')
|
||||||
self.region = LinearRegionItem([0, 1], LinearRegionItem.Horizontal)
|
self.regions = [
|
||||||
self.region.setZValue(1000)
|
LinearRegionItem([0, 1], 'horizontal', swapMode='block'),
|
||||||
self.vb.addItem(self.region)
|
LinearRegionItem([0, 1], 'horizontal', swapMode='block', pen='r',
|
||||||
|
brush=fn.mkBrush((255, 50, 50, 50)), span=(0., 1/3.)),
|
||||||
|
LinearRegionItem([0, 1], 'horizontal', swapMode='block', pen='g',
|
||||||
|
brush=fn.mkBrush((50, 255, 50, 50)), span=(1/3., 2/3.)),
|
||||||
|
LinearRegionItem([0, 1], 'horizontal', swapMode='block', pen='b',
|
||||||
|
brush=fn.mkBrush((50, 50, 255, 80)), span=(2/3., 1.)),
|
||||||
|
LinearRegionItem([0, 1], 'horizontal', swapMode='block', pen='w',
|
||||||
|
brush=fn.mkBrush((255, 255, 255, 50)), span=(2/3., 1.))]
|
||||||
|
for region in self.regions:
|
||||||
|
region.setZValue(1000)
|
||||||
|
self.vb.addItem(region)
|
||||||
|
region.lines[0].addMarker('<|', 0.5)
|
||||||
|
region.lines[1].addMarker('|>', 0.5)
|
||||||
|
region.sigRegionChanged.connect(self.regionChanging)
|
||||||
|
region.sigRegionChangeFinished.connect(self.regionChanged)
|
||||||
|
|
||||||
|
|
||||||
|
self.region = self.regions[0] # for backward compatibility.
|
||||||
|
|
||||||
self.axis = AxisItem('left', linkView=self.vb, maxTickLength=-10, parent=self)
|
self.axis = AxisItem('left', linkView=self.vb, maxTickLength=-10, parent=self)
|
||||||
self.layout.addItem(self.axis, 0, 0)
|
self.layout.addItem(self.axis, 0, 0)
|
||||||
self.layout.addItem(self.vb, 0, 1)
|
self.layout.addItem(self.vb, 0, 1)
|
||||||
|
@ -71,12 +91,23 @@ class HistogramLUTItem(GraphicsWidget):
|
||||||
#self.vb.addItem(self.grid)
|
#self.vb.addItem(self.grid)
|
||||||
|
|
||||||
self.gradient.sigGradientChanged.connect(self.gradientChanged)
|
self.gradient.sigGradientChanged.connect(self.gradientChanged)
|
||||||
self.region.sigRegionChanged.connect(self.regionChanging)
|
|
||||||
self.region.sigRegionChangeFinished.connect(self.regionChanged)
|
|
||||||
self.vb.sigRangeChanged.connect(self.viewRangeChanged)
|
self.vb.sigRangeChanged.connect(self.viewRangeChanged)
|
||||||
self.plot = PlotDataItem()
|
add = QtGui.QPainter.CompositionMode_Plus
|
||||||
self.plot.rotate(90)
|
self.plots = [
|
||||||
|
PlotCurveItem(pen=(200, 200, 200, 100)), # mono
|
||||||
|
PlotCurveItem(pen=(255, 0, 0, 100), compositionMode=add), # r
|
||||||
|
PlotCurveItem(pen=(0, 255, 0, 100), compositionMode=add), # g
|
||||||
|
PlotCurveItem(pen=(0, 0, 255, 100), compositionMode=add), # b
|
||||||
|
PlotCurveItem(pen=(200, 200, 200, 100), compositionMode=add), # a
|
||||||
|
]
|
||||||
|
|
||||||
|
self.plot = self.plots[0] # for backward compatibility.
|
||||||
|
for plot in self.plots:
|
||||||
|
plot.rotate(90)
|
||||||
|
self.vb.addItem(plot)
|
||||||
|
|
||||||
self.fillHistogram(fillHistogram)
|
self.fillHistogram(fillHistogram)
|
||||||
|
self._showRegions()
|
||||||
|
|
||||||
self.vb.addItem(self.plot)
|
self.vb.addItem(self.plot)
|
||||||
self.autoHistogramRange()
|
self.autoHistogramRange()
|
||||||
|
@ -86,25 +117,30 @@ class HistogramLUTItem(GraphicsWidget):
|
||||||
#self.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Expanding)
|
#self.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Expanding)
|
||||||
|
|
||||||
def fillHistogram(self, fill=True, level=0.0, color=(100, 100, 200)):
|
def fillHistogram(self, fill=True, level=0.0, color=(100, 100, 200)):
|
||||||
if fill:
|
colors = [color, (255, 0, 0, 50), (0, 255, 0, 50), (0, 0, 255, 50), (255, 255, 255, 50)]
|
||||||
self.plot.setFillLevel(level)
|
for i,plot in enumerate(self.plots):
|
||||||
self.plot.setFillBrush(color)
|
if fill:
|
||||||
else:
|
plot.setFillLevel(level)
|
||||||
self.plot.setFillLevel(None)
|
plot.setBrush(colors[i])
|
||||||
|
else:
|
||||||
|
plot.setFillLevel(None)
|
||||||
|
|
||||||
#def sizeHint(self, *args):
|
#def sizeHint(self, *args):
|
||||||
#return QtCore.QSizeF(115, 200)
|
#return QtCore.QSizeF(115, 200)
|
||||||
|
|
||||||
def paint(self, p, *args):
|
def paint(self, p, *args):
|
||||||
|
if self.levelMode != 'mono':
|
||||||
|
return
|
||||||
|
|
||||||
pen = self.region.lines[0].pen
|
pen = self.region.lines[0].pen
|
||||||
rgn = self.getLevels()
|
rgn = self.getLevels()
|
||||||
p1 = self.vb.mapFromViewToItem(self, Point(self.vb.viewRect().center().x(), rgn[0]))
|
p1 = self.vb.mapFromViewToItem(self, Point(self.vb.viewRect().center().x(), rgn[0]))
|
||||||
p2 = self.vb.mapFromViewToItem(self, Point(self.vb.viewRect().center().x(), rgn[1]))
|
p2 = self.vb.mapFromViewToItem(self, Point(self.vb.viewRect().center().x(), rgn[1]))
|
||||||
gradRect = self.gradient.mapRectToParent(self.gradient.gradRect.rect())
|
gradRect = self.gradient.mapRectToParent(self.gradient.gradRect.rect())
|
||||||
for pen in [fn.mkPen('k', width=3), pen]:
|
for pen in [fn.mkPen((0, 0, 0, 100), width=3), pen]:
|
||||||
p.setPen(pen)
|
p.setPen(pen)
|
||||||
p.drawLine(p1, gradRect.bottomLeft())
|
p.drawLine(p1 + Point(0, 5), gradRect.bottomLeft())
|
||||||
p.drawLine(p2, gradRect.topLeft())
|
p.drawLine(p2 - Point(0, 5), gradRect.topLeft())
|
||||||
p.drawLine(gradRect.topLeft(), gradRect.topRight())
|
p.drawLine(gradRect.topLeft(), gradRect.topRight())
|
||||||
p.drawLine(gradRect.bottomLeft(), gradRect.bottomRight())
|
p.drawLine(gradRect.bottomLeft(), gradRect.bottomRight())
|
||||||
#p.drawRect(self.boundingRect())
|
#p.drawRect(self.boundingRect())
|
||||||
|
@ -115,28 +151,9 @@ class HistogramLUTItem(GraphicsWidget):
|
||||||
self.vb.enableAutoRange(self.vb.YAxis, False)
|
self.vb.enableAutoRange(self.vb.YAxis, False)
|
||||||
self.vb.setYRange(mn, mx, padding)
|
self.vb.setYRange(mn, mx, padding)
|
||||||
|
|
||||||
#d = mx-mn
|
|
||||||
#mn -= d*padding
|
|
||||||
#mx += d*padding
|
|
||||||
#self.range = [mn,mx]
|
|
||||||
#self.updateRange()
|
|
||||||
#self.vb.setMouseEnabled(False, True)
|
|
||||||
#self.region.setBounds([mn,mx])
|
|
||||||
|
|
||||||
def autoHistogramRange(self):
|
def autoHistogramRange(self):
|
||||||
"""Enable auto-scaling on the histogram plot."""
|
"""Enable auto-scaling on the histogram plot."""
|
||||||
self.vb.enableAutoRange(self.vb.XYAxes)
|
self.vb.enableAutoRange(self.vb.XYAxes)
|
||||||
#self.range = None
|
|
||||||
#self.updateRange()
|
|
||||||
#self.vb.setMouseEnabled(False, False)
|
|
||||||
|
|
||||||
#def updateRange(self):
|
|
||||||
#self.vb.autoRange()
|
|
||||||
#if self.range is not None:
|
|
||||||
#self.vb.setYRange(*self.range)
|
|
||||||
#vr = self.vb.viewRect()
|
|
||||||
|
|
||||||
#self.region.setBounds([vr.top(), vr.bottom()])
|
|
||||||
|
|
||||||
def setImageItem(self, img):
|
def setImageItem(self, img):
|
||||||
"""Set an ImageItem to have its levels and LUT automatically controlled
|
"""Set an ImageItem to have its levels and LUT automatically controlled
|
||||||
|
@ -145,10 +162,8 @@ class HistogramLUTItem(GraphicsWidget):
|
||||||
self.imageItem = weakref.ref(img)
|
self.imageItem = weakref.ref(img)
|
||||||
img.sigImageChanged.connect(self.imageChanged)
|
img.sigImageChanged.connect(self.imageChanged)
|
||||||
img.setLookupTable(self.getLookupTable) ## send function pointer, not the result
|
img.setLookupTable(self.getLookupTable) ## send function pointer, not the result
|
||||||
#self.gradientChanged()
|
|
||||||
self.regionChanged()
|
self.regionChanged()
|
||||||
self.imageChanged(autoLevel=True)
|
self.imageChanged(autoLevel=True)
|
||||||
#self.vb.autoRange()
|
|
||||||
|
|
||||||
def viewRangeChanged(self):
|
def viewRangeChanged(self):
|
||||||
self.update()
|
self.update()
|
||||||
|
@ -161,14 +176,14 @@ class HistogramLUTItem(GraphicsWidget):
|
||||||
self.imageItem().setLookupTable(self.getLookupTable) ## send function pointer, not the result
|
self.imageItem().setLookupTable(self.getLookupTable) ## send function pointer, not the result
|
||||||
|
|
||||||
self.lut = None
|
self.lut = None
|
||||||
#if self.imageItem is not None:
|
|
||||||
#self.imageItem.setLookupTable(self.gradient.getLookupTable(512))
|
|
||||||
self.sigLookupTableChanged.emit(self)
|
self.sigLookupTableChanged.emit(self)
|
||||||
|
|
||||||
def getLookupTable(self, img=None, n=None, alpha=None):
|
def getLookupTable(self, img=None, n=None, alpha=None):
|
||||||
"""Return a lookup table from the color gradient defined by this
|
"""Return a lookup table from the color gradient defined by this
|
||||||
HistogramLUTItem.
|
HistogramLUTItem.
|
||||||
"""
|
"""
|
||||||
|
if self.levelMode is not 'mono':
|
||||||
|
return None
|
||||||
if n is None:
|
if n is None:
|
||||||
if img.dtype == np.uint8:
|
if img.dtype == np.uint8:
|
||||||
n = 256
|
n = 256
|
||||||
|
@ -182,34 +197,124 @@ class HistogramLUTItem(GraphicsWidget):
|
||||||
if self.imageItem() is not None:
|
if self.imageItem() is not None:
|
||||||
self.imageItem().setLevels(self.region.getRegion())
|
self.imageItem().setLevels(self.region.getRegion())
|
||||||
self.sigLevelChangeFinished.emit(self)
|
self.sigLevelChangeFinished.emit(self)
|
||||||
#self.update()
|
|
||||||
|
|
||||||
def regionChanging(self):
|
def regionChanging(self):
|
||||||
if self.imageItem() is not None:
|
if self.imageItem() is not None:
|
||||||
self.imageItem().setLevels(self.region.getRegion())
|
self.imageItem().setLevels(self.getLevels())
|
||||||
self.sigLevelsChanged.emit(self)
|
self.sigLevelsChanged.emit(self)
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
def imageChanged(self, autoLevel=False, autoRange=False):
|
def imageChanged(self, autoLevel=False, autoRange=False):
|
||||||
profiler = debug.Profiler()
|
if self.imageItem() is None:
|
||||||
h = self.imageItem().getHistogram()
|
|
||||||
profiler('get histogram')
|
|
||||||
if h[0] is None:
|
|
||||||
return
|
return
|
||||||
self.plot.setData(*h)
|
|
||||||
profiler('set plot')
|
if self.levelMode == 'mono':
|
||||||
if autoLevel:
|
for plt in self.plots[1:]:
|
||||||
mn = h[0][0]
|
plt.setVisible(False)
|
||||||
mx = h[0][-1]
|
self.plots[0].setVisible(True)
|
||||||
self.region.setRegion([mn, mx])
|
# plot one histogram for all image data
|
||||||
profiler('set region')
|
profiler = debug.Profiler()
|
||||||
|
h = self.imageItem().getHistogram()
|
||||||
|
profiler('get histogram')
|
||||||
|
if h[0] is None:
|
||||||
|
return
|
||||||
|
self.plot.setData(*h)
|
||||||
|
profiler('set plot')
|
||||||
|
if autoLevel:
|
||||||
|
mn = h[0][0]
|
||||||
|
mx = h[0][-1]
|
||||||
|
self.region.setRegion([mn, mx])
|
||||||
|
profiler('set region')
|
||||||
|
else:
|
||||||
|
mn, mx = self.imageItem().levels
|
||||||
|
self.region.setRegion([mn, mx])
|
||||||
|
else:
|
||||||
|
# plot one histogram for each channel
|
||||||
|
self.plots[0].setVisible(False)
|
||||||
|
ch = self.imageItem().getHistogram(perChannel=True)
|
||||||
|
if ch[0] is None:
|
||||||
|
return
|
||||||
|
for i in range(1, 5):
|
||||||
|
if len(ch) >= i:
|
||||||
|
h = ch[i-1]
|
||||||
|
self.plots[i].setVisible(True)
|
||||||
|
self.plots[i].setData(*h)
|
||||||
|
if autoLevel:
|
||||||
|
mn = h[0][0]
|
||||||
|
mx = h[0][-1]
|
||||||
|
self.region[i].setRegion([mn, mx])
|
||||||
|
else:
|
||||||
|
# hide channels not present in image data
|
||||||
|
self.plots[i].setVisible(False)
|
||||||
|
# make sure we are displaying the correct number of channels
|
||||||
|
self._showRegions()
|
||||||
|
|
||||||
def getLevels(self):
|
def getLevels(self):
|
||||||
"""Return the min and max levels.
|
"""Return the min and max levels.
|
||||||
"""
|
"""
|
||||||
return self.region.getRegion()
|
if self.levelMode == 'mono':
|
||||||
|
return self.region.getRegion()
|
||||||
|
else:
|
||||||
|
nch = self.imageItem().channels()
|
||||||
|
if nch is None:
|
||||||
|
nch = 3
|
||||||
|
return [r.getRegion() for r in self.regions[1:nch+1]]
|
||||||
|
|
||||||
def setLevels(self, mn, mx):
|
def setLevels(self, min=None, max=None, rgba=None):
|
||||||
"""Set the min and max levels.
|
"""Set the min/max (bright and dark) levels.
|
||||||
|
|
||||||
|
Arguments may be *min* and *max* for single-channel data, or
|
||||||
|
*rgba* = [(rmin, rmax), ...] for multi-channel data.
|
||||||
"""
|
"""
|
||||||
self.region.setRegion([mn, mx])
|
if self.levelMode == 'mono':
|
||||||
|
if min is None:
|
||||||
|
min, max = rgba[0]
|
||||||
|
assert None not in (min, max)
|
||||||
|
self.region.setRegion((min, max))
|
||||||
|
else:
|
||||||
|
if rgba is None:
|
||||||
|
raise TypeError("Must specify rgba argument when levelMode != 'mono'.")
|
||||||
|
for i, levels in enumerate(rgba):
|
||||||
|
self.regions[i+1].setRegion(levels)
|
||||||
|
|
||||||
|
def setLevelMode(self, mode):
|
||||||
|
""" Set the method of controlling the image levels offered to the user.
|
||||||
|
Options are 'mono' or 'rgba'.
|
||||||
|
"""
|
||||||
|
assert mode in ('mono', 'rgba')
|
||||||
|
self.levelMode = mode
|
||||||
|
self._showRegions()
|
||||||
|
self.imageChanged()
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def _showRegions(self):
|
||||||
|
for i in range(len(self.regions)):
|
||||||
|
self.regions[i].setVisible(False)
|
||||||
|
|
||||||
|
if self.levelMode == 'rgba':
|
||||||
|
imax = 4
|
||||||
|
if self.imageItem() is not None:
|
||||||
|
# Only show rgb channels if connected image lacks alpha.
|
||||||
|
nch = self.imageItem().channels()
|
||||||
|
if nch is None:
|
||||||
|
nch = 3
|
||||||
|
xdif = 1.0 / nch
|
||||||
|
for i in range(1, nch+1):
|
||||||
|
self.regions[i].setVisible(True)
|
||||||
|
self.regions[i].setSpan((i-1) * xdif, i * xdif)
|
||||||
|
self.gradient.hide()
|
||||||
|
elif self.levelMode == 'mono':
|
||||||
|
self.regions[0].setVisible(True)
|
||||||
|
self.gradient.show()
|
||||||
|
else:
|
||||||
|
raise ValueError("Unknown level mode %r" % self.levelMode)
|
||||||
|
|
||||||
|
def saveState(self):
|
||||||
|
return {
|
||||||
|
'gradient': self.gradient.saveState(),
|
||||||
|
'levels': self.getLevels(),
|
||||||
|
}
|
||||||
|
|
||||||
|
def restoreState(self, state):
|
||||||
|
self.gradient.restoreState(state['gradient'])
|
||||||
|
self.setLevels(*state['levels'])
|
||||||
|
|
Loading…
Reference in New Issue
Block a user